Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
529a42e
Introduce `Rope::chunks_in_range`
as-cii May 17, 2021
f7691fc
WIP: Switch to byte-oriented indexing
as-cii May 17, 2021
72b98ad
Get buffer tests passing after switching to byte coordinates
maxbrunsfeld May 17, 2021
b3d2a70
Don't return Results from position methods in FoldMap and DisplayMap
maxbrunsfeld May 17, 2021
a9583d0
Introduce FoldMapSnapshot::chunks_at, use it in FoldMap::text
maxbrunsfeld May 17, 2021
c62a679
Use chunk-wise DisplayMap iteration when laying out lines
maxbrunsfeld May 18, 2021
6621b9b
Expand tabs correctly with multibyte characters
maxbrunsfeld May 18, 2021
f3db0dc
Start work on making row/columnwise movement work w/ byte columns
maxbrunsfeld May 19, 2021
510f204
Get cursor movement working with byte columns
maxbrunsfeld May 19, 2021
a2c36fc
Fix bugs and add tests for move_{up,down}
maxbrunsfeld May 19, 2021
92b938f
Adjust `layout_str` to use byte coordinates
maxbrunsfeld May 19, 2021
4bc1b0f
Convert fuzzy match positions to byte offsets
maxbrunsfeld May 20, 2021
558ce41
WIP - Adjust Label element to expect highlights as byte offsets
maxbrunsfeld May 20, 2021
608336c
Complete unit test for Label highlights
as-cii May 20, 2021
9653835
Express rightmost_point in terms of chars as opposed to bytes
as-cii May 20, 2021
ed57ffe
Replace `rightmost_point` with `righmost_row`
as-cii May 20, 2021
995b80f
Index into `prefix` or `path` depending on where the match was found
as-cii May 20, 2021
6a0757e
Don't store rightmost row/char-column as a Point
as-cii May 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 87 additions & 20 deletions gpui/src/elements/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ use serde_json::json;
use crate::{
color::ColorU,
font_cache::FamilyId,
fonts::Properties,
fonts::{FontId, Properties},
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::{ToJson, Value},
text_layout::Line,
AfterLayoutContext, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
AfterLayoutContext, DebugContext, Element, Event, EventContext, FontCache, LayoutContext,
PaintContext, SizeConstraint,
};
use std::{ops::Range, sync::Arc};

Expand Down Expand Up @@ -58,29 +58,19 @@ impl Label {
});
self
}
}

impl Element for Label {
type LayoutState = LayoutState;
type PaintState = ();

fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let font_id = ctx
.font_cache
.select_font(self.family_id, &self.font_properties)
.unwrap();
let text_len = self.text.chars().count();
fn layout_text(
&self,
font_cache: &FontCache,
font_id: FontId,
) -> (Vec<(Range<usize>, FontId)>, Vec<(Range<usize>, ColorU)>) {
let text_len = self.text.len();
let mut styles;
let mut colors;
if let Some(highlights) = self.highlights.as_ref() {
styles = Vec::new();
colors = Vec::new();
let highlight_font_id = ctx
.font_cache
let highlight_font_id = font_cache
.select_font(self.family_id, &highlights.font_properties)
.unwrap_or(font_id);
let mut pending_highlight: Option<Range<usize>> = None;
Expand Down Expand Up @@ -117,6 +107,24 @@ impl Element for Label {
colors = vec![(0..text_len, ColorU::black())];
}

(styles, colors)
}
}

impl Element for Label {
type LayoutState = LayoutState;
type PaintState = ();

fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
let font_id = ctx
.font_cache
.select_font(self.family_id, &self.font_properties)
.unwrap();
let (styles, colors) = self.layout_text(&ctx.font_cache, font_id);
let line =
ctx.text_layout_cache
.layout_str(self.text.as_str(), self.font_size, styles.as_slice());
Expand Down Expand Up @@ -185,3 +193,62 @@ impl ToJson for Highlights {
})
}
}

#[cfg(test)]
mod tests {
use font_kit::properties::Weight;

use super::*;

#[crate::test(self)]
fn test_layout_label_with_highlights(app: &mut crate::MutableAppContext) {
let menlo = app.font_cache().load_family(&["Menlo"]).unwrap();
let menlo_regular = app
.font_cache()
.select_font(menlo, &Properties::new())
.unwrap();
let menlo_bold = app
.font_cache()
.select_font(menlo, Properties::new().weight(Weight::BOLD))
.unwrap();
let black = ColorU::black();
let red = ColorU::new(255, 0, 0, 255);

let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0).with_highlights(
red,
*Properties::new().weight(Weight::BOLD),
vec![
".α".len(),
".αβ".len(),
".αβγδ".len(),
".αβγδε.ⓐ".len(),
".αβγδε.ⓐⓑ".len(),
],
);

let (styles, colors) = label.layout_text(app.font_cache().as_ref(), menlo_regular);
assert_eq!(styles.len(), colors.len());

let mut spans = Vec::new();
for ((style_range, font_id), (color_range, color)) in styles.into_iter().zip(colors) {
assert_eq!(style_range, color_range);
spans.push((style_range, font_id, color));
}
assert_eq!(
spans,
&[
(0..3, menlo_regular, black),
(3..4, menlo_bold, red),
(4..5, menlo_regular, black),
(5..6, menlo_bold, red),
(6..9, menlo_regular, black),
(9..10, menlo_bold, red),
(10..15, menlo_regular, black),
(15..16, menlo_bold, red),
(16..18, menlo_regular, black),
(18..19, menlo_bold, red),
(19..34, menlo_regular, black)
]
);
}
}
110 changes: 60 additions & 50 deletions gpui/src/platform/mac/fonts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,49 +189,60 @@ impl FontSystemState {
) -> Line {
let font_id_attr_name = CFString::from_static_string("zed_font_id");

let len = text.len();
let mut utf8_and_utf16_ixs = text.char_indices().chain(Some((len, '\0'))).map({
let mut utf16_ix = 0;
move |(utf8_ix, c)| {
let result = (utf8_ix, utf16_ix);
utf16_ix += c.len_utf16();
result
}
});

// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
let mut string = CFMutableAttributedString::new();
string.replace_str(&CFString::new(text), CFRange::init(0, 0));

let mut utf16_lens = text.chars().map(|c| c.len_utf16());
let mut prev_char_ix = 0;
let mut prev_utf16_ix = 0;

for (range, font_id) in runs {
let utf16_start = prev_utf16_ix
+ utf16_lens
.by_ref()
.take(range.start - prev_char_ix)
.sum::<usize>();
let utf16_end = utf16_start
+ utf16_lens
.by_ref()
.take(range.end - range.start)
.sum::<usize>();
prev_char_ix = range.end;
prev_utf16_ix = utf16_end;

let cf_range = CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
let font = &self.fonts[font_id.0];
unsafe {
string.set_attribute(
cf_range,
kCTFontAttributeName,
&font.native_font().clone_with_font_size(font_size as f64),
);
string.set_attribute(
cf_range,
font_id_attr_name.as_concrete_TypeRef(),
&CFNumber::from(font_id.0 as i64),
);
{
let mut utf8_and_utf16_ixs = utf8_and_utf16_ixs.clone();
string.replace_str(&CFString::new(text), CFRange::init(0, 0));

let mut utf8_ix = 0;
let mut utf16_ix = 0;
for (range, font_id) in runs {
while utf8_ix < range.start {
let (next_utf8_ix, next_utf16_ix) = utf8_and_utf16_ixs.next().unwrap();
utf8_ix = next_utf8_ix;
utf16_ix = next_utf16_ix;
}
let utf16_start = utf16_ix;
while utf8_ix < range.end {
let (next_utf8_ix, next_utf16_ix) = utf8_and_utf16_ixs.next().unwrap();
utf8_ix = next_utf8_ix;
utf16_ix = next_utf16_ix;
}

let cf_range =
CFRange::init(utf16_start as isize, (utf16_ix - utf16_start) as isize);
let font = &self.fonts[font_id.0];
unsafe {
string.set_attribute(
cf_range,
kCTFontAttributeName,
&font.native_font().clone_with_font_size(font_size as f64),
);
string.set_attribute(
cf_range,
font_id_attr_name.as_concrete_TypeRef(),
&CFNumber::from(font_id.0 as i64),
);
}
}
}

// Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());

let mut utf16_chars = text.encode_utf16();
let mut char_ix = 0;
let mut prev_utf16_ix = 0;

let mut utf8_ix = 0;
let mut utf16_ix = 0;
let mut runs = Vec::new();
for run in line.glyph_runs().into_iter() {
let font_id = FontId(
Expand All @@ -245,21 +256,22 @@ impl FontSystemState {
);

let mut glyphs = Vec::new();
for ((glyph_id, position), utf16_ix) in run
for ((glyph_id, position), glyph_utf16_ix) in run
.glyphs()
.iter()
.zip(run.positions().iter())
.zip(run.string_indices().iter())
{
let utf16_ix = usize::try_from(*utf16_ix).unwrap();
char_ix +=
char::decode_utf16(utf16_chars.by_ref().take(utf16_ix - prev_utf16_ix)).count();
prev_utf16_ix = utf16_ix;

let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
while utf16_ix < glyph_utf16_ix {
let (next_utf8_ix, next_utf16_ix) = utf8_and_utf16_ixs.next().unwrap();
utf8_ix = next_utf8_ix;
utf16_ix = next_utf16_ix;
}
glyphs.push(Glyph {
id: *glyph_id as GlyphId,
position: vec2f(position.x as f32, position.y as f32),
index: char_ix,
index: utf8_ix,
});
}

Expand All @@ -273,7 +285,7 @@ impl FontSystemState {
descent: typographic_bounds.descent as f32,
runs,
font_size,
len: char_ix + 1,
len,
}
}
}
Expand Down Expand Up @@ -312,7 +324,7 @@ mod tests {
}

#[test]
fn test_char_indices() -> anyhow::Result<()> {
fn test_glyph_offsets() -> anyhow::Result<()> {
let fonts = FontSystem::new();
let zapfino = fonts.load_family("Zapfino")?;
let zapfino_regular = fonts.select_font(&zapfino, &Properties::new())?;
Expand All @@ -326,7 +338,7 @@ mod tests {
&[
(0..9, zapfino_regular),
(9..22, menlo_regular),
(22..text.encode_utf16().count(), zapfino_regular),
(22..text.len(), zapfino_regular),
],
);
assert_eq!(
Expand All @@ -335,9 +347,7 @@ mod tests {
.flat_map(|r| r.glyphs.iter())
.map(|g| g.index)
.collect::<Vec<_>>(),
vec![
0, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31
]
vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
);
Ok(())
}
Expand Down
8 changes: 4 additions & 4 deletions zed/src/editor/buffer/anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,24 @@ impl Anchor {
})
}

pub fn bias_left(&self, buffer: &Buffer) -> Result<Anchor> {
pub fn bias_left(&self, buffer: &Buffer) -> Anchor {
match self {
Anchor::Start
| Anchor::Middle {
bias: AnchorBias::Left,
..
} => Ok(self.clone()),
} => self.clone(),
_ => buffer.anchor_before(self),
}
}

pub fn bias_right(&self, buffer: &Buffer) -> Result<Anchor> {
pub fn bias_right(&self, buffer: &Buffer) -> Anchor {
match self {
Anchor::End
| Anchor::Middle {
bias: AnchorBias::Right,
..
} => Ok(self.clone()),
} => self.clone(),
_ => buffer.anchor_after(self),
}
}
Expand Down
Loading