Move editor into its own crate

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2021-10-04 15:06:12 -07:00
parent d5b60ad124
commit 1d97f08901
28 changed files with 456 additions and 400 deletions

24
Cargo.lock generated
View File

@ -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",

View File

@ -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)>,
}

29
crates/editor/Cargo.toml Normal file
View File

@ -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"] }

View File

@ -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};

View File

@ -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};

View File

@ -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;

View File

@ -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<Editor>,
style: EditorStyle,
settings: EditorSettings,
}
impl EditorElement {
pub fn new(view: WeakViewHandle<Editor>, style: EditorStyle) -> Self {
Self { view, style }
pub fn new(view: WeakViewHandle<Editor>, 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<Option<text_layout::Line>> {
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);

View File

@ -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<SelectionStyle>,
pub syntax: Arc<SyntaxTheme>,
}
#[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<RefCell<dyn FnMut(&mut MutableAppContext) -> EditorStyle>>,
settings: watch::Receiver<Settings>,
build_settings: Rc<RefCell<dyn Fn(&AppContext) -> 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<Arc<str>>,
pub theme: Arc<Theme>,
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<Settings>,
build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle,
build_settings: impl 'static + Fn(&AppContext) -> EditorSettings,
cx: &mut ViewContext<Self>,
) -> 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<Settings>,
build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle,
build_settings: impl 'static + Fn(&AppContext) -> EditorSettings,
cx: &mut ViewContext<Self>,
) -> 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<Buffer>,
settings: watch::Receiver<Settings>,
build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle,
build_settings: impl 'static + Fn(&AppContext) -> EditorSettings,
cx: &mut ViewContext<Self>,
) -> 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>) -> 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<Buffer>,
settings: watch::Receiver<Settings>,
build_style: Rc<RefCell<dyn FnMut(&mut MutableAppContext) -> EditorStyle>>,
build_settings: Rc<RefCell<dyn Fn(&AppContext) -> EditorSettings>>,
cx: &mut ViewContext<Self>,
) -> 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<Self>) -> Result<()>
fn select_display_ranges<'a, T>(
&mut self,
ranges: T,
cx: &mut ViewContext<Self>,
) -> anyhow::Result<()>
where
T: IntoIterator<Item = &'a Range<DisplayPoint>>,
{
@ -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<Theme> {
&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<Self>) -> 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<Self>,
settings: watch::Receiver<Settings>,
cx: &mut ViewContext<Self::View>,
) -> 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<ProjectPath> {
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<ProjectPath> {
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<Self>) -> Option<Self>
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<Self>) -> Result<Task<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<Worktree>,
path: &Path,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
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<DisplayPoint> {
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<Buffer>,
settings: watch::Receiver<Settings>,
settings: EditorSettings,
cx: &mut ViewContext<Editor>,
) -> 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)
}
}

View File

@ -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) {

39
crates/editor/src/test.rs Normal file
View File

@ -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<T>(PhantomData<T>);
impl<T: 'static> Entity for Observer<T> {
type Event = ();
}
impl<T: Entity> Observer<T> {
pub fn new(
handle: &ModelHandle<T>,
cx: &mut gpui::TestAppContext,
) -> (ModelHandle<Self>, 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)
}
}

View File

@ -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<Arc<AppState>>;

View File

@ -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)

View File

@ -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"] }

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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,
)

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -4,8 +4,6 @@ use std::sync::Arc;
#[cfg(target_os = "macos")]
pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
use crate::editor;
vec![
Menu {
name: "Zed",

View File

@ -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<AppState> {
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<AppState> {
})
}
pub struct Observer<T>(PhantomData<T>);
impl<T: 'static> Entity for Observer<T> {
type Event = ();
}
impl<T: Entity> Observer<T> {
pub fn new(
handle: &ModelHandle<T>,
cx: &mut gpui::TestAppContext,
) -> (ModelHandle<Self>, 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<dyn 'static + Send + Sync + Fn(Request) -> BoxFuture<'static, Result<ServerResponse>>>,

View File

@ -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(),
}
}
}

View File

@ -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,
)

View File

@ -1,3 +1,4 @@
mod items;
pub mod pane;
pub mod pane_group;
pub mod sidebar;
@ -1156,11 +1157,8 @@ impl WorkspaceHandle for ViewHandle<Workspace> {
#[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;

View File

@ -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<Self>,
settings: watch::Receiver<Settings>,
cx: &mut ViewContext<Self::View>,
) -> 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<ProjectPath> {
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<ProjectPath> {
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<Self>) -> Option<Self>
where
Self: Sized,
{
Some(self.clone(cx))
}
fn save(&mut self, cx: &mut ViewContext<Self>) -> Result<Task<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<Worktree>,
path: &Path,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
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()
}
}