diff --git a/zed/src/editor.rs b/zed/src/editor.rs index a19b63c5ce93ff7b57ac2190117ed41c562d9c21..3157b0cd2fbb28010d13d2b6ab5da4ec4ba35f6c 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -348,7 +348,7 @@ impl Editor { cx: &mut ViewContext, ) -> Self { let display_map = - cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.borrow().clone(), None, cx)); + cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.clone(), None, cx)); cx.observe(&buffer, Self::on_buffer_changed).detach(); cx.subscribe(&buffer, Self::on_buffer_event).detach(); cx.observe(&display_map, Self::on_display_map_changed) diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index 425c4b97a5ec908f1a109b2fa6b63b27e5076b9b..16d4da79b4c6446fa02f42c10469297dc397ccb0 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -5,6 +5,7 @@ mod wrap_map; use super::{buffer, Anchor, Bias, Buffer, Point, Settings, ToOffset, ToPoint}; use fold_map::FoldMap; use gpui::{Entity, ModelContext, ModelHandle}; +use postage::watch; use std::ops::Range; use tab_map::TabMap; pub use wrap_map::BufferRows; @@ -24,12 +25,12 @@ impl Entity for DisplayMap { impl DisplayMap { pub fn new( buffer: ModelHandle, - settings: Settings, + settings: watch::Receiver, wrap_width: Option, cx: &mut ModelContext, ) -> Self { let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx); - let (tab_map, snapshot) = TabMap::new(snapshot, settings.tab_size); + let (tab_map, snapshot) = TabMap::new(snapshot, settings.borrow().tab_size); let wrap_map = cx.add_model(|cx| WrapMap::new(snapshot, settings, wrap_width, cx)); cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach(); DisplayMap { @@ -387,6 +388,7 @@ mod tests { let text = RandomCharIter::new(&mut rng).take(len).collect::(); Buffer::new(0, text, cx) }); + let settings = watch::channel_with(settings).1; let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx)); let (_observer, notifications) = Observer::new(&map, &mut cx); @@ -543,7 +545,8 @@ mod tests { let text = "one two three four five\nsix seven eight"; let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx)); - let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx)); + let (mut settings_tx, settings_rx) = watch::channel_with(settings); + let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings_rx, wrap_width, cx)); let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx)); assert_eq!( @@ -599,23 +602,28 @@ mod tests { snapshot.chunks_at(1).collect::(), "three four \nfive\nsix and \nseven eight" ); + + // Re-wrap on font size changes + settings_tx.borrow_mut().buffer_font_size += 3.; + + map.next_notification(&mut cx).await; + + let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx)); + assert_eq!( + snapshot.chunks_at(1).collect::(), + "three \nfour five\nsix and \nseven \neight" + ) } #[gpui::test] fn test_chunks_at(cx: &mut gpui::MutableAppContext) { let text = sample_text(6, 6); let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - let map = cx.add_model(|cx| { - DisplayMap::new( - buffer.clone(), - Settings { - tab_size: 4, - ..Settings::test(cx) - }, - None, - cx, - ) + let settings = watch::channel_with(Settings { + tab_size: 4, + ..Settings::test(cx) }); + let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.1, None, cx)); buffer.update(cx, |buffer, cx| { buffer.edit( vec![ @@ -687,17 +695,13 @@ mod tests { }); buffer.condition(&cx, |buf, _| !buf.is_parsing()).await; - let map = cx.add_model(|cx| { - DisplayMap::new( - buffer, - Settings { - tab_size: 2, - ..Settings::test(cx) - }, - None, - cx, - ) + let settings = cx.update(|cx| { + watch::channel_with(Settings { + tab_size: 2, + ..Settings::test(cx) + }) }); + let map = cx.add_model(|cx| DisplayMap::new(buffer, settings.1, None, cx)); assert_eq!( cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)), vec![ @@ -778,13 +782,15 @@ mod tests { buffer.condition(&cx, |buf, _| !buf.is_parsing()).await; let font_cache = cx.font_cache(); - let settings = Settings { - tab_size: 4, - buffer_font_family: font_cache.load_family(&["Courier"]).unwrap(), - buffer_font_size: 16.0, - ..cx.read(Settings::test) - }; - let map = cx.add_model(|cx| DisplayMap::new(buffer, settings, Some(40.0), cx)); + let settings = cx.update(|cx| { + watch::channel_with(Settings { + tab_size: 4, + buffer_font_family: font_cache.load_family(&["Courier"]).unwrap(), + buffer_font_size: 16.0, + ..Settings::test(cx) + }) + }); + let map = cx.add_model(|cx| DisplayMap::new(buffer, settings.1, Some(40.0), cx)); assert_eq!( cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)), [ @@ -819,17 +825,11 @@ mod tests { let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n"; let display_text = "\n'a', 'α', '✋', '❎', '🍐'\n"; let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - let map = cx.add_model(|cx| { - DisplayMap::new( - buffer.clone(), - Settings { - tab_size: 4, - ..Settings::test(cx) - }, - None, - cx, - ) + let settings = watch::channel_with(Settings { + tab_size: 4, + ..Settings::test(cx) }); + let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.1, None, cx)); let map = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!(map.text(), display_text); @@ -863,17 +863,11 @@ mod tests { fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) { let text = "✅\t\tα\nβ\t\n🏀β\t\tγ"; let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - let map = cx.add_model(|cx| { - DisplayMap::new( - buffer.clone(), - Settings { - tab_size: 4, - ..Settings::test(cx) - }, - None, - cx, - ) + let settings = watch::channel_with(Settings { + tab_size: 4, + ..Settings::test(cx) }); + let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.1, None, cx)); let map = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!(map.text(), "✅ α\nβ \n🏀β γ"); assert_eq!( @@ -930,17 +924,11 @@ mod tests { #[gpui::test] fn test_max_point(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "aaa\n\t\tbbb", cx)); - let map = cx.add_model(|cx| { - DisplayMap::new( - buffer.clone(), - Settings { - tab_size: 4, - ..Settings::test(cx) - }, - None, - cx, - ) + let settings = watch::channel_with(Settings { + tab_size: 4, + ..Settings::test(cx) }); + let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings.1, None, cx)); assert_eq!( map.update(cx, |map, cx| map.snapshot(cx)).max_point(), DisplayPoint::new(1, 11) diff --git a/zed/src/editor/display_map/wrap_map.rs b/zed/src/editor/display_map/wrap_map.rs index 8720e39979a70f6abaa1925ea3f5cc146e905086..d648a052aab0c6fb4ffc626d803d47983568d3a7 100644 --- a/zed/src/editor/display_map/wrap_map.rs +++ b/zed/src/editor/display_map/wrap_map.rs @@ -9,6 +9,7 @@ use gpui::{ Entity, ModelContext, Task, }; use lazy_static::lazy_static; +use postage::{prelude::Stream, watch}; use smol::future::yield_now; use std::{collections::VecDeque, ops::Range, time::Duration}; @@ -17,7 +18,8 @@ pub struct WrapMap { pending_edits: VecDeque<(TabSnapshot, Vec)>, wrap_width: Option, background_task: Option>, - settings: Settings, + _watch_settings: Task<()>, + settings: watch::Receiver, } impl Entity for WrapMap { @@ -74,18 +76,39 @@ pub struct BufferRows<'a> { impl WrapMap { pub fn new( tab_snapshot: TabSnapshot, - settings: Settings, + settings: watch::Receiver, wrap_width: Option, cx: &mut ModelContext, ) -> Self { + let _watch_settings = cx.spawn_weak({ + let mut prev_font = ( + settings.borrow().buffer_font_size, + settings.borrow().buffer_font_family, + ); + let mut settings = settings.clone(); + move |this, mut cx| async move { + while let Some(settings) = settings.recv().await { + if let Some(this) = this.upgrade(&cx) { + let font = (settings.buffer_font_size, settings.buffer_font_family); + if font != prev_font { + prev_font = font; + this.update(&mut cx, |this, cx| this.rewrap(cx)); + } + } + } + } + }); + let mut this = Self { - background_task: None, wrap_width: None, pending_edits: Default::default(), snapshot: Snapshot::new(tab_snapshot), settings, + background_task: None, + _watch_settings, }; this.set_wrap_width(wrap_width, cx); + this } @@ -111,17 +134,25 @@ impl WrapMap { } self.wrap_width = wrap_width; + self.rewrap(cx); + true + } + + fn rewrap(&mut self, cx: &mut ModelContext) { self.background_task.take(); - if let Some(wrap_width) = wrap_width { + if let Some(wrap_width) = self.wrap_width { let mut new_snapshot = self.snapshot.clone(); let font_cache = cx.font_cache().clone(); let settings = self.settings.clone(); let task = cx.background().spawn(async move { - let font_id = font_cache - .select_font(settings.buffer_font_family, &Default::default()) - .unwrap(); - let mut line_wrapper = font_cache.line_wrapper(font_id, settings.buffer_font_size); + let mut line_wrapper = { + let settings = settings.borrow(); + let font_id = font_cache + .select_font(settings.buffer_font_family, &Default::default()) + .unwrap(); + font_cache.line_wrapper(font_id, settings.buffer_font_size) + }; let tab_snapshot = new_snapshot.tab_snapshot.clone(); let range = TabPoint::zero()..tab_snapshot.max_point(); new_snapshot @@ -144,6 +175,7 @@ impl WrapMap { { Ok(snapshot) => { self.snapshot = snapshot; + cx.notify(); } Err(wrap_task) => { self.background_task = Some(cx.spawn(|this, mut cx| async move { @@ -166,8 +198,6 @@ impl WrapMap { .push(Transform::isomorphic(summary), &()); } } - - true } fn flush_edits(&mut self, cx: &mut ModelContext) { @@ -194,11 +224,14 @@ impl WrapMap { let font_cache = cx.font_cache().clone(); let settings = self.settings.clone(); let update_task = cx.background().spawn(async move { - let font_id = font_cache - .select_font(settings.buffer_font_family, &Default::default()) - .unwrap(); - let mut line_wrapper = - font_cache.line_wrapper(font_id, settings.buffer_font_size); + let mut line_wrapper = { + let settings = settings.borrow(); + let font_id = font_cache + .select_font(settings.buffer_font_family, &Default::default()) + .unwrap(); + font_cache.line_wrapper(font_id, settings.buffer_font_size) + }; + for (tab_snapshot, edits) in pending_edits { snapshot .update(tab_snapshot, &edits, wrap_width, &mut line_wrapper) @@ -942,9 +975,6 @@ mod tests { folds_snapshot.text() ); log::info!("Unwrapped text (expanded tabs): {:?}", tabs_snapshot.text()); - let wrap_map = cx - .add_model(|cx| WrapMap::new(tabs_snapshot.clone(), settings.clone(), wrap_width, cx)); - let (_observer, notifications) = Observer::new(&wrap_map, &mut cx); let font_id = font_cache .select_font(settings.buffer_font_family, &Default::default()) @@ -953,6 +983,11 @@ mod tests { let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); + let settings = watch::channel_with(settings).1; + let wrap_map = cx + .add_model(|cx| WrapMap::new(tabs_snapshot.clone(), settings.clone(), wrap_width, cx)); + let (_observer, notifications) = Observer::new(&wrap_map, &mut cx); + if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) { notifications.recv().await.unwrap(); } diff --git a/zed/src/editor/element.rs b/zed/src/editor/element.rs index 791c6a5be07446e0d468591a13005d613a31748d..8ab6c52cc5fcf9d15027a0940fbb08cbb6c2414a 100644 --- a/zed/src/editor/element.rs +++ b/zed/src/editor/element.rs @@ -8,6 +8,7 @@ use gpui::{ PathBuilder, }, json::{self, ToJson}, + keymap::Keystroke, text_layout::{self, TextLayoutCache}, AppContext, Axis, Border, Element, Event, EventContext, FontCache, LayoutContext, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, @@ -127,14 +128,14 @@ impl EditorElement { } } - fn key_down(&self, chars: &str, cx: &mut EventContext) -> bool { + fn key_down(&self, chars: &str, keystroke: &Keystroke, cx: &mut EventContext) -> bool { let view = self.view.upgrade(cx.app).unwrap(); if view.is_focused(cx.app) { if chars.is_empty() { false } else { - if chars.chars().any(|c| c.is_control()) { + if chars.chars().any(|c| c.is_control()) || keystroke.cmd || keystroke.ctrl { false } else { cx.dispatch_action(Insert(chars.to_string())); @@ -629,7 +630,9 @@ impl Element for EditorElement { delta, precise, } => self.scroll(*position, *delta, *precise, layout, paint, cx), - Event::KeyDown { chars, .. } => self.key_down(chars, cx), + Event::KeyDown { + chars, keystroke, .. + } => self.key_down(chars, keystroke, cx), _ => false, } } else { diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index 0e244995e7c78c8b148f9210e5ff22bbb0fd552e..aec3932a7d9ad96154f44e95783e9d8a70801c61 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -187,7 +187,7 @@ mod tests { #[gpui::test] fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) { - let settings = test_app_state(cx).settings.borrow().clone(); + let settings = test_app_state(cx).settings.clone(); let buffer = cx.add_model(|cx| Buffer::new(0, "a bcΔ defγ", cx)); let display_map = cx.add_model(|cx| DisplayMap::new(buffer, settings, None, cx)); let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); diff --git a/zed/src/lib.rs b/zed/src/lib.rs index e7bb31b207b86a20fe80d091611cbc6ec57947d1..162c69c1db9e7085107d4f252cb7f3b1c6825992 100644 --- a/zed/src/lib.rs +++ b/zed/src/lib.rs @@ -22,7 +22,7 @@ pub mod worktree; use crate::util::TryFutureExt; use channel::ChannelList; -use gpui::{action, ModelHandle}; +use gpui::{action, keymap::Binding, ModelHandle}; use parking_lot::Mutex; use postage::watch; use std::sync::Arc; @@ -32,6 +32,9 @@ pub use settings::Settings; action!(About); action!(Quit); action!(Authenticate); +action!(AdjustBufferFontSize, f32); + +const MIN_FONT_SIZE: f32 = 6.0; pub struct AppState { pub settings_tx: Arc>>, @@ -54,6 +57,22 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { .detach(); } }); + + cx.add_global_action({ + let settings_tx = app_state.settings_tx.clone(); + + move |action: &AdjustBufferFontSize, cx| { + let mut settings_tx = settings_tx.lock(); + let new_size = (settings_tx.borrow().buffer_font_size + action.0).max(MIN_FONT_SIZE); + settings_tx.borrow_mut().buffer_font_size = new_size; + cx.refresh_windows(); + } + }); + + cx.add_bindings(vec![ + Binding::new("cmd-=", AdjustBufferFontSize(1.), None), + Binding::new("cmd--", AdjustBufferFontSize(-1.), None), + ]) } fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {