From 463284f0afd4a7845fa3018666a35a2b523cad1f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 25 Aug 2021 10:43:54 +0200 Subject: [PATCH] Move `LineWrapper` into gpui --- Cargo.lock | 1 + gpui/Cargo.toml | 1 + gpui/src/elements/text.rs | 1 - gpui/src/text_layout.rs | 375 ++++++++++++++++++- zed/src/editor/display_map.rs | 1 - zed/src/editor/display_map/line_wrapper.rs | 403 --------------------- zed/src/editor/display_map/wrap_map.rs | 19 +- 7 files changed, 391 insertions(+), 410 deletions(-) delete mode 100644 zed/src/editor/display_map/line_wrapper.rs diff --git a/Cargo.lock b/Cargo.lock index 426b99d932..ec4cfc74fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2167,6 +2167,7 @@ dependencies = [ "font-kit", "foreign-types", "gpui_macros", + "lazy_static", "log", "metal", "num_cpus", diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index be3d47e42a..b6b29c39e2 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -11,6 +11,7 @@ backtrace = "0.3" ctor = "0.1" etagere = "0.2" gpui_macros = { path = "../gpui_macros" } +lazy_static = "1.4.0" log = "0.4" num_cpus = "1.13" ordered-float = "2.1.1" diff --git a/gpui/src/elements/text.rs b/gpui/src/elements/text.rs index 55f6344708..4c6d0559e6 100644 --- a/gpui/src/elements/text.rs +++ b/gpui/src/elements/text.rs @@ -56,7 +56,6 @@ impl Element for Text { .font_cache .select_font(self.family_id, &self.style.font_properties) .unwrap(); - todo!() // let line = // cx.text_layout_cache diff --git a/gpui/src/text_layout.rs b/gpui/src/text_layout.rs index d9103498c4..e19f82ed55 100644 --- a/gpui/src/text_layout.rs +++ b/gpui/src/text_layout.rs @@ -5,8 +5,9 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, - platform, scene, PaintContext, + platform, scene, FontSystem, PaintContext, }; +use lazy_static::lazy_static; use ordered_float::OrderedFloat; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; @@ -14,6 +15,8 @@ use std::{ borrow::Borrow, collections::HashMap, hash::{Hash, Hasher}, + iter, + ops::{Deref, DerefMut}, sync::Arc, }; @@ -255,3 +258,373 @@ impl Run { &self.glyphs } } + +lazy_static! { + static ref WRAPPER_POOL: Mutex> = Default::default(); +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Boundary { + pub ix: usize, + pub next_indent: u32, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct ShapedBoundary { + pub run_ix: usize, + pub glyph_ix: usize, +} + +impl Boundary { + fn new(ix: usize, next_indent: u32) -> Self { + Self { ix, next_indent } + } +} + +pub struct LineWrapper { + font_system: Arc, + font_id: FontId, + font_size: f32, + cached_ascii_char_widths: [f32; 128], + cached_other_char_widths: HashMap, +} + +impl LineWrapper { + pub const MAX_INDENT: u32 = 256; + + pub fn acquire( + font_id: FontId, + font_size: f32, + font_system: Arc, + ) -> LineWrapperHandle { + let wrapper = if let Some(mut wrapper) = WRAPPER_POOL.lock().pop() { + if wrapper.font_id != font_id || wrapper.font_size != font_size { + wrapper.cached_ascii_char_widths = [f32::NAN; 128]; + wrapper.cached_other_char_widths.clear(); + } + wrapper + } else { + LineWrapper::new(font_id, font_size, font_system) + }; + LineWrapperHandle(Some(wrapper)) + } + + pub fn new(font_id: FontId, font_size: f32, font_system: Arc) -> Self { + Self { + font_system, + font_id, + font_size, + cached_ascii_char_widths: [f32::NAN; 128], + cached_other_char_widths: HashMap::new(), + } + } + + pub fn wrap_line<'a>( + &'a mut self, + line: &'a str, + wrap_width: f32, + ) -> impl Iterator + 'a { + let mut width = 0.0; + let mut first_non_whitespace_ix = None; + let mut indent = None; + let mut last_candidate_ix = 0; + let mut last_candidate_width = 0.0; + let mut last_wrap_ix = 0; + let mut prev_c = '\0'; + let mut char_indices = line.char_indices(); + iter::from_fn(move || { + while let Some((ix, c)) = char_indices.next() { + if c == '\n' { + continue; + } + + if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() { + last_candidate_ix = ix; + last_candidate_width = width; + } + + if c != ' ' && first_non_whitespace_ix.is_none() { + first_non_whitespace_ix = Some(ix); + } + + let char_width = self.width_for_char(c); + width += char_width; + if width > wrap_width && ix > last_wrap_ix { + if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix) + { + indent = Some( + Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32), + ); + } + + if last_candidate_ix > 0 { + last_wrap_ix = last_candidate_ix; + width -= last_candidate_width; + last_candidate_ix = 0; + } else { + last_wrap_ix = ix; + width = char_width; + } + + let indent_width = + indent.map(|indent| indent as f32 * self.width_for_char(' ')); + width += indent_width.unwrap_or(0.); + + return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0))); + } + prev_c = c; + } + + None + }) + } + + pub fn wrap_shaped_line<'a>( + &'a mut self, + str: &'a str, + line: &'a Line, + wrap_width: f32, + ) -> impl Iterator + 'a { + let mut first_non_whitespace_ix = None; + let mut last_candidate_ix = None; + let mut last_candidate_x = 0.0; + let mut last_wrap_ix = ShapedBoundary { + run_ix: 0, + glyph_ix: 0, + }; + let mut last_wrap_x = 0.; + let mut prev_c = '\0'; + let mut glyphs = line + .runs() + .iter() + .enumerate() + .flat_map(move |(run_ix, run)| { + run.glyphs() + .iter() + .enumerate() + .map(move |(glyph_ix, glyph)| { + let character = str[glyph.index..].chars().next().unwrap(); + ( + ShapedBoundary { run_ix, glyph_ix }, + character, + glyph.position.x(), + ) + }) + }); + + iter::from_fn(move || { + while let Some((ix, c, x)) = glyphs.next() { + if c == '\n' { + continue; + } + + if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() { + last_candidate_ix = Some(ix); + last_candidate_x = x; + } + + if c != ' ' && first_non_whitespace_ix.is_none() { + first_non_whitespace_ix = Some(ix); + } + + let width = x - last_wrap_x; + if width > wrap_width && ix > last_wrap_ix { + if let Some(last_candidate_ix) = last_candidate_ix.take() { + last_wrap_ix = last_candidate_ix; + last_wrap_x = last_candidate_x; + } else { + last_wrap_ix = ix; + last_wrap_x = x; + } + + return Some(last_wrap_ix); + } + prev_c = c; + } + + None + }) + } + + fn is_boundary(&self, prev: char, next: char) -> bool { + (prev == ' ') && (next != ' ') + } + + #[inline(always)] + fn width_for_char(&mut self, c: char) -> f32 { + if (c as u32) < 128 { + let mut width = self.cached_ascii_char_widths[c as usize]; + if width.is_nan() { + width = self.compute_width_for_char(c); + self.cached_ascii_char_widths[c as usize] = width; + } + width + } else { + let mut width = self + .cached_other_char_widths + .get(&c) + .copied() + .unwrap_or(f32::NAN); + if width.is_nan() { + width = self.compute_width_for_char(c); + self.cached_other_char_widths.insert(c, width); + } + width + } + } + + fn compute_width_for_char(&self, c: char) -> f32 { + self.font_system + .layout_line( + &c.to_string(), + self.font_size, + &[(1, self.font_id, Default::default())], + ) + .width + } +} + +pub struct LineWrapperHandle(Option); + +impl Drop for LineWrapperHandle { + fn drop(&mut self) { + let wrapper = self.0.take().unwrap(); + WRAPPER_POOL.lock().push(wrapper) + } +} + +impl Deref for LineWrapperHandle { + type Target = LineWrapper; + + fn deref(&self) -> &Self::Target { + self.0.as_ref().unwrap() + } +} + +impl DerefMut for LineWrapperHandle { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut().unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + color::Color, + fonts::{Properties, Weight}, + }; + + #[crate::test(self)] + fn test_wrap_line(cx: &mut crate::MutableAppContext) { + let font_cache = cx.font_cache().clone(); + let font_system = cx.platform().fonts(); + let family = font_cache.load_family(&["Courier"]).unwrap(); + let font_id = font_cache.select_font(family, &Default::default()).unwrap(); + + let mut wrapper = LineWrapper::new(font_id, 16., font_system); + assert_eq!( + wrapper + .wrap_line("aa bbb cccc ddddd eeee", 72.0) + .collect::>(), + &[ + Boundary::new(7, 0), + Boundary::new(12, 0), + Boundary::new(18, 0) + ], + ); + assert_eq!( + wrapper + .wrap_line("aaa aaaaaaaaaaaaaaaaaa", 72.0) + .collect::>(), + &[ + Boundary::new(4, 0), + Boundary::new(11, 0), + Boundary::new(18, 0) + ], + ); + assert_eq!( + wrapper.wrap_line(" aaaaaaa", 72.).collect::>(), + &[ + Boundary::new(7, 5), + Boundary::new(9, 5), + Boundary::new(11, 5), + ] + ); + assert_eq!( + wrapper + .wrap_line(" ", 72.) + .collect::>(), + &[ + Boundary::new(7, 0), + Boundary::new(14, 0), + Boundary::new(21, 0) + ] + ); + assert_eq!( + wrapper + .wrap_line(" aaaaaaaaaaaaaa", 72.) + .collect::>(), + &[ + Boundary::new(7, 0), + Boundary::new(14, 3), + Boundary::new(18, 3), + Boundary::new(22, 3), + ] + ); + } + + #[crate::test(self)] + fn test_wrap_layout_line(cx: &mut crate::MutableAppContext) { + let font_cache = cx.font_cache().clone(); + let font_system = cx.platform().fonts(); + let text_layout_cache = TextLayoutCache::new(font_system.clone()); + + let family = font_cache.load_family(&["Helvetica"]).unwrap(); + let font_id = font_cache.select_font(family, &Default::default()).unwrap(); + let normal = font_cache.select_font(family, &Default::default()).unwrap(); + let bold = font_cache + .select_font( + family, + &Properties { + weight: Weight::BOLD, + ..Default::default() + }, + ) + .unwrap(); + + let text = "aa bbb cccc ddddd eeee"; + let line = text_layout_cache.layout_str( + text, + 16.0, + &[ + (4, normal, Color::default()), + (5, bold, Color::default()), + (6, normal, Color::default()), + (1, bold, Color::default()), + (7, normal, Color::default()), + ], + ); + + let mut wrapper = LineWrapper::new(font_id, 16., font_system); + assert_eq!( + wrapper + .wrap_shaped_line(&text, &line, 72.0) + .collect::>(), + &[ + ShapedBoundary { + run_ix: 1, + glyph_ix: 3 + }, + ShapedBoundary { + run_ix: 2, + glyph_ix: 3 + }, + ShapedBoundary { + run_ix: 4, + glyph_ix: 2 + } + ], + ); + } +} diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index 83b1575ab7..e118f3202d 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -1,5 +1,4 @@ mod fold_map; -mod line_wrapper; mod tab_map; mod wrap_map; diff --git a/zed/src/editor/display_map/line_wrapper.rs b/zed/src/editor/display_map/line_wrapper.rs deleted file mode 100644 index 696a2ac2dd..0000000000 --- a/zed/src/editor/display_map/line_wrapper.rs +++ /dev/null @@ -1,403 +0,0 @@ -use crate::Settings; -use gpui::{fonts::FontId, text_layout::Line, FontCache, FontSystem}; -use lazy_static::lazy_static; -use parking_lot::Mutex; -use std::{ - collections::HashMap, - iter, - ops::{Deref, DerefMut}, - sync::Arc, -}; - -lazy_static! { - static ref POOL: Mutex> = Default::default(); -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Boundary { - pub ix: usize, - pub next_indent: u32, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct ShapedBoundary { - pub run_ix: usize, - pub glyph_ix: usize, -} - -impl Boundary { - fn new(ix: usize, next_indent: u32) -> Self { - Self { ix, next_indent } - } -} - -pub struct LineWrapper { - font_system: Arc, - font_id: FontId, - font_size: f32, - cached_ascii_char_widths: [f32; 128], - cached_other_char_widths: HashMap, -} - -impl LineWrapper { - pub const MAX_INDENT: u32 = 256; - - pub fn acquire( - font_system: Arc, - font_cache: &FontCache, - settings: Settings, - ) -> LineWrapperHandle { - let wrapper = if let Some(mut wrapper) = POOL.lock().pop() { - let font_id = font_cache - .select_font(settings.buffer_font_family, &Default::default()) - .unwrap(); - let font_size = settings.buffer_font_size; - if wrapper.font_id != font_id || wrapper.font_size != font_size { - wrapper.cached_ascii_char_widths = [f32::NAN; 128]; - wrapper.cached_other_char_widths.clear(); - } - wrapper - } else { - LineWrapper::new(font_system, font_cache, settings) - }; - LineWrapperHandle(Some(wrapper)) - } - - pub fn new( - font_system: Arc, - font_cache: &FontCache, - settings: Settings, - ) -> Self { - let font_id = font_cache - .select_font(settings.buffer_font_family, &Default::default()) - .unwrap(); - let font_size = settings.buffer_font_size; - Self { - font_system, - font_id, - font_size, - cached_ascii_char_widths: [f32::NAN; 128], - cached_other_char_widths: HashMap::new(), - } - } - - pub fn wrap_line<'a>( - &'a mut self, - line: &'a str, - wrap_width: f32, - ) -> impl Iterator + 'a { - let mut width = 0.0; - let mut first_non_whitespace_ix = None; - let mut indent = None; - let mut last_candidate_ix = 0; - let mut last_candidate_width = 0.0; - let mut last_wrap_ix = 0; - let mut prev_c = '\0'; - let mut char_indices = line.char_indices(); - iter::from_fn(move || { - while let Some((ix, c)) = char_indices.next() { - if c == '\n' { - continue; - } - - if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() { - last_candidate_ix = ix; - last_candidate_width = width; - } - - if c != ' ' && first_non_whitespace_ix.is_none() { - first_non_whitespace_ix = Some(ix); - } - - let char_width = self.width_for_char(c); - width += char_width; - if width > wrap_width && ix > last_wrap_ix { - if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix) - { - indent = Some( - Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32), - ); - } - - if last_candidate_ix > 0 { - last_wrap_ix = last_candidate_ix; - width -= last_candidate_width; - last_candidate_ix = 0; - } else { - last_wrap_ix = ix; - width = char_width; - } - - let indent_width = - indent.map(|indent| indent as f32 * self.width_for_char(' ')); - width += indent_width.unwrap_or(0.); - - return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0))); - } - prev_c = c; - } - - None - }) - } - - pub fn wrap_shaped_line<'a>( - &'a mut self, - str: &'a str, - line: &'a Line, - wrap_width: f32, - ) -> impl Iterator + 'a { - let mut width = 0.0; - let mut first_non_whitespace_ix = None; - let mut last_candidate_ix = None; - let mut last_candidate_x = 0.0; - let mut last_wrap_ix = ShapedBoundary { - run_ix: 0, - glyph_ix: 0, - }; - let mut last_wrap_x = 0.; - let mut prev_c = '\0'; - let mut glyphs = line - .runs() - .iter() - .enumerate() - .flat_map(move |(run_ix, run)| { - run.glyphs() - .iter() - .enumerate() - .map(move |(glyph_ix, glyph)| { - let character = str[glyph.index..].chars().next().unwrap(); - ( - ShapedBoundary { run_ix, glyph_ix }, - character, - glyph.position.x(), - ) - }) - }); - - iter::from_fn(move || { - while let Some((ix, c, x)) = glyphs.next() { - if c == '\n' { - continue; - } - - if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() { - last_candidate_ix = Some(ix); - last_candidate_x = x; - } - - if c != ' ' && first_non_whitespace_ix.is_none() { - first_non_whitespace_ix = Some(ix); - } - - let width = x - last_wrap_x; - if width > wrap_width && ix > last_wrap_ix { - if let Some(last_candidate_ix) = last_candidate_ix.take() { - last_wrap_ix = last_candidate_ix; - last_wrap_x = last_candidate_x; - } else { - last_wrap_ix = ix; - last_wrap_x = x; - } - - return Some(last_wrap_ix); - } - prev_c = c; - } - - None - }) - } - - fn is_boundary(&self, prev: char, next: char) -> bool { - (prev == ' ') && (next != ' ') - } - - #[inline(always)] - fn width_for_char(&mut self, c: char) -> f32 { - if (c as u32) < 128 { - let mut width = self.cached_ascii_char_widths[c as usize]; - if width.is_nan() { - width = self.compute_width_for_char(c); - self.cached_ascii_char_widths[c as usize] = width; - } - width - } else { - let mut width = self - .cached_other_char_widths - .get(&c) - .copied() - .unwrap_or(f32::NAN); - if width.is_nan() { - width = self.compute_width_for_char(c); - self.cached_other_char_widths.insert(c, width); - } - width - } - } - - fn compute_width_for_char(&self, c: char) -> f32 { - self.font_system - .layout_line( - &c.to_string(), - self.font_size, - &[(1, self.font_id, Default::default())], - ) - .width - } -} - -pub struct LineWrapperHandle(Option); - -impl Drop for LineWrapperHandle { - fn drop(&mut self) { - let wrapper = self.0.take().unwrap(); - POOL.lock().push(wrapper) - } -} - -impl Deref for LineWrapperHandle { - type Target = LineWrapper; - - fn deref(&self) -> &Self::Target { - self.0.as_ref().unwrap() - } -} - -impl DerefMut for LineWrapperHandle { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0.as_mut().unwrap() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use gpui::{ - color::Color, - fonts::{Properties, Weight}, - TextLayoutCache, - }; - - #[gpui::test] - fn test_wrap_line(cx: &mut gpui::MutableAppContext) { - let font_cache = cx.font_cache().clone(); - let font_system = cx.platform().fonts(); - let settings = Settings { - tab_size: 4, - buffer_font_family: font_cache.load_family(&["Courier"]).unwrap(), - buffer_font_size: 16.0, - ..Settings::new(&font_cache).unwrap() - }; - - let mut wrapper = LineWrapper::new(font_system, &font_cache, settings); - assert_eq!( - wrapper - .wrap_line("aa bbb cccc ddddd eeee", 72.0) - .collect::>(), - &[ - Boundary::new(7, 0), - Boundary::new(12, 0), - Boundary::new(18, 0) - ], - ); - assert_eq!( - wrapper - .wrap_line("aaa aaaaaaaaaaaaaaaaaa", 72.0) - .collect::>(), - &[ - Boundary::new(4, 0), - Boundary::new(11, 0), - Boundary::new(18, 0) - ], - ); - assert_eq!( - wrapper.wrap_line(" aaaaaaa", 72.).collect::>(), - &[ - Boundary::new(7, 5), - Boundary::new(9, 5), - Boundary::new(11, 5), - ] - ); - assert_eq!( - wrapper - .wrap_line(" ", 72.) - .collect::>(), - &[ - Boundary::new(7, 0), - Boundary::new(14, 0), - Boundary::new(21, 0) - ] - ); - assert_eq!( - wrapper - .wrap_line(" aaaaaaaaaaaaaa", 72.) - .collect::>(), - &[ - Boundary::new(7, 0), - Boundary::new(14, 3), - Boundary::new(18, 3), - Boundary::new(22, 3), - ] - ); - } - - #[gpui::test] - fn test_wrap_layout_line(cx: &mut gpui::MutableAppContext) { - let font_cache = cx.font_cache().clone(); - let font_system = cx.platform().fonts(); - let text_layout_cache = TextLayoutCache::new(font_system.clone()); - - let family = font_cache.load_family(&["Helvetica"]).unwrap(); - let settings = Settings { - tab_size: 4, - buffer_font_family: family, - buffer_font_size: 16.0, - ..Settings::new(&font_cache).unwrap() - }; - let normal = font_cache.select_font(family, &Default::default()).unwrap(); - let bold = font_cache - .select_font( - family, - &Properties { - weight: Weight::BOLD, - ..Default::default() - }, - ) - .unwrap(); - - let text = "aa bbb cccc ddddd eeee"; - let line = text_layout_cache.layout_str( - text, - 16.0, - &[ - (4, normal, Color::default()), - (5, bold, Color::default()), - (6, normal, Color::default()), - (1, bold, Color::default()), - (7, normal, Color::default()), - ], - ); - - let mut wrapper = LineWrapper::new(font_system, &font_cache, settings); - assert_eq!( - wrapper - .wrap_shaped_line(&text, &line, 72.0) - .collect::>(), - &[ - ShapedBoundary { - run_ix: 1, - glyph_ix: 3 - }, - ShapedBoundary { - run_ix: 2, - glyph_ix: 3 - }, - ShapedBoundary { - run_ix: 4, - glyph_ix: 2 - } - ], - ); - } -} diff --git a/zed/src/editor/display_map/wrap_map.rs b/zed/src/editor/display_map/wrap_map.rs index 1d5359eabf..c8c8f067ca 100644 --- a/zed/src/editor/display_map/wrap_map.rs +++ b/zed/src/editor/display_map/wrap_map.rs @@ -1,11 +1,11 @@ use super::{ fold_map, - line_wrapper::LineWrapper, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary}, }; use crate::{editor::Point, settings::HighlightId, util::Bias, Settings}; use gpui::{ sum_tree::{self, Cursor, SumTree}, + text_layout::LineWrapper, Entity, ModelContext, Task, }; use lazy_static::lazy_static; @@ -119,7 +119,11 @@ impl WrapMap { let font_cache = cx.font_cache().clone(); let settings = self.settings.clone(); let task = cx.background().spawn(async move { - let mut line_wrapper = LineWrapper::acquire(font_system, &font_cache, settings); + let font_id = font_cache + .select_font(settings.buffer_font_family, &Default::default()) + .unwrap(); + let mut line_wrapper = + LineWrapper::acquire(font_id, settings.buffer_font_size, font_system); let tab_snapshot = new_snapshot.tab_snapshot.clone(); let range = TabPoint::zero()..tab_snapshot.max_point(); new_snapshot @@ -193,7 +197,11 @@ impl WrapMap { let font_cache = cx.font_cache().clone(); let settings = self.settings.clone(); let update_task = cx.background().spawn(async move { - let mut line_wrapper = LineWrapper::acquire(font_system, &font_cache, settings); + let font_id = font_cache + .select_font(settings.buffer_font_family, &Default::default()) + .unwrap(); + let mut line_wrapper = + LineWrapper::acquire(font_id, settings.buffer_font_size, font_system); for (tab_snapshot, edits) in pending_edits { snapshot .update(tab_snapshot, &edits, wrap_width, &mut line_wrapper) @@ -941,7 +949,10 @@ mod tests { .add_model(|cx| WrapMap::new(tabs_snapshot.clone(), settings.clone(), wrap_width, cx)); let (_observer, notifications) = Observer::new(&wrap_map, &mut cx); - let mut line_wrapper = LineWrapper::new(font_system, &font_cache, settings); + let font_id = font_cache + .select_font(settings.buffer_font_family, &Default::default()) + .unwrap(); + let mut line_wrapper = LineWrapper::new(font_id, settings.buffer_font_size, font_system); let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);