Start work on allowing blocks to be styled

This commit is contained in:
Max Brunsfeld 2021-11-18 18:16:35 -08:00
parent c04151f999
commit 8d1a4a6a24
7 changed files with 232 additions and 96 deletions

View file

@ -13,7 +13,7 @@ use language::{Anchor, Buffer, Point, ToOffset, ToPoint};
use std::{collections::HashSet, ops::Range};
use sum_tree::Bias;
use tab_map::TabMap;
use theme::SyntaxTheme;
use theme::{BlockStyle, SyntaxTheme};
use wrap_map::WrapMap;
pub trait ToDisplayPoint {
@ -172,8 +172,8 @@ impl DisplayMapSnapshot {
self.buffer_snapshot.len() == 0
}
pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
self.blocks_snapshot.buffer_rows(start_row)
pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> {
self.blocks_snapshot.buffer_rows(start_row, cx)
}
pub fn buffer_row_count(&self) -> u32 {
@ -416,6 +416,13 @@ impl ToDisplayPoint for Anchor {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DisplayRow {
Buffer(u32),
Block(BlockId, Option<BlockStyle>),
Wrap,
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -1,4 +1,7 @@
use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint};
use super::{
wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint},
BlockStyle, DisplayRow,
};
use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _};
use gpui::{fonts::HighlightStyle, AppContext, ModelHandle};
use language::{Buffer, Chunk};
@ -45,11 +48,12 @@ struct BlockRow(u32);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
struct WrapRow(u32);
struct Block {
pub struct Block {
id: BlockId,
position: Anchor,
text: Rope,
build_runs: Option<Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>>,
build_style: Option<Arc<dyn Fn(&AppContext) -> BlockStyle>>,
disposition: BlockDisposition,
}
@ -62,6 +66,7 @@ where
pub position: P,
pub text: T,
pub build_runs: Option<Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>>,
pub build_style: Option<Arc<dyn Fn(&AppContext) -> BlockStyle>>,
pub disposition: BlockDisposition,
}
@ -115,6 +120,7 @@ pub struct BufferRows<'a> {
transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
input_buffer_rows: wrap_map::BufferRows<'a>,
output_row: u32,
cx: Option<&'a AppContext>,
started: bool,
}
@ -415,6 +421,7 @@ impl<'a> BlockMapWriter<'a> {
position,
text: block.text.into(),
build_runs: block.build_runs,
build_style: block.build_style,
disposition: block.disposition,
}),
);
@ -519,7 +526,7 @@ impl BlockSnapshot {
}
}
pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
cursor.seek(&BlockRow(start_row), Bias::Right, &());
let (output_start, input_start) = cursor.start();
@ -530,6 +537,7 @@ impl BlockSnapshot {
};
let input_start_row = input_start.0 + overshoot;
BufferRows {
cx,
transforms: cursor,
input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
output_row: start_row,
@ -871,7 +879,7 @@ impl<'a> Iterator for BlockChunks<'a> {
}
impl<'a> Iterator for BufferRows<'a> {
type Item = Option<u32>;
type Item = DisplayRow;
fn next(&mut self) -> Option<Self::Item> {
if self.started {
@ -885,10 +893,13 @@ impl<'a> Iterator for BufferRows<'a> {
}
let transform = self.transforms.item()?;
if transform.is_isomorphic() {
Some(self.input_buffer_rows.next().unwrap())
if let Some(block) = &transform.block {
let style = self
.cx
.and_then(|cx| block.build_style.as_ref().map(|f| f(cx)));
Some(DisplayRow::Block(block.id, style))
} else {
Some(None)
Some(self.input_buffer_rows.next().unwrap())
}
}
}
@ -1006,6 +1017,7 @@ mod tests {
id: BlockId(0),
position: Anchor::min(),
text: "one!\ntwo three\nfour".into(),
build_style: None,
build_runs: Some(Arc::new(move |_| {
vec![(3, red.into()), (6, Default::default()), (5, blue.into())]
})),
@ -1080,25 +1092,28 @@ mod tests {
let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
writer.insert(
let block_ids = writer.insert(
vec![
BlockProperties {
position: Point::new(1, 0),
text: "BLOCK 1",
disposition: BlockDisposition::Above,
build_runs: None,
build_style: None,
},
BlockProperties {
position: Point::new(1, 2),
text: "BLOCK 2",
disposition: BlockDisposition::Above,
build_runs: None,
build_style: None,
},
BlockProperties {
position: Point::new(3, 2),
text: "BLOCK 3",
disposition: BlockDisposition::Below,
build_runs: None,
build_style: None,
},
],
cx,
@ -1181,8 +1196,16 @@ mod tests {
);
assert_eq!(
snapshot.buffer_rows(0).collect::<Vec<_>>(),
&[Some(0), None, None, Some(1), Some(2), Some(3), None]
snapshot.buffer_rows(0, None).collect::<Vec<_>>(),
&[
DisplayRow::Buffer(0),
DisplayRow::Block(block_ids[0], None),
DisplayRow::Block(block_ids[1], None),
DisplayRow::Buffer(1),
DisplayRow::Buffer(2),
DisplayRow::Buffer(3),
DisplayRow::Block(block_ids[2], None)
]
);
// Insert a line break, separating two block decorations into separate
@ -1227,12 +1250,14 @@ mod tests {
text: "<BLOCK 1",
disposition: BlockDisposition::Above,
build_runs: None,
build_style: None,
},
BlockProperties {
position: Point::new(1, 1),
text: ">BLOCK 2",
disposition: BlockDisposition::Below,
build_runs: None,
build_style: None,
},
],
cx,
@ -1325,8 +1350,9 @@ mod tests {
BlockProperties {
position,
text,
build_runs: None,
disposition,
build_runs: None,
build_style: None,
}
})
.collect::<Vec<_>>();
@ -1402,6 +1428,7 @@ mod tests {
position: BlockPoint::new(row, column),
text: block.text,
build_runs: block.build_runs.clone(),
build_style: None,
disposition: block.disposition,
},
)
@ -1424,7 +1451,7 @@ mod tests {
.to_point(WrapPoint::new(row, 0), Bias::Left)
.row;
while let Some((_, block)) = sorted_blocks.peek() {
while let Some((block_id, block)) = sorted_blocks.peek() {
if block.position.row == row && block.disposition == BlockDisposition::Above {
let text = block.text.to_string();
let padding = " ".repeat(block.position.column as usize);
@ -1434,7 +1461,7 @@ mod tests {
expected_text.push_str(line);
}
expected_text.push('\n');
expected_buffer_rows.push(None);
expected_buffer_rows.push(DisplayRow::Block(*block_id, None));
}
sorted_blocks.next();
} else {
@ -1443,10 +1470,14 @@ mod tests {
}
let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) });
expected_buffer_rows.push(if soft_wrapped {
DisplayRow::Wrap
} else {
DisplayRow::Buffer(buffer_row)
});
expected_text.push_str(input_line);
while let Some((_, block)) = sorted_blocks.peek() {
while let Some((block_id, block)) = sorted_blocks.peek() {
if block.position.row == row && block.disposition == BlockDisposition::Below {
let text = block.text.to_string();
let padding = " ".repeat(block.position.column as usize);
@ -1456,7 +1487,7 @@ mod tests {
expected_text.push_str(&padding);
expected_text.push_str(line);
}
expected_buffer_rows.push(None);
expected_buffer_rows.push(DisplayRow::Block(*block_id, None));
}
sorted_blocks.next();
} else {
@ -1480,7 +1511,7 @@ mod tests {
);
assert_eq!(
blocks_snapshot
.buffer_rows(start_row as u32)
.buffer_rows(start_row as u32, None)
.collect::<Vec<_>>(),
&expected_buffer_rows[start_row..]
);

View file

@ -2,6 +2,7 @@ use super::{
fold_map,
patch::Patch,
tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint},
DisplayRow,
};
use gpui::{
fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext,
@ -712,7 +713,11 @@ impl Snapshot {
prev_tab_row = tab_point.row();
soft_wrapped = false;
}
expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) });
expected_buffer_rows.push(if soft_wrapped {
DisplayRow::Wrap
} else {
DisplayRow::Buffer(buffer_row)
});
}
for start_display_row in 0..expected_buffer_rows.len() {
@ -792,7 +797,7 @@ impl<'a> Iterator for Chunks<'a> {
}
impl<'a> Iterator for BufferRows<'a> {
type Item = Option<u32>;
type Item = DisplayRow;
fn next(&mut self) -> Option<Self::Item> {
if self.output_row > self.max_output_row {
@ -812,7 +817,11 @@ impl<'a> Iterator for BufferRows<'a> {
self.soft_wrapped = true;
}
Some(if soft_wrapped { None } else { Some(buffer_row) })
Some(if soft_wrapped {
DisplayRow::Wrap
} else {
DisplayRow::Buffer(buffer_row)
})
}
}

View file

@ -1,6 +1,6 @@
use super::{
DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select,
SelectPhase, Snapshot, MAX_LINE_LEN,
DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll,
Select, SelectPhase, Snapshot, MAX_LINE_LEN,
};
use clock::ReplicaId;
use gpui::{
@ -25,6 +25,7 @@ use std::{
fmt::Write,
ops::Range,
};
use theme::BlockStyle;
pub struct EditorElement {
view: WeakViewHandle<Editor>,
@ -359,6 +360,30 @@ impl EditorElement {
}
if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) {
// Draw blocks
for (ixs, block_style) in &layout.block_layouts {
let row = start_row + ixs.start;
let origin = content_origin
+ vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top);
let height = ixs.len() as f32 * layout.line_height;
cx.scene.push_quad(Quad {
bounds: RectF::new(origin, vec2f(visible_text_bounds.width(), height)),
background: block_style.background,
border: block_style
.border
.map_or(Default::default(), |color| Border {
width: 1.,
color,
overlay: true,
top: true,
right: false,
bottom: true,
left: false,
}),
corner_radius: 0.,
});
}
// Draw glyphs
for (ix, line) in layout.line_layouts.iter().enumerate() {
let row = start_row + ix as u32;
@ -401,18 +426,24 @@ impl EditorElement {
.width()
}
fn layout_line_numbers(
fn layout_rows(
&self,
rows: Range<u32>,
active_rows: &BTreeMap<u32, bool>,
snapshot: &Snapshot,
cx: &LayoutContext,
) -> Vec<Option<text_layout::Line>> {
) -> (
Vec<Option<text_layout::Line>>,
Vec<(Range<u32>, BlockStyle)>,
) {
let style = &self.settings.style;
let mut layouts = Vec::with_capacity(rows.len());
let include_line_numbers = snapshot.mode == EditorMode::Full;
let mut last_block_id = None;
let mut blocks = Vec::<(Range<u32>, BlockStyle)>::new();
let mut line_number_layouts = Vec::with_capacity(rows.len());
let mut line_number = String::new();
for (ix, buffer_row) in snapshot
.buffer_rows(rows.start)
for (ix, row) in snapshot
.buffer_rows(rows.start, cx)
.take((rows.end - rows.start) as usize)
.enumerate()
{
@ -422,27 +453,46 @@ impl EditorElement {
} else {
style.line_number
};
if let Some(buffer_row) = buffer_row {
line_number.clear();
write!(&mut line_number, "{}", buffer_row + 1).unwrap();
layouts.push(Some(cx.text_layout_cache.layout_str(
&line_number,
style.text.font_size,
&[(
line_number.len(),
RunStyle {
font_id: style.text.font_id,
color,
underline: None,
},
)],
)));
} else {
layouts.push(None);
match row {
DisplayRow::Buffer(buffer_row) => {
if include_line_numbers {
line_number.clear();
write!(&mut line_number, "{}", buffer_row + 1).unwrap();
line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
&line_number,
style.text.font_size,
&[(
line_number.len(),
RunStyle {
font_id: style.text.font_id,
color,
underline: None,
},
)],
)));
}
last_block_id = None;
}
DisplayRow::Block(block_id, style) => {
let ix = ix as u32;
if last_block_id == Some(block_id) {
if let Some((row_range, _)) = blocks.last_mut() {
row_range.end += 1;
}
} else if let Some(style) = style {
blocks.push((ix..ix + 1, style));
}
line_number_layouts.push(None);
last_block_id = Some(block_id);
}
DisplayRow::Wrap => {
line_number_layouts.push(None);
last_block_id = None;
}
}
}
layouts
(line_number_layouts, blocks)
}
fn layout_lines(
@ -541,7 +591,7 @@ impl EditorElement {
}
let underline = if let Some(severity) = chunk.diagnostic {
Some(super::diagnostic_color(severity, style))
Some(super::diagnostic_style(severity, style).text)
} else {
highlight_style.underline
};
@ -669,11 +719,8 @@ impl Element for EditorElement {
}
});
let line_number_layouts = if snapshot.mode == EditorMode::Full {
self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx)
} else {
Vec::new()
};
let (line_number_layouts, block_layouts) =
self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx);
let mut max_visible_line_width = 0.0;
let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx);
@ -695,6 +742,7 @@ impl Element for EditorElement {
active_rows,
line_layouts,
line_number_layouts,
block_layouts,
line_height,
em_width,
selections,
@ -817,6 +865,7 @@ pub struct LayoutState {
active_rows: BTreeMap<u32, bool>,
line_layouts: Vec<text_layout::Line>,
line_number_layouts: Vec<Option<text_layout::Line>>,
block_layouts: Vec<(Range<u32>, BlockStyle)>,
line_height: f32,
em_width: f32,
selections: HashMap<ReplicaId, Vec<Range<DisplayPoint>>>,
@ -1071,11 +1120,11 @@ mod tests {
});
let element = EditorElement::new(editor.downgrade(), settings);
let layouts = editor.update(cx, |editor, cx| {
let (layouts, _) = editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let mut presenter = cx.build_presenter(window_id, 30.);
let mut layout_cx = presenter.build_layout_context(false, cx);
element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
element.layout_rows(0..6, &Default::default(), &snapshot, &mut layout_cx)
});
assert_eq!(layouts.len(), 6);
}

View file

@ -7,12 +7,11 @@ mod test;
use buffer::rope::TextDimension;
use clock::ReplicaId;
pub use display_map::DisplayPoint;
use display_map::*;
pub use display_map::{DisplayPoint, DisplayRow};
pub use element::*;
use gpui::{
action,
color::Color,
geometry::vector::{vec2f, Vector2F},
keymap::Binding,
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
@ -33,7 +32,7 @@ use std::{
time::Duration,
};
use sum_tree::Bias;
use theme::{EditorStyle, SyntaxTheme};
use theme::{DiagnosticStyle, EditorStyle, SyntaxTheme};
use util::post_inc;
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
@ -342,6 +341,7 @@ struct BracketPairState {
#[derive(Debug)]
struct ActiveDiagnosticGroup {
primary_range: Range<Anchor>,
group_range: Range<Anchor>,
block_ids: HashSet<BlockId>,
}
@ -2238,21 +2238,34 @@ impl Editor {
loop {
let next_group = buffer
.diagnostics_in_range::<_, usize>(search_start..buffer.len())
.filter(|(_, diagnostic)| diagnostic.is_primary)
.skip_while(|(range, _)| {
Some(range.end) == active_primary_range.as_ref().map(|r| *r.end())
})
.next()
.map(|(range, diagnostic)| (range, diagnostic.group_id));
.find_map(|(range, diagnostic)| {
if diagnostic.is_primary
&& Some(range.end) != active_primary_range.as_ref().map(|r| *r.end())
{
Some((range, diagnostic.group_id))
} else {
None
}
});
if let Some((primary_range, group_id)) = next_group {
self.dismiss_diagnostics(cx);
self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
let buffer = self.buffer.read(cx);
let mut group_end = Point::zero();
let diagnostic_group = buffer
.diagnostic_group::<Point>(group_id)
.map(|(range, diagnostic)| (range, diagnostic.clone()))
.map(|(range, diagnostic)| {
if range.end > group_end {
group_end = range.end;
}
(range, diagnostic.clone())
})
.collect::<Vec<_>>();
let group_range = buffer.anchor_after(diagnostic_group[0].0.start)
..buffer.anchor_before(group_end);
let primary_range = buffer.anchor_after(primary_range.start)
..buffer.anchor_before(primary_range.end);
@ -2265,12 +2278,24 @@ impl Editor {
BlockProperties {
position: range.start,
text: diagnostic.message.as_str(),
build_runs: Some(Arc::new(move |cx| {
let settings = build_settings.borrow()(cx);
vec![(
message_len,
diagnostic_color(severity, &settings.style).into(),
)]
build_runs: Some(Arc::new({
let build_settings = build_settings.clone();
move |cx| {
let settings = build_settings.borrow()(cx);
vec![(
message_len,
diagnostic_style(severity, &settings.style)
.text
.into(),
)]
}
})),
build_style: Some(Arc::new({
let build_settings = build_settings.clone();
move |cx| {
let settings = build_settings.borrow()(cx);
diagnostic_style(severity, &settings.style).block
}
})),
disposition: BlockDisposition::Below,
}
@ -2282,6 +2307,7 @@ impl Editor {
Some(ActiveDiagnosticGroup {
primary_range,
group_range,
block_ids,
})
});
@ -2815,8 +2841,8 @@ impl Snapshot {
self.display_snapshot.buffer_row_count()
}
pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
self.display_snapshot.buffer_rows(start_row)
pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: &'a AppContext) -> BufferRows<'a> {
self.display_snapshot.buffer_rows(start_row, Some(cx))
}
pub fn chunks<'a>(
@ -2893,10 +2919,10 @@ impl EditorSettings {
selection: Default::default(),
guest_selections: Default::default(),
syntax: Default::default(),
error_color: Default::default(),
warning_color: Default::default(),
information_color: Default::default(),
hint_color: Default::default(),
diagnostic_error: Default::default(),
diagnostic_warning: Default::default(),
diagnostic_information: Default::default(),
diagnostic_hint: Default::default(),
}
},
}
@ -3020,13 +3046,13 @@ impl SelectionExt for Selection<Point> {
}
}
pub fn diagnostic_color(severity: DiagnosticSeverity, style: &EditorStyle) -> Color {
pub fn diagnostic_style(severity: DiagnosticSeverity, style: &EditorStyle) -> DiagnosticStyle {
match severity {
DiagnosticSeverity::ERROR => style.error_color,
DiagnosticSeverity::WARNING => style.warning_color,
DiagnosticSeverity::INFORMATION => style.information_color,
DiagnosticSeverity::HINT => style.hint_color,
_ => style.text.color,
DiagnosticSeverity::ERROR => style.diagnostic_error,
DiagnosticSeverity::WARNING => style.diagnostic_warning,
DiagnosticSeverity::INFORMATION => style.diagnostic_information,
DiagnosticSeverity::HINT => style.diagnostic_hint,
_ => Default::default(),
}
}

View file

@ -227,12 +227,19 @@ pub struct EditorStyle {
pub line_number_active: Color,
pub guest_selections: Vec<SelectionStyle>,
pub syntax: Arc<SyntaxTheme>,
pub error_color: Color,
pub warning_color: Color,
pub diagnostic_error: DiagnosticStyle,
pub diagnostic_warning: DiagnosticStyle,
#[serde(default)]
pub information_color: Color,
pub diagnostic_information: DiagnosticStyle,
#[serde(default)]
pub hint_color: Color,
pub diagnostic_hint: DiagnosticStyle,
}
#[derive(Copy, Clone, Deserialize, Default)]
pub struct DiagnosticStyle {
pub text: Color,
#[serde(flatten)]
pub block: BlockStyle,
}
#[derive(Clone, Copy, Default, Deserialize)]
@ -251,6 +258,12 @@ pub struct InputEditorStyle {
pub selection: SelectionStyle,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
pub struct BlockStyle {
pub background: Option<Color>,
pub border: Option<Color>,
}
impl EditorStyle {
pub fn placeholder_text(&self) -> &TextStyle {
self.placeholder_text.as_ref().unwrap_or(&self.text)
@ -273,10 +286,10 @@ impl InputEditorStyle {
line_number_active: Default::default(),
guest_selections: Default::default(),
syntax: Default::default(),
error_color: Default::default(),
warning_color: Default::default(),
information_color: Default::default(),
hint_color: Default::default(),
diagnostic_error: Default::default(),
diagnostic_warning: Default::default(),
diagnostic_information: Default::default(),
diagnostic_hint: Default::default(),
}
}
}

View file

@ -173,7 +173,7 @@ corner_radius = 6
[project_panel]
extends = "$panel"
padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2
[project_panel.entry]
text = "$text.1"
@ -236,6 +236,7 @@ line_number_active = "$text.0.color"
selection = "$selection.host"
guest_selections = "$selection.guests"
error_color = "$status.bad"
warning_color = "$status.warn"
info_color = "$status.info"
hint_color = "$status.info"
diagnostic_error = { text = "$status.bad", border = "#ff0000", background = "#ffdddd" }
diagnostic_warning = { text = "$status.warn", border = "#ffff00", background = "#ffffdd" }
diagnostic_info = { text = "$status.info" }
diagnostic_hint = { text = "$status.info" }