diff --git a/Cargo.lock b/Cargo.lock index 27e45aa38c..dc36ca627f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1575,6 +1575,29 @@ dependencies = [ "getrandom 0.2.2", ] +[[package]] +name = "editor" +version = "0.1.0" +dependencies = [ + "anyhow", + "buffer", + "clock", + "gpui", + "lazy_static", + "log", + "parking_lot", + "postage", + "rand 0.8.3", + "serde 1.0.125", + "smallvec", + "smol", + "sum_tree", + "tree-sitter", + "tree-sitter-rust", + "unindent", + "util", +] + [[package]] name = "either" version = "1.6.1" @@ -6030,6 +6053,7 @@ dependencies = [ "ctor", "dirs 3.0.1", "easy-parallel", + "editor", "env_logger", "fsevent", "futures", diff --git a/crates/buffer/src/syntax_theme.rs b/crates/buffer/src/syntax_theme.rs index d0162f45f3..86aa5b3c47 100644 --- a/crates/buffer/src/syntax_theme.rs +++ b/crates/buffer/src/syntax_theme.rs @@ -4,6 +4,7 @@ use crate::HighlightId; use gpui::fonts::HighlightStyle; use serde::Deserialize; +#[derive(Default)] pub struct SyntaxTheme { pub(crate) highlights: Vec<(String, HighlightStyle)>, } diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml new file mode 100644 index 0000000000..3c592e69c3 --- /dev/null +++ b/crates/editor/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "editor" +version = "0.1.0" +edition = "2021" + +[features] +test-support = ["buffer/test-support"] + +[dependencies] +anyhow = "1.0" +buffer = { path = "../buffer" } +clock = { path = "../clock" } +gpui = { path = "../gpui" } +lazy_static = "1.4" +log = "0.4" +parking_lot = "0.11" +postage = { version = "0.4", features = ["futures-traits"] } +serde = { version = "1", features = ["derive", "rc"] } +smallvec = { version = "1.6", features = ["union"] } +smol = "1.2" +sum_tree = { path = "../sum_tree" } +util = { path = "../util" } + +[dev-dependencies] +rand = "0.8" +unindent = "0.1.7" +tree-sitter = "0.19" +tree-sitter-rust = "0.19" +buffer = { path = "../buffer", features = ["test-support"] } diff --git a/crates/zed/src/editor/display_map.rs b/crates/editor/src/display_map.rs similarity index 99% rename from crates/zed/src/editor/display_map.rs rename to crates/editor/src/display_map.rs index ad2b6659bd..7ba2fb3d77 100644 --- a/crates/zed/src/editor/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -357,7 +357,7 @@ impl ToDisplayPoint for Anchor { #[cfg(test)] mod tests { use super::*; - use crate::{editor::movement, test::*}; + use crate::{movement, test::*}; use buffer::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal, SyntaxTheme}; use gpui::{color::Color, MutableAppContext}; use rand::{prelude::StdRng, Rng}; diff --git a/crates/zed/src/editor/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs similarity index 99% rename from crates/zed/src/editor/display_map/fold_map.rs rename to crates/editor/src/display_map/fold_map.rs index a58033ea55..cc1f1e1a8c 100644 --- a/crates/zed/src/editor/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1128,7 +1128,7 @@ impl FoldEdit { #[cfg(test)] mod tests { use super::*; - use crate::{editor::ToPoint, test::sample_text}; + use crate::{test::sample_text, ToPoint}; use buffer::RandomCharIter; use rand::prelude::*; use std::{env, mem}; diff --git a/crates/zed/src/editor/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs similarity index 100% rename from crates/zed/src/editor/display_map/tab_map.rs rename to crates/editor/src/display_map/tab_map.rs diff --git a/crates/zed/src/editor/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs similarity index 99% rename from crates/zed/src/editor/display_map/wrap_map.rs rename to crates/editor/src/display_map/wrap_map.rs index 8377ec30a9..2b3433711e 100644 --- a/crates/zed/src/editor/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -2,8 +2,7 @@ use super::{ fold_map, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary}, }; -use crate::editor::Point; -use buffer::HighlightId; +use buffer::{HighlightId, Point}; use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task}; use lazy_static::lazy_static; use smol::future::yield_now; @@ -897,13 +896,10 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { mod tests { use super::*; use crate::{ - editor::{ - display_map::{fold_map::FoldMap, tab_map::TabMap}, - Buffer, - }, + display_map::{fold_map::FoldMap, tab_map::TabMap}, test::Observer, }; - use buffer::RandomCharIter; + use buffer::{Buffer, RandomCharIter}; use rand::prelude::*; use std::env; diff --git a/crates/zed/src/editor/element.rs b/crates/editor/src/element.rs similarity index 93% rename from crates/zed/src/editor/element.rs rename to crates/editor/src/element.rs index 05e3cbfe72..1b8420ce89 100644 --- a/crates/zed/src/editor/element.rs +++ b/crates/editor/src/element.rs @@ -1,6 +1,6 @@ use super::{ - DisplayPoint, Editor, EditorMode, EditorStyle, Insert, Scroll, Select, SelectPhase, Snapshot, - MAX_LINE_LEN, + DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Insert, Scroll, Select, + SelectPhase, Snapshot, MAX_LINE_LEN, }; use buffer::HighlightId; use clock::ReplicaId; @@ -28,12 +28,12 @@ use std::{ pub struct EditorElement { view: WeakViewHandle, - style: EditorStyle, + settings: EditorSettings, } impl EditorElement { - pub fn new(view: WeakViewHandle, style: EditorStyle) -> Self { - Self { view, style } + pub fn new(view: WeakViewHandle, settings: EditorSettings) -> Self { + Self { view, settings } } fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor { @@ -196,15 +196,16 @@ impl EditorElement { let bounds = gutter_bounds.union_rect(text_bounds); let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height; let editor = self.view(cx.app); + let style = &self.settings.style; cx.scene.push_quad(Quad { bounds: gutter_bounds, - background: Some(self.style.gutter_background), + background: Some(style.gutter_background), border: Border::new(0., Color::transparent_black()), corner_radius: 0., }); cx.scene.push_quad(Quad { bounds: text_bounds, - background: Some(self.style.background), + background: Some(style.background), border: Border::new(0., Color::transparent_black()), corner_radius: 0., }); @@ -231,7 +232,7 @@ impl EditorElement { ); cx.scene.push_quad(Quad { bounds: RectF::new(origin, size), - background: Some(self.style.active_line_background), + background: Some(style.active_line_background), border: Border::default(), corner_radius: 0., }); @@ -268,8 +269,7 @@ impl EditorElement { cx: &mut PaintContext, ) { let view = self.view(cx.app); - let settings = self.view(cx.app).settings.borrow(); - let theme = &settings.theme.editor; + let style = &self.settings.style; let local_replica_id = view.replica_id(cx); let scroll_position = layout.snapshot.scroll_position(); let start_row = scroll_position.y() as u32; @@ -287,11 +287,11 @@ impl EditorElement { let content_origin = bounds.origin() + layout.text_offset; for (replica_id, selections) in &layout.selections { - let style_ix = *replica_id as usize % (theme.guest_selections.len() + 1); + let style_ix = *replica_id as usize % (style.guest_selections.len() + 1); let style = if style_ix == 0 { - &theme.selection + &style.selection } else { - &theme.guest_selections[style_ix - 1] + &style.guest_selections[style_ix - 1] }; for selection in selections { @@ -383,15 +383,16 @@ impl EditorElement { fn max_line_number_width(&self, snapshot: &Snapshot, cx: &LayoutContext) -> f32 { let digit_count = (snapshot.buffer_row_count() as f32).log10().floor() as usize + 1; + let style = &self.settings.style; cx.text_layout_cache .layout_str( "1".repeat(digit_count).as_str(), - self.style.text.font_size, + style.text.font_size, &[( digit_count, RunStyle { - font_id: self.style.text.font_id, + font_id: style.text.font_id, color: Color::black(), underline: false, }, @@ -407,6 +408,7 @@ impl EditorElement { snapshot: &Snapshot, cx: &LayoutContext, ) -> Vec> { + let style = &self.settings.style; let mut layouts = Vec::with_capacity(rows.len()); let mut line_number = String::new(); for (ix, (buffer_row, soft_wrapped)) in snapshot @@ -416,9 +418,9 @@ impl EditorElement { { let display_row = rows.start + ix as u32; let color = if active_rows.contains_key(&display_row) { - self.style.line_number_active + style.line_number_active } else { - self.style.line_number + style.line_number }; if soft_wrapped { layouts.push(None); @@ -427,11 +429,11 @@ impl EditorElement { write!(&mut line_number, "{}", buffer_row + 1).unwrap(); layouts.push(Some(cx.text_layout_cache.layout_str( &line_number, - self.style.text.font_size, + style.text.font_size, &[( line_number.len(), RunStyle { - font_id: self.style.text.font_id, + font_id: style.text.font_id, color, underline: false, }, @@ -456,7 +458,7 @@ impl EditorElement { // When the editor is empty and unfocused, then show the placeholder. if snapshot.is_empty() && !snapshot.is_focused() { - let placeholder_style = self.style.placeholder_text(); + let placeholder_style = self.settings.style.placeholder_text(); let placeholder_text = snapshot.placeholder_text(); let placeholder_lines = placeholder_text .as_ref() @@ -482,10 +484,10 @@ impl EditorElement { .collect(); } - let mut prev_font_properties = self.style.text.font_properties.clone(); - let mut prev_font_id = self.style.text.font_id; + let style = &self.settings.style; + let mut prev_font_properties = style.text.font_properties.clone(); + let mut prev_font_id = style.text.font_id; - let theme = snapshot.theme().clone(); let mut layouts = Vec::with_capacity(rows.len()); let mut line = String::new(); let mut styles = Vec::new(); @@ -498,7 +500,7 @@ impl EditorElement { if ix > 0 { layouts.push(cx.text_layout_cache.layout_str( &line, - self.style.text.font_size, + style.text.font_size, &styles, )); line.clear(); @@ -511,17 +513,20 @@ impl EditorElement { } if !line_chunk.is_empty() && !line_exceeded_max_len { - let style = theme + let highlight_style = style .syntax .highlight_style(style_ix) - .unwrap_or(self.style.text.clone().into()); + .unwrap_or(style.text.clone().into()); // Avoid a lookup if the font properties match the previous ones. - let font_id = if style.font_properties == prev_font_properties { + let font_id = if highlight_style.font_properties == prev_font_properties { prev_font_id } else { cx.font_cache - .select_font(self.style.text.font_family_id, &style.font_properties) - .unwrap_or(self.style.text.font_id) + .select_font( + style.text.font_family_id, + &highlight_style.font_properties, + ) + .unwrap_or(style.text.font_id) }; if line.len() + line_chunk.len() > MAX_LINE_LEN { @@ -538,12 +543,12 @@ impl EditorElement { line_chunk.len(), RunStyle { font_id, - color: style.color, - underline: style.underline, + color: highlight_style.color, + underline: highlight_style.underline, }, )); prev_font_id = font_id; - prev_font_properties = style.font_properties; + prev_font_properties = highlight_style.font_properties; } } } @@ -567,12 +572,13 @@ impl Element for EditorElement { } let snapshot = self.snapshot(cx.app); - let line_height = self.style.text.line_height(cx.font_cache); + let style = self.settings.style.clone(); + let line_height = style.text.line_height(cx.font_cache); let gutter_padding; let gutter_width; if snapshot.mode == EditorMode::Full { - gutter_padding = self.style.text.em_width(cx.font_cache); + gutter_padding = style.text.em_width(cx.font_cache); gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; } else { gutter_padding = 0.0; @@ -580,8 +586,8 @@ impl Element for EditorElement { }; let text_width = size.x() - gutter_width; - let text_offset = vec2f(-self.style.text.descent(cx.font_cache), 0.); - let em_width = self.style.text.em_width(cx.font_cache); + let text_offset = vec2f(-style.text.descent(cx.font_cache), 0.); + let em_width = style.text.em_width(cx.font_cache); let overscroll = vec2f(em_width, 0.); let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width; let snapshot = self.update_view(cx.app, |view, cx| { @@ -677,7 +683,7 @@ impl Element for EditorElement { overscroll, text_offset, snapshot, - style: self.style.clone(), + style: self.settings.style.clone(), active_rows, line_layouts, line_number_layouts, @@ -689,7 +695,7 @@ impl Element for EditorElement { let scroll_max = layout.scroll_max(cx.font_cache, cx.text_layout_cache).x(); let scroll_width = layout.scroll_width(cx.text_layout_cache); - let max_glyph_width = self.style.text.em_width(&cx.font_cache); + let max_glyph_width = style.text.em_width(&cx.font_cache); self.update_view(cx.app, |view, cx| { let clamped = view.clamp_scroll_left(scroll_max); let autoscrolled; @@ -1035,30 +1041,27 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 { mod tests { use super::*; use crate::{ - editor::{Buffer, Editor, EditorStyle}, - settings, test::sample_text, + {Editor, EditorSettings}, }; + use buffer::Buffer; #[gpui::test] fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) { - let font_cache = cx.font_cache().clone(); - let settings = settings::test(&cx).1; - let style = EditorStyle::test(&font_cache); + let settings = EditorSettings::test(cx); let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); let (window_id, editor) = cx.add_window(Default::default(), |cx| { Editor::for_buffer( buffer, - settings.clone(), { - let style = style.clone(); - move |_| style.clone() + let settings = settings.clone(); + move |_| settings.clone() }, cx, ) }); - let element = EditorElement::new(editor.downgrade(), style); + let element = EditorElement::new(editor.downgrade(), settings); let layouts = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); diff --git a/crates/zed/src/editor.rs b/crates/editor/src/lib.rs similarity index 94% rename from crates/zed/src/editor.rs rename to crates/editor/src/lib.rs index e5c5d5bd99..056af77d28 100644 --- a/crates/zed/src/editor.rs +++ b/crates/editor/src/lib.rs @@ -2,8 +2,11 @@ pub mod display_map; mod element; pub mod movement; -use crate::{project::ProjectPath, settings::Settings, theme::Theme, workspace}; -use anyhow::Result; +#[cfg(test)] +mod test; + +// use crate::{project::ProjectPath, settings::Settings, theme::Theme, workspace}; + use buffer::*; use clock::ReplicaId; pub use display_map::DisplayPoint; @@ -12,9 +15,8 @@ pub use element::*; use gpui::{ action, color::Color, fonts::TextStyle, geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, - MutableAppContext, RenderContext, Task, View, ViewContext, WeakViewHandle, + MutableAppContext, RenderContext, View, ViewContext, WeakViewHandle, }; -use postage::watch; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use smol::Timer; @@ -23,14 +25,12 @@ use std::{ cmp::{self, Ordering}, mem, ops::{Range, RangeInclusive}, - path::Path, rc::Rc, sync::Arc, time::Duration, }; use sum_tree::Bias; use util::post_inc; -use worktree::Worktree; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const MAX_LINE_LEN: usize = 1024; @@ -279,6 +279,12 @@ pub enum EditorMode { Full, } +#[derive(Clone)] +pub struct EditorSettings { + pub tab_size: usize, + pub style: EditorStyle, +} + #[derive(Clone, Deserialize)] pub struct EditorStyle { pub text: TextStyle, @@ -291,6 +297,7 @@ pub struct EditorStyle { pub line_number: Color, pub line_number_active: Color, pub guest_selections: Vec, + pub syntax: Arc, } #[derive(Clone, Copy, Default, Deserialize)] @@ -311,8 +318,7 @@ pub struct Editor { scroll_position: Vector2F, scroll_top_anchor: Anchor, autoscroll_requested: bool, - build_style: Rc EditorStyle>>, - settings: watch::Receiver, + build_settings: Rc EditorSettings>>, focused: bool, show_local_cursors: bool, blink_epoch: usize, @@ -325,7 +331,6 @@ pub struct Snapshot { pub mode: EditorMode, pub display_snapshot: DisplayMapSnapshot, pub placeholder_text: Option>, - pub theme: Arc, is_focused: bool, scroll_position: Vector2F, scroll_top_anchor: Anchor, @@ -344,50 +349,53 @@ struct ClipboardSelection { impl Editor { pub fn single_line( - settings: watch::Receiver, - build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle, + build_settings: impl 'static + Fn(&AppContext) -> EditorSettings, cx: &mut ViewContext, ) -> Self { let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); - let mut view = Self::for_buffer(buffer, settings, build_style, cx); + let mut view = Self::for_buffer(buffer, build_settings, cx); view.mode = EditorMode::SingleLine; view } pub fn auto_height( max_lines: usize, - settings: watch::Receiver, - build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle, + build_settings: impl 'static + Fn(&AppContext) -> EditorSettings, cx: &mut ViewContext, ) -> Self { let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); - let mut view = Self::for_buffer(buffer, settings, build_style, cx); + let mut view = Self::for_buffer(buffer, build_settings, cx); view.mode = EditorMode::AutoHeight { max_lines }; view } pub fn for_buffer( buffer: ModelHandle, - settings: watch::Receiver, - build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle, + build_settings: impl 'static + Fn(&AppContext) -> EditorSettings, cx: &mut ViewContext, ) -> Self { - Self::new(buffer, settings, Rc::new(RefCell::new(build_style)), cx) + Self::new(buffer, Rc::new(RefCell::new(build_settings)), cx) } - fn new( + pub fn clone(&self, cx: &mut ViewContext) -> Self { + let mut clone = Self::new(self.buffer.clone(), self.build_settings.clone(), cx); + clone.scroll_position = self.scroll_position; + clone.scroll_top_anchor = self.scroll_top_anchor.clone(); + clone + } + + pub fn new( buffer: ModelHandle, - settings: watch::Receiver, - build_style: Rc EditorStyle>>, + build_settings: Rc EditorSettings>>, cx: &mut ViewContext, ) -> Self { - let style = build_style.borrow_mut()(cx); + let settings = build_settings.borrow_mut()(cx); let display_map = cx.add_model(|cx| { DisplayMap::new( buffer.clone(), - settings.borrow().tab_size, - style.text.font_id, - style.text.font_size, + settings.tab_size, + settings.style.text.font_id, + settings.style.text.font_size, None, cx, ) @@ -419,11 +427,10 @@ impl Editor { next_selection_id, add_selections_state: None, select_larger_syntax_node_stack: Vec::new(), - build_style, + build_settings, scroll_position: Vector2F::zero(), scroll_top_anchor: Anchor::min(), autoscroll_requested: false, - settings, focused: false, show_local_cursors: false, blink_epoch: 0, @@ -442,14 +449,11 @@ impl Editor { } pub fn snapshot(&mut self, cx: &mut MutableAppContext) -> Snapshot { - let settings = self.settings.borrow(); - Snapshot { mode: self.mode, display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)), scroll_position: self.scroll_position, scroll_top_anchor: self.scroll_top_anchor.clone(), - theme: settings.theme.clone(), placeholder_text: self.placeholder_text.clone(), is_focused: self .handle @@ -719,7 +723,11 @@ impl Editor { } #[cfg(test)] - fn select_display_ranges<'a, T>(&mut self, ranges: T, cx: &mut ViewContext) -> Result<()> + fn select_display_ranges<'a, T>( + &mut self, + ranges: T, + cx: &mut ViewContext, + ) -> anyhow::Result<()> where T: IntoIterator>, { @@ -2293,9 +2301,9 @@ impl Editor { .text() } - pub fn font_size(&self) -> f32 { - self.settings.borrow().buffer_font_size - } + // pub fn font_size(&self) -> f32 { + // self.settings.font_size + // } pub fn set_wrap_width(&self, width: f32, cx: &mut MutableAppContext) -> bool { self.display_map @@ -2409,10 +2417,6 @@ impl Snapshot { .highlighted_chunks_for_rows(display_rows) } - pub fn theme(&self) -> &Arc { - &self.theme - } - pub fn scroll_position(&self) -> Vector2F { compute_scroll_position( &self.display_snapshot, @@ -2473,6 +2477,7 @@ impl EditorStyle { line_number_active: Default::default(), selection: Default::default(), guest_selections: Default::default(), + syntax: Default::default(), } } @@ -2481,6 +2486,16 @@ impl EditorStyle { } } +impl EditorSettings { + #[cfg(any(test, feature = "test-support"))] + pub fn test(cx: &AppContext) -> Self { + Self { + tab_size: 4, + style: EditorStyle::test(cx.font_cache()), + } + } +} + fn compute_scroll_position( snapshot: &DisplayMapSnapshot, mut scroll_position: Vector2F, @@ -2517,11 +2532,15 @@ impl Entity for Editor { impl View for Editor { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - let style = self.build_style.borrow_mut()(cx); + let settings = self.build_settings.borrow_mut()(cx); self.display_map.update(cx, |map, cx| { - map.set_font(style.text.font_id, style.text.font_size, cx) + map.set_font( + settings.style.text.font_id, + settings.style.text.font_size, + cx, + ) }); - EditorElement::new(self.handle.clone(), style).boxed() + EditorElement::new(self.handle.clone(), settings).boxed() } fn ui_name() -> &'static str { @@ -2560,156 +2579,6 @@ impl View for Editor { } } -impl workspace::Item for Buffer { - type View = Editor; - - fn build_view( - handle: ModelHandle, - settings: watch::Receiver, - cx: &mut ViewContext, - ) -> Self::View { - Editor::for_buffer( - handle, - settings.clone(), - move |cx| { - let settings = settings.borrow(); - let font_cache = cx.font_cache(); - let font_family_id = settings.buffer_font_family; - let font_family_name = cx.font_cache().family_name(font_family_id).unwrap(); - let font_properties = Default::default(); - let font_id = font_cache - .select_font(font_family_id, &font_properties) - .unwrap(); - let font_size = settings.buffer_font_size; - - let mut theme = settings.theme.editor.clone(); - theme.text = TextStyle { - color: theme.text.color, - font_family_name, - font_family_id, - font_id, - font_size, - font_properties, - underline: false, - }; - theme - }, - cx, - ) - } - - fn project_path(&self) -> Option { - self.file().map(|f| ProjectPath { - worktree_id: f.worktree_id(), - path: f.path().clone(), - }) - } -} - -impl workspace::ItemView for Editor { - fn should_activate_item_on_event(event: &Self::Event) -> bool { - matches!(event, Event::Activate) - } - - fn should_close_item_on_event(event: &Self::Event) -> bool { - matches!(event, Event::Closed) - } - - fn should_update_tab_on_event(event: &Self::Event) -> bool { - matches!( - event, - Event::Saved | Event::Dirtied | Event::FileHandleChanged - ) - } - - fn title(&self, cx: &AppContext) -> std::string::String { - let filename = self - .buffer - .read(cx) - .file() - .and_then(|file| file.file_name(cx)); - if let Some(name) = filename { - name.to_string_lossy().into() - } else { - "untitled".into() - } - } - - fn project_path(&self, cx: &AppContext) -> Option { - self.buffer.read(cx).file().map(|file| ProjectPath { - worktree_id: file.worktree_id(), - path: file.path().clone(), - }) - } - - fn clone_on_split(&self, cx: &mut ViewContext) -> Option - where - Self: Sized, - { - let mut clone = Editor::new( - self.buffer.clone(), - self.settings.clone(), - self.build_style.clone(), - cx, - ); - clone.scroll_position = self.scroll_position; - clone.scroll_top_anchor = self.scroll_top_anchor.clone(); - Some(clone) - } - - fn save(&mut self, cx: &mut ViewContext) -> Result>> { - let save = self.buffer.update(cx, |b, cx| b.save(cx))?; - Ok(cx.spawn(|_, _| async move { - save.await?; - Ok(()) - })) - } - - fn save_as( - &mut self, - worktree: ModelHandle, - path: &Path, - cx: &mut ViewContext, - ) -> Task> { - self.buffer.update(cx, |buffer, cx| { - let handle = cx.handle(); - let text = buffer.as_rope().clone(); - let version = buffer.version(); - - let save_as = worktree.update(cx, |worktree, cx| { - worktree - .as_local_mut() - .unwrap() - .save_buffer_as(handle, path, text, cx) - }); - - cx.spawn(|buffer, mut cx| async move { - save_as.await.map(|new_file| { - let language = worktree.read_with(&cx, |worktree, cx| { - worktree - .languages() - .select_language(new_file.full_path(cx)) - .cloned() - }); - - buffer.update(&mut cx, |buffer, cx| { - buffer.did_save(version, new_file.mtime, Some(Box::new(new_file)), cx); - buffer.set_language(language, cx); - }); - }) - }) - }) - } - - fn is_dirty(&self, cx: &AppContext) -> bool { - self.buffer.read(cx).is_dirty() - } - - fn has_conflict(&self, cx: &AppContext) -> bool { - self.buffer.read(cx).has_conflict() - } -} - impl SelectionExt for Selection { fn display_range(&self, map: &DisplayMapSnapshot) -> Range { let start = self.start.to_display_point(map, Bias::Left); @@ -2749,18 +2618,14 @@ impl SelectionExt for Selection { #[cfg(test)] mod tests { use super::*; - use crate::{ - editor::Point, - settings, - test::{self, sample_text}, - }; - use buffer::History; + use crate::test::sample_text; + use buffer::{History, Point}; use unindent::Unindent; #[gpui::test] fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(cx); let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); @@ -2827,7 +2692,7 @@ mod tests { #[gpui::test] fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { @@ -2859,7 +2724,7 @@ mod tests { #[gpui::test] fn test_cancel(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { @@ -2922,7 +2787,7 @@ mod tests { cx, ) }); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| { build_editor(buffer.clone(), settings, cx) }); @@ -2990,7 +2855,7 @@ mod tests { #[gpui::test] fn test_move_cursor(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| { build_editor(buffer.clone(), settings, cx) }); @@ -3067,7 +2932,7 @@ mod tests { #[gpui::test] fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| { build_editor(buffer.clone(), settings, cx) }); @@ -3125,7 +2990,7 @@ mod tests { #[gpui::test] fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| { build_editor(buffer.clone(), settings, cx) }); @@ -3156,7 +3021,7 @@ mod tests { #[gpui::test] fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "abc\n def", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( @@ -3299,7 +3164,7 @@ mod tests { fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_display_ranges( @@ -3439,11 +3304,11 @@ mod tests { fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "use one::{\n two::three::four::five\n};", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { - view.set_wrap_width(130., cx); + view.set_wrap_width(140., cx); assert_eq!( view.display_text(cx), "use one::{\n two::three::\n four::five\n};" @@ -3493,7 +3358,7 @@ mod tests { #[gpui::test] fn test_delete_to_word_boundary(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "one two three four", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| { build_editor(buffer.clone(), settings, cx) }); @@ -3540,7 +3405,7 @@ mod tests { cx, ) }); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| { build_editor(buffer.clone(), settings, cx) }); @@ -3576,7 +3441,7 @@ mod tests { cx, ) }); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| { build_editor(buffer.clone(), settings, cx) }); @@ -3605,7 +3470,7 @@ mod tests { #[gpui::test] fn test_delete_line(cx: &mut gpui::MutableAppContext) { - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { @@ -3629,7 +3494,7 @@ mod tests { ); }); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { @@ -3646,7 +3511,7 @@ mod tests { #[gpui::test] fn test_duplicate_line(cx: &mut gpui::MutableAppContext) { - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { @@ -3673,7 +3538,7 @@ mod tests { ); }); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { @@ -3699,7 +3564,7 @@ mod tests { #[gpui::test] fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) { - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(10, 5), cx)); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { @@ -3797,7 +3662,7 @@ mod tests { #[gpui::test] fn test_clipboard(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "one✅ two three four five six ", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let view = cx .add_window(Default::default(), |cx| { build_editor(buffer.clone(), settings, cx) @@ -3932,7 +3797,7 @@ mod tests { #[gpui::test] fn test_select_all(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "abc\nde\nfgh", cx)); - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { view.select_all(&SelectAll, cx); @@ -3945,7 +3810,7 @@ mod tests { #[gpui::test] fn test_select_line(cx: &mut gpui::MutableAppContext) { - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 5), cx)); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { @@ -3991,7 +3856,7 @@ mod tests { #[gpui::test] fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) { - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(9, 5), cx)); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); view.update(cx, |view, cx| { @@ -4059,7 +3924,7 @@ mod tests { #[gpui::test] fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) { - let settings = settings::test(&cx).1; + let settings = EditorSettings::test(&cx); let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndefghi\n\njk\nlmno\n", cx)); let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx)); @@ -4232,9 +4097,17 @@ mod tests { #[gpui::test] async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) { - let app_state = cx.update(test::test_app_state); + let settings = cx.read(EditorSettings::test); + + let grammar = tree_sitter_rust::language(); + let language = Arc::new(Language { + config: LanguageConfig::default(), + brackets_query: tree_sitter::Query::new(grammar, "").unwrap(), + highlight_query: tree_sitter::Query::new(grammar, "").unwrap(), + highlight_map: Default::default(), + grammar, + }); - let lang = app_state.languages.select_language("z.rs"); let text = r#" use mod1::mod2::{mod3, mod4}; @@ -4245,9 +4118,9 @@ mod tests { .unindent(); let buffer = cx.add_model(|cx| { let history = History::new(text.into()); - Buffer::from_history(0, history, None, lang.cloned(), cx) + Buffer::from_history(0, history, None, Some(language), cx) }); - let (_, view) = cx.add_window(|cx| build_editor(buffer, app_state.settings.clone(), cx)); + let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx)); view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing()) .await; @@ -4388,36 +4261,10 @@ mod tests { fn build_editor( buffer: ModelHandle, - settings: watch::Receiver, + settings: EditorSettings, cx: &mut ViewContext, ) -> Editor { - let style = { - let font_cache = cx.font_cache(); - let settings = settings.borrow(); - EditorStyle { - text: TextStyle { - color: Default::default(), - font_family_name: font_cache.family_name(settings.buffer_font_family).unwrap(), - font_family_id: settings.buffer_font_family, - font_id: font_cache - .select_font(settings.buffer_font_family, &Default::default()) - .unwrap(), - font_size: settings.buffer_font_size, - font_properties: Default::default(), - underline: false, - }, - placeholder_text: None, - background: Default::default(), - selection: Default::default(), - gutter_background: Default::default(), - active_line_background: Default::default(), - line_number: Default::default(), - line_number_active: Default::default(), - guest_selections: Default::default(), - } - }; - - Editor::for_buffer(buffer, settings, move |_| style.clone(), cx) + Editor::for_buffer(buffer, move |_| settings.clone(), cx) } } diff --git a/crates/zed/src/editor/movement.rs b/crates/editor/src/movement.rs similarity index 99% rename from crates/zed/src/editor/movement.rs rename to crates/editor/src/movement.rs index d86aa9ca53..7ad1374f8e 100644 --- a/crates/zed/src/editor/movement.rs +++ b/crates/editor/src/movement.rs @@ -196,7 +196,7 @@ fn char_kind(c: char) -> CharKind { #[cfg(test)] mod tests { use super::*; - use crate::editor::{display_map::DisplayMap, Buffer}; + use crate::{display_map::DisplayMap, Buffer}; #[gpui::test] fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) { diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs new file mode 100644 index 0000000000..97e7f5a08c --- /dev/null +++ b/crates/editor/src/test.rs @@ -0,0 +1,39 @@ +use gpui::{Entity, ModelHandle}; +use smol::channel; +use std::marker::PhantomData; + +pub fn sample_text(rows: usize, cols: usize) -> String { + let mut text = String::new(); + for row in 0..rows { + let c: char = ('a' as u32 + row as u32) as u8 as char; + let mut line = c.to_string().repeat(cols); + if row < rows - 1 { + line.push('\n'); + } + text += &line; + } + text +} + +pub struct Observer(PhantomData); + +impl Entity for Observer { + type Event = (); +} + +impl Observer { + pub fn new( + handle: &ModelHandle, + cx: &mut gpui::TestAppContext, + ) -> (ModelHandle, channel::Receiver<()>) { + let (notify_tx, notify_rx) = channel::unbounded(); + let observer = cx.add_model(|cx| { + cx.observe(handle, move |_, _, _| { + let _ = notify_tx.try_send(()); + }) + .detach(); + Observer(PhantomData) + }); + (observer, notify_rx) + } +} diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs index b6b3ffc236..82c5e01c44 100644 --- a/crates/server/src/main.rs +++ b/crates/server/src/main.rs @@ -13,6 +13,7 @@ mod rpc; mod team; use self::errors::TideResultExt as _; +use ::rpc::Peer; use anyhow::Result; use async_std::net::TcpListener; use async_trait::async_trait; @@ -26,7 +27,6 @@ use std::sync::Arc; use surf::http::cookies::SameSite; use tide::{log, sessions::SessionMiddleware}; use tide_compress::CompressMiddleware; -use rpc::Peer; type Request = tide::Request>; diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 3012d17db6..e4a6f6d268 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -10,6 +10,10 @@ use async_std::{sync::RwLock, task}; use async_tungstenite::{tungstenite::protocol::Role, WebSocketStream}; use futures::{future::BoxFuture, FutureExt}; use postage::{mpsc, prelude::Sink as _, prelude::Stream as _}; +use rpc::{ + proto::{self, AnyTypedEnvelope, EnvelopedMessage}, + Connection, ConnectionId, Peer, TypedEnvelope, +}; use sha1::{Digest as _, Sha1}; use std::{ any::TypeId, @@ -27,10 +31,6 @@ use tide::{ Request, Response, }; use time::OffsetDateTime; -use rpc::{ - proto::{self, AnyTypedEnvelope, EnvelopedMessage}, - Connection, ConnectionId, Peer, TypedEnvelope, -}; type MessageHandler = Box< dyn Send @@ -960,6 +960,7 @@ mod tests { db::{tests::TestDb, UserId}, github, AppState, Config, }; + use ::rpc::Peer; use async_std::{sync::RwLockReadGuard, task}; use gpui::{ModelHandle, TestAppContext}; use parking_lot::Mutex; @@ -977,23 +978,20 @@ mod tests { use zed::{ buffer::LanguageRegistry, channel::{Channel, ChannelDetails, ChannelList}, - editor::{Editor, EditorStyle, Insert}, + editor::{Editor, EditorSettings, Insert}, fs::{FakeFs, Fs as _}, people_panel::JoinWorktree, project::ProjectPath, rpc::{self, Client, Credentials, EstablishConnectionError}, - settings, test::FakeHttpClient, user::UserStore, workspace::Workspace, worktree::Worktree, }; - use rpc::Peer; #[gpui::test] async fn test_share_worktree(mut cx_a: TestAppContext, mut cx_b: TestAppContext) { let (window_b, _) = cx_b.add_window(|_| EmptyView); - let settings = cx_b.read(settings::test).1; let lang_registry = Arc::new(LanguageRegistry::new()); // Connect to a server as 2 clients. @@ -1063,12 +1061,7 @@ mod tests { // Create a selection set as client B and see that selection set as client A. let editor_b = cx_b.add_view(window_b, |cx| { - Editor::for_buffer( - buffer_b, - settings, - |cx| EditorStyle::test(cx.font_cache()), - cx, - ) + Editor::for_buffer(buffer_b, |cx| EditorSettings::test(cx), cx) }); buffer_a .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 1) diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index def8af297f..bd4c4d55c6 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -33,6 +33,7 @@ clock = { path = "../clock" } crossbeam-channel = "0.5.0" ctor = "0.1.20" dirs = "3.0" +editor = { path = "../editor" } easy-parallel = "3.1.0" fsevent = { path = "../fsevent" } futures = "0.3" @@ -70,7 +71,7 @@ tree-sitter = "0.19.5" tree-sitter-rust = "0.19.0" url = "2.2" util = { path = "../util" } -worktree = { path = "../worktree" } +worktree = { path = "../worktree" } rpc = { path = "../rpc" } [dev-dependencies] @@ -80,6 +81,7 @@ serde_json = { version = "1.0.64", features = ["preserve_order"] } tempdir = { version = "0.3.7" } unindent = "0.1.7" buffer = { path = "../buffer", features = ["test-support"] } +editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } rpc_client = { path = "../rpc_client", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index e03b3eb134..d74780281c 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -208,7 +208,7 @@ padding = { left = 16, right = 16, top = 8, bottom = 4 } [selector.item] text = "$text.1" -highlight_text = { extends = "$text.base", color = "$syntax.keyword.color", weight = "$syntax.keyword.weight" } +highlight_text = { extends = "$text.base", color = "$editor.syntax.keyword.color", weight = "$editor.syntax.keyword.weight" } padding = { left = 16, right = 16, top = 4, bottom = 4 } corner_radius = 6 diff --git a/crates/zed/assets/themes/black.toml b/crates/zed/assets/themes/black.toml index 3a7319e2a9..d37b7905be 100644 --- a/crates/zed/assets/themes/black.toml +++ b/crates/zed/assets/themes/black.toml @@ -26,7 +26,7 @@ guests = [ { selection = "#3B874B33", cursor = "#3B874B" }, { selection = "#BD7CB433", cursor = "#BD7CB4" }, { selection = "#EE823133", cursor = "#EE8231" }, - { selection = "#5A2B9233", cursor = "#5A2B92" } + { selection = "#5A2B9233", cursor = "#5A2B92" }, ] [status] @@ -39,7 +39,7 @@ bad = "#b7372e" active_line = "#00000033" hover = "#00000033" -[syntax] +[editor.syntax] keyword = { color = "#0086c0", weight = "bold" } function = "#dcdcaa" string = "#cb8f77" diff --git a/crates/zed/assets/themes/dark.toml b/crates/zed/assets/themes/dark.toml index f9c5a97f2a..694e346911 100644 --- a/crates/zed/assets/themes/dark.toml +++ b/crates/zed/assets/themes/dark.toml @@ -26,7 +26,7 @@ guests = [ { selection = "#3B874B33", cursor = "#3B874B" }, { selection = "#BD7CB433", cursor = "#BD7CB4" }, { selection = "#EE823133", cursor = "#EE8231" }, - { selection = "#5A2B9233", cursor = "#5A2B92" } + { selection = "#5A2B9233", cursor = "#5A2B92" }, ] [status] @@ -39,7 +39,7 @@ bad = "#b7372e" active_line = "#00000022" hover = "#00000033" -[syntax] +[editor.syntax] keyword = { color = "#0086c0", weight = "bold" } function = "#dcdcaa" string = "#cb8f77" diff --git a/crates/zed/assets/themes/light.toml b/crates/zed/assets/themes/light.toml index fe3262b12c..d19b5ad4c9 100644 --- a/crates/zed/assets/themes/light.toml +++ b/crates/zed/assets/themes/light.toml @@ -26,7 +26,7 @@ guests = [ { selection = "#3B874B33", cursor = "#3B874B" }, { selection = "#BD7CB433", cursor = "#BD7CB4" }, { selection = "#EE823133", cursor = "#EE8231" }, - { selection = "#5A2B9233", cursor = "#5A2B92" } + { selection = "#5A2B9233", cursor = "#5A2B92" }, ] [status] @@ -39,7 +39,7 @@ bad = "#b7372e" active_line = "#00000008" hover = "#0000000D" -[syntax] +[editor.syntax] keyword = { color = "#0000fa", weight = "bold" } function = "#795e26" string = "#a82121" diff --git a/crates/zed/src/chat_panel.rs b/crates/zed/src/chat_panel.rs index ebfb6151c1..dc1e5f0eec 100644 --- a/crates/zed/src/chat_panel.rs +++ b/crates/zed/src/chat_panel.rs @@ -1,10 +1,8 @@ -use std::sync::Arc; - use crate::{ channel::{Channel, ChannelEvent, ChannelList, ChannelMessage}, - editor::Editor, theme, Settings, }; +use editor::{Editor, EditorSettings}; use gpui::{ action, elements::*, @@ -16,6 +14,7 @@ use gpui::{ }; use postage::{prelude::Stream, watch}; use rpc_client as rpc; +use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; use util::{ResultExt, TryFutureExt}; @@ -55,10 +54,15 @@ impl ChatPanel { let input_editor = cx.add_view(|cx| { Editor::auto_height( 4, - settings.clone(), { let settings = settings.clone(); - move |_| settings.borrow().theme.chat_panel.input_editor.as_editor() + move |_| { + let settings = settings.borrow(); + EditorSettings { + tab_size: settings.tab_size, + style: settings.theme.chat_panel.input_editor.as_editor(), + } + } }, cx, ) diff --git a/crates/zed/src/file_finder.rs b/crates/zed/src/file_finder.rs index 44569c54a7..5eca8a1827 100644 --- a/crates/zed/src/file_finder.rs +++ b/crates/zed/src/file_finder.rs @@ -1,10 +1,10 @@ use crate::{ - editor::{self, Editor}, fuzzy::PathMatch, project::{Project, ProjectPath}, settings::Settings, workspace::Workspace, }; +use editor::{self, Editor, EditorSettings}; use gpui::{ action, elements::*, @@ -271,10 +271,15 @@ impl FileFinder { let query_editor = cx.add_view(|cx| { Editor::single_line( - settings.clone(), { let settings = settings.clone(); - move |_| settings.borrow().theme.selector.input_editor.as_editor() + move |_| { + let settings = settings.borrow(); + EditorSettings { + style: settings.theme.selector.input_editor.as_editor(), + tab_size: settings.tab_size, + } + } }, cx, ) @@ -420,11 +425,8 @@ impl FileFinder { #[cfg(test)] mod tests { use super::*; - use crate::{ - editor::{self, Insert}, - test::test_app_state, - workspace::Workspace, - }; + use crate::{test::test_app_state, workspace::Workspace}; + use editor::{self, Insert}; use serde_json::json; use std::path::PathBuf; use worktree::fs::FakeFs; diff --git a/crates/zed/src/lib.rs b/crates/zed/src/lib.rs index 8536d53271..e6584d34d8 100644 --- a/crates/zed/src/lib.rs +++ b/crates/zed/src/lib.rs @@ -1,7 +1,6 @@ pub mod assets; pub mod channel; pub mod chat_panel; -pub mod editor; pub mod file_finder; mod fuzzy; pub mod http; @@ -21,6 +20,7 @@ pub mod workspace; pub use buffer; use buffer::LanguageRegistry; use channel::ChannelList; +pub use editor; use gpui::{action, keymap::Binding, ModelHandle}; use parking_lot::Mutex; use postage::watch; diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c7370626c1..6be0427bac 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -33,7 +33,7 @@ fn main() { let themes = settings::ThemeRegistry::new(Assets, app.font_cache()); let (settings_tx, settings) = settings::channel(&app.font_cache(), &themes).unwrap(); let languages = Arc::new(language::build_language_registry()); - languages.set_theme(&settings.borrow().theme.syntax); + languages.set_theme(&settings.borrow().theme.editor.syntax); app.run(move |cx| { let rpc = rpc::Client::new(); diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index e885d31209..82dc24af1e 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -4,8 +4,6 @@ use std::sync::Arc; #[cfg(target_os = "macos")] pub fn menus(state: &Arc) -> Vec> { - use crate::editor; - vec![ Menu { name: "Zed", diff --git a/crates/zed/src/test.rs b/crates/zed/src/test.rs index 7051f29ef0..ab5edd6354 100644 --- a/crates/zed/src/test.rs +++ b/crates/zed/src/test.rs @@ -10,11 +10,10 @@ use crate::{ use anyhow::Result; use buffer::LanguageRegistry; use futures::{future::BoxFuture, Future}; -use gpui::{Entity, ModelHandle, MutableAppContext}; +use gpui::MutableAppContext; use parking_lot::Mutex; use rpc_client as rpc; -use smol::channel; -use std::{fmt, marker::PhantomData, sync::Arc}; +use std::{fmt, sync::Arc}; use worktree::fs::FakeFs; #[cfg(test)] @@ -23,19 +22,6 @@ fn init_logger() { env_logger::init(); } -pub fn sample_text(rows: usize, cols: usize) -> String { - let mut text = String::new(); - for row in 0..rows { - let c: char = ('a' as u32 + row as u32) as u8 as char; - let mut line = c.to_string().repeat(cols); - if row < rows - 1 { - line.push('\n'); - } - text += &line; - } - text -} - pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { let (settings_tx, settings) = settings::test(cx); let mut languages = LanguageRegistry::new(); @@ -56,29 +42,6 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { }) } -pub struct Observer(PhantomData); - -impl Entity for Observer { - type Event = (); -} - -impl Observer { - pub fn new( - handle: &ModelHandle, - cx: &mut gpui::TestAppContext, - ) -> (ModelHandle, channel::Receiver<()>) { - let (notify_tx, notify_rx) = channel::unbounded(); - let observer = cx.add_model(|cx| { - cx.observe(handle, move |_, _, _| { - let _ = notify_tx.try_send(()); - }) - .detach(); - Observer(PhantomData) - }); - (observer, notify_rx) - } -} - pub struct FakeHttpClient { handler: Box BoxFuture<'static, Result>>, diff --git a/crates/zed/src/theme.rs b/crates/zed/src/theme.rs index f2a98c1677..9f93ba4608 100644 --- a/crates/zed/src/theme.rs +++ b/crates/zed/src/theme.rs @@ -1,8 +1,7 @@ mod resolution; mod theme_registry; -use crate::editor::{EditorStyle, SelectionStyle}; -use buffer::SyntaxTheme; +use editor::{EditorStyle, SelectionStyle}; use gpui::{ color::Color, elements::{ContainerStyle, ImageStyle, LabelStyle}, @@ -25,7 +24,6 @@ pub struct Theme { pub project_panel: ProjectPanel, pub selector: Selector, pub editor: EditorStyle, - pub syntax: SyntaxTheme, } #[derive(Deserialize)] @@ -228,6 +226,7 @@ impl InputEditorStyle { line_number: Default::default(), line_number_active: Default::default(), guest_selections: Default::default(), + syntax: Default::default(), } } } diff --git a/crates/zed/src/theme_selector.rs b/crates/zed/src/theme_selector.rs index 4bb657d619..9f7edb02ff 100644 --- a/crates/zed/src/theme_selector.rs +++ b/crates/zed/src/theme_selector.rs @@ -1,12 +1,12 @@ use std::{cmp, sync::Arc}; use crate::{ - editor::{self, Editor}, fuzzy::{match_strings, StringMatch, StringMatchCandidate}, settings::ThemeRegistry, workspace::Workspace, AppState, Settings, }; +use editor::{self, Editor, EditorSettings}; use gpui::{ action, elements::*, @@ -59,10 +59,15 @@ impl ThemeSelector { ) -> Self { let query_editor = cx.add_view(|cx| { Editor::single_line( - settings.clone(), { let settings = settings.clone(); - move |_| settings.borrow().theme.selector.input_editor.as_editor() + move |_| { + let settings = settings.borrow(); + EditorSettings { + tab_size: settings.tab_size, + style: settings.theme.selector.input_editor.as_editor(), + } + } }, cx, ) diff --git a/crates/zed/src/workspace.rs b/crates/zed/src/workspace.rs index c2aaa75963..fc91edab3a 100644 --- a/crates/zed/src/workspace.rs +++ b/crates/zed/src/workspace.rs @@ -1,3 +1,4 @@ +mod items; pub mod pane; pub mod pane_group; pub mod sidebar; @@ -1156,11 +1157,8 @@ impl WorkspaceHandle for ViewHandle { #[cfg(test)] mod tests { use super::*; - use crate::{ - editor::{Editor, Insert}, - fs::FakeFs, - test::test_app_state, - }; + use crate::{fs::FakeFs, test::test_app_state}; + use editor::{Editor, Insert}; use serde_json::json; use std::collections::HashSet; use util::test::temp_tree; diff --git a/crates/zed/src/workspace/items.rs b/crates/zed/src/workspace/items.rs new file mode 100644 index 0000000000..c002fddd41 --- /dev/null +++ b/crates/zed/src/workspace/items.rs @@ -0,0 +1,153 @@ +use super::{Item, ItemView}; +use crate::{project::ProjectPath, Settings}; +use anyhow::Result; +use buffer::{Buffer, File as _}; +use editor::{Editor, EditorSettings, Event}; +use gpui::{fonts::TextStyle, AppContext, ModelHandle, Task, ViewContext}; +use postage::watch; +use std::path::Path; +use worktree::Worktree; + +impl Item for Buffer { + type View = Editor; + + fn build_view( + handle: ModelHandle, + settings: watch::Receiver, + cx: &mut ViewContext, + ) -> Self::View { + Editor::for_buffer( + handle, + move |cx| { + let settings = settings.borrow(); + let font_cache = cx.font_cache(); + let font_family_id = settings.buffer_font_family; + let font_family_name = cx.font_cache().family_name(font_family_id).unwrap(); + let font_properties = Default::default(); + let font_id = font_cache + .select_font(font_family_id, &font_properties) + .unwrap(); + let font_size = settings.buffer_font_size; + + let mut theme = settings.theme.editor.clone(); + theme.text = TextStyle { + color: theme.text.color, + font_family_name, + font_family_id, + font_id, + font_size, + font_properties, + underline: false, + }; + EditorSettings { + tab_size: settings.tab_size, + style: theme, + } + }, + cx, + ) + } + + fn project_path(&self) -> Option { + self.file().map(|f| ProjectPath { + worktree_id: f.worktree_id(), + path: f.path().clone(), + }) + } +} + +impl ItemView for Editor { + fn should_activate_item_on_event(event: &Event) -> bool { + matches!(event, Event::Activate) + } + + fn should_close_item_on_event(event: &Event) -> bool { + matches!(event, Event::Closed) + } + + fn should_update_tab_on_event(event: &Event) -> bool { + matches!( + event, + Event::Saved | Event::Dirtied | Event::FileHandleChanged + ) + } + + fn title(&self, cx: &AppContext) -> String { + let filename = self + .buffer() + .read(cx) + .file() + .and_then(|file| file.file_name(cx)); + if let Some(name) = filename { + name.to_string_lossy().into() + } else { + "untitled".into() + } + } + + fn project_path(&self, cx: &AppContext) -> Option { + self.buffer().read(cx).file().map(|file| ProjectPath { + worktree_id: file.worktree_id(), + path: file.path().clone(), + }) + } + + fn clone_on_split(&self, cx: &mut ViewContext) -> Option + where + Self: Sized, + { + Some(self.clone(cx)) + } + + fn save(&mut self, cx: &mut ViewContext) -> Result>> { + let save = self.buffer().update(cx, |b, cx| b.save(cx))?; + Ok(cx.spawn(|_, _| async move { + save.await?; + Ok(()) + })) + } + + fn save_as( + &mut self, + worktree: ModelHandle, + path: &Path, + cx: &mut ViewContext, + ) -> Task> { + self.buffer().update(cx, |buffer, cx| { + let handle = cx.handle(); + let text = buffer.as_rope().clone(); + let version = buffer.version(); + + let save_as = worktree.update(cx, |worktree, cx| { + worktree + .as_local_mut() + .unwrap() + .save_buffer_as(handle, path, text, cx) + }); + + cx.spawn(|buffer, mut cx| async move { + save_as.await.map(|new_file| { + let language = worktree.read_with(&cx, |worktree, cx| { + worktree + .languages() + .select_language(new_file.full_path(cx)) + .cloned() + }); + + buffer.update(&mut cx, |buffer, cx| { + buffer.did_save(version, new_file.mtime, Some(Box::new(new_file)), cx); + buffer.set_language(language, cx); + }); + }) + }) + }) + } + + fn is_dirty(&self, cx: &AppContext) -> bool { + self.buffer().read(cx).is_dirty() + } + + fn has_conflict(&self, cx: &AppContext) -> bool { + self.buffer().read(cx).has_conflict() + } +}