Move editor into its own crate
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
d5b60ad124
commit
1d97f08901
28 changed files with 456 additions and 400 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -1575,6 +1575,29 @@ dependencies = [
|
||||||
"getrandom 0.2.2",
|
"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]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
@ -6030,6 +6053,7 @@ dependencies = [
|
||||||
"ctor",
|
"ctor",
|
||||||
"dirs 3.0.1",
|
"dirs 3.0.1",
|
||||||
"easy-parallel",
|
"easy-parallel",
|
||||||
|
"editor",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"fsevent",
|
"fsevent",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::HighlightId;
|
||||||
use gpui::fonts::HighlightStyle;
|
use gpui::fonts::HighlightStyle;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct SyntaxTheme {
|
pub struct SyntaxTheme {
|
||||||
pub(crate) highlights: Vec<(String, HighlightStyle)>,
|
pub(crate) highlights: Vec<(String, HighlightStyle)>,
|
||||||
}
|
}
|
||||||
|
|
29
crates/editor/Cargo.toml
Normal file
29
crates/editor/Cargo.toml
Normal 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"] }
|
|
@ -357,7 +357,7 @@ impl ToDisplayPoint for Anchor {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{editor::movement, test::*};
|
use crate::{movement, test::*};
|
||||||
use buffer::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal, SyntaxTheme};
|
use buffer::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal, SyntaxTheme};
|
||||||
use gpui::{color::Color, MutableAppContext};
|
use gpui::{color::Color, MutableAppContext};
|
||||||
use rand::{prelude::StdRng, Rng};
|
use rand::{prelude::StdRng, Rng};
|
|
@ -1128,7 +1128,7 @@ impl FoldEdit {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{editor::ToPoint, test::sample_text};
|
use crate::{test::sample_text, ToPoint};
|
||||||
use buffer::RandomCharIter;
|
use buffer::RandomCharIter;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::{env, mem};
|
use std::{env, mem};
|
|
@ -2,8 +2,7 @@ use super::{
|
||||||
fold_map,
|
fold_map,
|
||||||
tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary},
|
tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary},
|
||||||
};
|
};
|
||||||
use crate::editor::Point;
|
use buffer::{HighlightId, Point};
|
||||||
use buffer::HighlightId;
|
|
||||||
use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task};
|
use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use smol::future::yield_now;
|
use smol::future::yield_now;
|
||||||
|
@ -897,13 +896,10 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::{
|
display_map::{fold_map::FoldMap, tab_map::TabMap},
|
||||||
display_map::{fold_map::FoldMap, tab_map::TabMap},
|
|
||||||
Buffer,
|
|
||||||
},
|
|
||||||
test::Observer,
|
test::Observer,
|
||||||
};
|
};
|
||||||
use buffer::RandomCharIter;
|
use buffer::{Buffer, RandomCharIter};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{
|
use super::{
|
||||||
DisplayPoint, Editor, EditorMode, EditorStyle, Insert, Scroll, Select, SelectPhase, Snapshot,
|
DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Insert, Scroll, Select,
|
||||||
MAX_LINE_LEN,
|
SelectPhase, Snapshot, MAX_LINE_LEN,
|
||||||
};
|
};
|
||||||
use buffer::HighlightId;
|
use buffer::HighlightId;
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
|
@ -28,12 +28,12 @@ use std::{
|
||||||
|
|
||||||
pub struct EditorElement {
|
pub struct EditorElement {
|
||||||
view: WeakViewHandle<Editor>,
|
view: WeakViewHandle<Editor>,
|
||||||
style: EditorStyle,
|
settings: EditorSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorElement {
|
impl EditorElement {
|
||||||
pub fn new(view: WeakViewHandle<Editor>, style: EditorStyle) -> Self {
|
pub fn new(view: WeakViewHandle<Editor>, settings: EditorSettings) -> Self {
|
||||||
Self { view, style }
|
Self { view, settings }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
|
fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
|
||||||
|
@ -196,15 +196,16 @@ impl EditorElement {
|
||||||
let bounds = gutter_bounds.union_rect(text_bounds);
|
let bounds = gutter_bounds.union_rect(text_bounds);
|
||||||
let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
|
let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
|
||||||
let editor = self.view(cx.app);
|
let editor = self.view(cx.app);
|
||||||
|
let style = &self.settings.style;
|
||||||
cx.scene.push_quad(Quad {
|
cx.scene.push_quad(Quad {
|
||||||
bounds: gutter_bounds,
|
bounds: gutter_bounds,
|
||||||
background: Some(self.style.gutter_background),
|
background: Some(style.gutter_background),
|
||||||
border: Border::new(0., Color::transparent_black()),
|
border: Border::new(0., Color::transparent_black()),
|
||||||
corner_radius: 0.,
|
corner_radius: 0.,
|
||||||
});
|
});
|
||||||
cx.scene.push_quad(Quad {
|
cx.scene.push_quad(Quad {
|
||||||
bounds: text_bounds,
|
bounds: text_bounds,
|
||||||
background: Some(self.style.background),
|
background: Some(style.background),
|
||||||
border: Border::new(0., Color::transparent_black()),
|
border: Border::new(0., Color::transparent_black()),
|
||||||
corner_radius: 0.,
|
corner_radius: 0.,
|
||||||
});
|
});
|
||||||
|
@ -231,7 +232,7 @@ impl EditorElement {
|
||||||
);
|
);
|
||||||
cx.scene.push_quad(Quad {
|
cx.scene.push_quad(Quad {
|
||||||
bounds: RectF::new(origin, size),
|
bounds: RectF::new(origin, size),
|
||||||
background: Some(self.style.active_line_background),
|
background: Some(style.active_line_background),
|
||||||
border: Border::default(),
|
border: Border::default(),
|
||||||
corner_radius: 0.,
|
corner_radius: 0.,
|
||||||
});
|
});
|
||||||
|
@ -268,8 +269,7 @@ impl EditorElement {
|
||||||
cx: &mut PaintContext,
|
cx: &mut PaintContext,
|
||||||
) {
|
) {
|
||||||
let view = self.view(cx.app);
|
let view = self.view(cx.app);
|
||||||
let settings = self.view(cx.app).settings.borrow();
|
let style = &self.settings.style;
|
||||||
let theme = &settings.theme.editor;
|
|
||||||
let local_replica_id = view.replica_id(cx);
|
let local_replica_id = view.replica_id(cx);
|
||||||
let scroll_position = layout.snapshot.scroll_position();
|
let scroll_position = layout.snapshot.scroll_position();
|
||||||
let start_row = scroll_position.y() as u32;
|
let start_row = scroll_position.y() as u32;
|
||||||
|
@ -287,11 +287,11 @@ impl EditorElement {
|
||||||
let content_origin = bounds.origin() + layout.text_offset;
|
let content_origin = bounds.origin() + layout.text_offset;
|
||||||
|
|
||||||
for (replica_id, selections) in &layout.selections {
|
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 {
|
let style = if style_ix == 0 {
|
||||||
&theme.selection
|
&style.selection
|
||||||
} else {
|
} else {
|
||||||
&theme.guest_selections[style_ix - 1]
|
&style.guest_selections[style_ix - 1]
|
||||||
};
|
};
|
||||||
|
|
||||||
for selection in selections {
|
for selection in selections {
|
||||||
|
@ -383,15 +383,16 @@ impl EditorElement {
|
||||||
|
|
||||||
fn max_line_number_width(&self, snapshot: &Snapshot, cx: &LayoutContext) -> f32 {
|
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 digit_count = (snapshot.buffer_row_count() as f32).log10().floor() as usize + 1;
|
||||||
|
let style = &self.settings.style;
|
||||||
|
|
||||||
cx.text_layout_cache
|
cx.text_layout_cache
|
||||||
.layout_str(
|
.layout_str(
|
||||||
"1".repeat(digit_count).as_str(),
|
"1".repeat(digit_count).as_str(),
|
||||||
self.style.text.font_size,
|
style.text.font_size,
|
||||||
&[(
|
&[(
|
||||||
digit_count,
|
digit_count,
|
||||||
RunStyle {
|
RunStyle {
|
||||||
font_id: self.style.text.font_id,
|
font_id: style.text.font_id,
|
||||||
color: Color::black(),
|
color: Color::black(),
|
||||||
underline: false,
|
underline: false,
|
||||||
},
|
},
|
||||||
|
@ -407,6 +408,7 @@ impl EditorElement {
|
||||||
snapshot: &Snapshot,
|
snapshot: &Snapshot,
|
||||||
cx: &LayoutContext,
|
cx: &LayoutContext,
|
||||||
) -> Vec<Option<text_layout::Line>> {
|
) -> Vec<Option<text_layout::Line>> {
|
||||||
|
let style = &self.settings.style;
|
||||||
let mut layouts = Vec::with_capacity(rows.len());
|
let mut layouts = Vec::with_capacity(rows.len());
|
||||||
let mut line_number = String::new();
|
let mut line_number = String::new();
|
||||||
for (ix, (buffer_row, soft_wrapped)) in snapshot
|
for (ix, (buffer_row, soft_wrapped)) in snapshot
|
||||||
|
@ -416,9 +418,9 @@ impl EditorElement {
|
||||||
{
|
{
|
||||||
let display_row = rows.start + ix as u32;
|
let display_row = rows.start + ix as u32;
|
||||||
let color = if active_rows.contains_key(&display_row) {
|
let color = if active_rows.contains_key(&display_row) {
|
||||||
self.style.line_number_active
|
style.line_number_active
|
||||||
} else {
|
} else {
|
||||||
self.style.line_number
|
style.line_number
|
||||||
};
|
};
|
||||||
if soft_wrapped {
|
if soft_wrapped {
|
||||||
layouts.push(None);
|
layouts.push(None);
|
||||||
|
@ -427,11 +429,11 @@ impl EditorElement {
|
||||||
write!(&mut line_number, "{}", buffer_row + 1).unwrap();
|
write!(&mut line_number, "{}", buffer_row + 1).unwrap();
|
||||||
layouts.push(Some(cx.text_layout_cache.layout_str(
|
layouts.push(Some(cx.text_layout_cache.layout_str(
|
||||||
&line_number,
|
&line_number,
|
||||||
self.style.text.font_size,
|
style.text.font_size,
|
||||||
&[(
|
&[(
|
||||||
line_number.len(),
|
line_number.len(),
|
||||||
RunStyle {
|
RunStyle {
|
||||||
font_id: self.style.text.font_id,
|
font_id: style.text.font_id,
|
||||||
color,
|
color,
|
||||||
underline: false,
|
underline: false,
|
||||||
},
|
},
|
||||||
|
@ -456,7 +458,7 @@ impl EditorElement {
|
||||||
|
|
||||||
// When the editor is empty and unfocused, then show the placeholder.
|
// When the editor is empty and unfocused, then show the placeholder.
|
||||||
if snapshot.is_empty() && !snapshot.is_focused() {
|
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_text = snapshot.placeholder_text();
|
||||||
let placeholder_lines = placeholder_text
|
let placeholder_lines = placeholder_text
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -482,10 +484,10 @@ impl EditorElement {
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut prev_font_properties = self.style.text.font_properties.clone();
|
let style = &self.settings.style;
|
||||||
let mut prev_font_id = self.style.text.font_id;
|
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 layouts = Vec::with_capacity(rows.len());
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
let mut styles = Vec::new();
|
let mut styles = Vec::new();
|
||||||
|
@ -498,7 +500,7 @@ impl EditorElement {
|
||||||
if ix > 0 {
|
if ix > 0 {
|
||||||
layouts.push(cx.text_layout_cache.layout_str(
|
layouts.push(cx.text_layout_cache.layout_str(
|
||||||
&line,
|
&line,
|
||||||
self.style.text.font_size,
|
style.text.font_size,
|
||||||
&styles,
|
&styles,
|
||||||
));
|
));
|
||||||
line.clear();
|
line.clear();
|
||||||
|
@ -511,17 +513,20 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !line_chunk.is_empty() && !line_exceeded_max_len {
|
if !line_chunk.is_empty() && !line_exceeded_max_len {
|
||||||
let style = theme
|
let highlight_style = style
|
||||||
.syntax
|
.syntax
|
||||||
.highlight_style(style_ix)
|
.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.
|
// 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
|
prev_font_id
|
||||||
} else {
|
} else {
|
||||||
cx.font_cache
|
cx.font_cache
|
||||||
.select_font(self.style.text.font_family_id, &style.font_properties)
|
.select_font(
|
||||||
.unwrap_or(self.style.text.font_id)
|
style.text.font_family_id,
|
||||||
|
&highlight_style.font_properties,
|
||||||
|
)
|
||||||
|
.unwrap_or(style.text.font_id)
|
||||||
};
|
};
|
||||||
|
|
||||||
if line.len() + line_chunk.len() > MAX_LINE_LEN {
|
if line.len() + line_chunk.len() > MAX_LINE_LEN {
|
||||||
|
@ -538,12 +543,12 @@ impl EditorElement {
|
||||||
line_chunk.len(),
|
line_chunk.len(),
|
||||||
RunStyle {
|
RunStyle {
|
||||||
font_id,
|
font_id,
|
||||||
color: style.color,
|
color: highlight_style.color,
|
||||||
underline: style.underline,
|
underline: highlight_style.underline,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
prev_font_id = font_id;
|
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 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_padding;
|
||||||
let gutter_width;
|
let gutter_width;
|
||||||
if snapshot.mode == EditorMode::Full {
|
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;
|
gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
|
||||||
} else {
|
} else {
|
||||||
gutter_padding = 0.0;
|
gutter_padding = 0.0;
|
||||||
|
@ -580,8 +586,8 @@ impl Element for EditorElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
let text_width = size.x() - gutter_width;
|
let text_width = size.x() - gutter_width;
|
||||||
let text_offset = vec2f(-self.style.text.descent(cx.font_cache), 0.);
|
let text_offset = vec2f(-style.text.descent(cx.font_cache), 0.);
|
||||||
let em_width = self.style.text.em_width(cx.font_cache);
|
let em_width = style.text.em_width(cx.font_cache);
|
||||||
let overscroll = vec2f(em_width, 0.);
|
let overscroll = vec2f(em_width, 0.);
|
||||||
let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width;
|
let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width;
|
||||||
let snapshot = self.update_view(cx.app, |view, cx| {
|
let snapshot = self.update_view(cx.app, |view, cx| {
|
||||||
|
@ -677,7 +683,7 @@ impl Element for EditorElement {
|
||||||
overscroll,
|
overscroll,
|
||||||
text_offset,
|
text_offset,
|
||||||
snapshot,
|
snapshot,
|
||||||
style: self.style.clone(),
|
style: self.settings.style.clone(),
|
||||||
active_rows,
|
active_rows,
|
||||||
line_layouts,
|
line_layouts,
|
||||||
line_number_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_max = layout.scroll_max(cx.font_cache, cx.text_layout_cache).x();
|
||||||
let scroll_width = layout.scroll_width(cx.text_layout_cache);
|
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| {
|
self.update_view(cx.app, |view, cx| {
|
||||||
let clamped = view.clamp_scroll_left(scroll_max);
|
let clamped = view.clamp_scroll_left(scroll_max);
|
||||||
let autoscrolled;
|
let autoscrolled;
|
||||||
|
@ -1035,30 +1041,27 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::{Buffer, Editor, EditorStyle},
|
|
||||||
settings,
|
|
||||||
test::sample_text,
|
test::sample_text,
|
||||||
|
{Editor, EditorSettings},
|
||||||
};
|
};
|
||||||
|
use buffer::Buffer;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
|
fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
|
||||||
let font_cache = cx.font_cache().clone();
|
let settings = EditorSettings::test(cx);
|
||||||
let settings = settings::test(&cx).1;
|
|
||||||
let style = EditorStyle::test(&font_cache);
|
|
||||||
|
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), 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| {
|
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||||
Editor::for_buffer(
|
Editor::for_buffer(
|
||||||
buffer,
|
buffer,
|
||||||
settings.clone(),
|
|
||||||
{
|
{
|
||||||
let style = style.clone();
|
let settings = settings.clone();
|
||||||
move |_| style.clone()
|
move |_| settings.clone()
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let element = EditorElement::new(editor.downgrade(), style);
|
let element = EditorElement::new(editor.downgrade(), settings);
|
||||||
|
|
||||||
let layouts = editor.update(cx, |editor, cx| {
|
let layouts = editor.update(cx, |editor, cx| {
|
||||||
let snapshot = editor.snapshot(cx);
|
let snapshot = editor.snapshot(cx);
|
|
@ -2,8 +2,11 @@ pub mod display_map;
|
||||||
mod element;
|
mod element;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
|
|
||||||
use crate::{project::ProjectPath, settings::Settings, theme::Theme, workspace};
|
#[cfg(test)]
|
||||||
use anyhow::Result;
|
mod test;
|
||||||
|
|
||||||
|
// use crate::{project::ProjectPath, settings::Settings, theme::Theme, workspace};
|
||||||
|
|
||||||
use buffer::*;
|
use buffer::*;
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
pub use display_map::DisplayPoint;
|
pub use display_map::DisplayPoint;
|
||||||
|
@ -12,9 +15,8 @@ pub use element::*;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action, color::Color, fonts::TextStyle, geometry::vector::Vector2F, keymap::Binding,
|
action, color::Color, fonts::TextStyle, geometry::vector::Vector2F, keymap::Binding,
|
||||||
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
|
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 serde::{Deserialize, Serialize};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
|
@ -23,14 +25,12 @@ use std::{
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
mem,
|
mem,
|
||||||
ops::{Range, RangeInclusive},
|
ops::{Range, RangeInclusive},
|
||||||
path::Path,
|
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use sum_tree::Bias;
|
use sum_tree::Bias;
|
||||||
use util::post_inc;
|
use util::post_inc;
|
||||||
use worktree::Worktree;
|
|
||||||
|
|
||||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||||
const MAX_LINE_LEN: usize = 1024;
|
const MAX_LINE_LEN: usize = 1024;
|
||||||
|
@ -279,6 +279,12 @@ pub enum EditorMode {
|
||||||
Full,
|
Full,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EditorSettings {
|
||||||
|
pub tab_size: usize,
|
||||||
|
pub style: EditorStyle,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
#[derive(Clone, Deserialize)]
|
||||||
pub struct EditorStyle {
|
pub struct EditorStyle {
|
||||||
pub text: TextStyle,
|
pub text: TextStyle,
|
||||||
|
@ -291,6 +297,7 @@ pub struct EditorStyle {
|
||||||
pub line_number: Color,
|
pub line_number: Color,
|
||||||
pub line_number_active: Color,
|
pub line_number_active: Color,
|
||||||
pub guest_selections: Vec<SelectionStyle>,
|
pub guest_selections: Vec<SelectionStyle>,
|
||||||
|
pub syntax: Arc<SyntaxTheme>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Deserialize)]
|
#[derive(Clone, Copy, Default, Deserialize)]
|
||||||
|
@ -311,8 +318,7 @@ pub struct Editor {
|
||||||
scroll_position: Vector2F,
|
scroll_position: Vector2F,
|
||||||
scroll_top_anchor: Anchor,
|
scroll_top_anchor: Anchor,
|
||||||
autoscroll_requested: bool,
|
autoscroll_requested: bool,
|
||||||
build_style: Rc<RefCell<dyn FnMut(&mut MutableAppContext) -> EditorStyle>>,
|
build_settings: Rc<RefCell<dyn Fn(&AppContext) -> EditorSettings>>,
|
||||||
settings: watch::Receiver<Settings>,
|
|
||||||
focused: bool,
|
focused: bool,
|
||||||
show_local_cursors: bool,
|
show_local_cursors: bool,
|
||||||
blink_epoch: usize,
|
blink_epoch: usize,
|
||||||
|
@ -325,7 +331,6 @@ pub struct Snapshot {
|
||||||
pub mode: EditorMode,
|
pub mode: EditorMode,
|
||||||
pub display_snapshot: DisplayMapSnapshot,
|
pub display_snapshot: DisplayMapSnapshot,
|
||||||
pub placeholder_text: Option<Arc<str>>,
|
pub placeholder_text: Option<Arc<str>>,
|
||||||
pub theme: Arc<Theme>,
|
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
scroll_position: Vector2F,
|
scroll_position: Vector2F,
|
||||||
scroll_top_anchor: Anchor,
|
scroll_top_anchor: Anchor,
|
||||||
|
@ -344,50 +349,53 @@ struct ClipboardSelection {
|
||||||
|
|
||||||
impl Editor {
|
impl Editor {
|
||||||
pub fn single_line(
|
pub fn single_line(
|
||||||
settings: watch::Receiver<Settings>,
|
build_settings: impl 'static + Fn(&AppContext) -> EditorSettings,
|
||||||
build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle,
|
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
|
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.mode = EditorMode::SingleLine;
|
||||||
view
|
view
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auto_height(
|
pub fn auto_height(
|
||||||
max_lines: usize,
|
max_lines: usize,
|
||||||
settings: watch::Receiver<Settings>,
|
build_settings: impl 'static + Fn(&AppContext) -> EditorSettings,
|
||||||
build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle,
|
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
|
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.mode = EditorMode::AutoHeight { max_lines };
|
||||||
view
|
view
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_buffer(
|
pub fn for_buffer(
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
settings: watch::Receiver<Settings>,
|
build_settings: impl 'static + Fn(&AppContext) -> EditorSettings,
|
||||||
build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle,
|
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> 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>,
|
buffer: ModelHandle<Buffer>,
|
||||||
settings: watch::Receiver<Settings>,
|
build_settings: Rc<RefCell<dyn Fn(&AppContext) -> EditorSettings>>,
|
||||||
build_style: Rc<RefCell<dyn FnMut(&mut MutableAppContext) -> EditorStyle>>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let style = build_style.borrow_mut()(cx);
|
let settings = build_settings.borrow_mut()(cx);
|
||||||
let display_map = cx.add_model(|cx| {
|
let display_map = cx.add_model(|cx| {
|
||||||
DisplayMap::new(
|
DisplayMap::new(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
settings.borrow().tab_size,
|
settings.tab_size,
|
||||||
style.text.font_id,
|
settings.style.text.font_id,
|
||||||
style.text.font_size,
|
settings.style.text.font_size,
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -419,11 +427,10 @@ impl Editor {
|
||||||
next_selection_id,
|
next_selection_id,
|
||||||
add_selections_state: None,
|
add_selections_state: None,
|
||||||
select_larger_syntax_node_stack: Vec::new(),
|
select_larger_syntax_node_stack: Vec::new(),
|
||||||
build_style,
|
build_settings,
|
||||||
scroll_position: Vector2F::zero(),
|
scroll_position: Vector2F::zero(),
|
||||||
scroll_top_anchor: Anchor::min(),
|
scroll_top_anchor: Anchor::min(),
|
||||||
autoscroll_requested: false,
|
autoscroll_requested: false,
|
||||||
settings,
|
|
||||||
focused: false,
|
focused: false,
|
||||||
show_local_cursors: false,
|
show_local_cursors: false,
|
||||||
blink_epoch: 0,
|
blink_epoch: 0,
|
||||||
|
@ -442,14 +449,11 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot(&mut self, cx: &mut MutableAppContext) -> Snapshot {
|
pub fn snapshot(&mut self, cx: &mut MutableAppContext) -> Snapshot {
|
||||||
let settings = self.settings.borrow();
|
|
||||||
|
|
||||||
Snapshot {
|
Snapshot {
|
||||||
mode: self.mode,
|
mode: self.mode,
|
||||||
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
||||||
scroll_position: self.scroll_position,
|
scroll_position: self.scroll_position,
|
||||||
scroll_top_anchor: self.scroll_top_anchor.clone(),
|
scroll_top_anchor: self.scroll_top_anchor.clone(),
|
||||||
theme: settings.theme.clone(),
|
|
||||||
placeholder_text: self.placeholder_text.clone(),
|
placeholder_text: self.placeholder_text.clone(),
|
||||||
is_focused: self
|
is_focused: self
|
||||||
.handle
|
.handle
|
||||||
|
@ -719,7 +723,11 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[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
|
where
|
||||||
T: IntoIterator<Item = &'a Range<DisplayPoint>>,
|
T: IntoIterator<Item = &'a Range<DisplayPoint>>,
|
||||||
{
|
{
|
||||||
|
@ -2293,9 +2301,9 @@ impl Editor {
|
||||||
.text()
|
.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font_size(&self) -> f32 {
|
// pub fn font_size(&self) -> f32 {
|
||||||
self.settings.borrow().buffer_font_size
|
// self.settings.font_size
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn set_wrap_width(&self, width: f32, cx: &mut MutableAppContext) -> bool {
|
pub fn set_wrap_width(&self, width: f32, cx: &mut MutableAppContext) -> bool {
|
||||||
self.display_map
|
self.display_map
|
||||||
|
@ -2409,10 +2417,6 @@ impl Snapshot {
|
||||||
.highlighted_chunks_for_rows(display_rows)
|
.highlighted_chunks_for_rows(display_rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn theme(&self) -> &Arc<Theme> {
|
|
||||||
&self.theme
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scroll_position(&self) -> Vector2F {
|
pub fn scroll_position(&self) -> Vector2F {
|
||||||
compute_scroll_position(
|
compute_scroll_position(
|
||||||
&self.display_snapshot,
|
&self.display_snapshot,
|
||||||
|
@ -2473,6 +2477,7 @@ impl EditorStyle {
|
||||||
line_number_active: Default::default(),
|
line_number_active: Default::default(),
|
||||||
selection: Default::default(),
|
selection: Default::default(),
|
||||||
guest_selections: 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(
|
fn compute_scroll_position(
|
||||||
snapshot: &DisplayMapSnapshot,
|
snapshot: &DisplayMapSnapshot,
|
||||||
mut scroll_position: Vector2F,
|
mut scroll_position: Vector2F,
|
||||||
|
@ -2517,11 +2532,15 @@ impl Entity for Editor {
|
||||||
|
|
||||||
impl View for Editor {
|
impl View for Editor {
|
||||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
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| {
|
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 {
|
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 {
|
impl SelectionExt for Selection {
|
||||||
fn display_range(&self, map: &DisplayMapSnapshot) -> Range<DisplayPoint> {
|
fn display_range(&self, map: &DisplayMapSnapshot) -> Range<DisplayPoint> {
|
||||||
let start = self.start.to_display_point(map, Bias::Left);
|
let start = self.start.to_display_point(map, Bias::Left);
|
||||||
|
@ -2749,18 +2618,14 @@ impl SelectionExt for Selection {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::test::sample_text;
|
||||||
editor::Point,
|
use buffer::{History, Point};
|
||||||
settings,
|
|
||||||
test::{self, sample_text},
|
|
||||||
};
|
|
||||||
use buffer::History;
|
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
|
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 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) =
|
let (_, editor) =
|
||||||
cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
|
|
||||||
|
@ -2827,7 +2692,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) {
|
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 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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
|
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
|
@ -2859,7 +2724,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_cancel(cx: &mut gpui::MutableAppContext) {
|
fn test_cancel(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx));
|
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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
|
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
|
@ -2922,7 +2787,7 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let settings = settings::test(&cx).1;
|
let settings = EditorSettings::test(&cx);
|
||||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||||
build_editor(buffer.clone(), settings, cx)
|
build_editor(buffer.clone(), settings, cx)
|
||||||
});
|
});
|
||||||
|
@ -2990,7 +2855,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
|
fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
|
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| {
|
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||||
build_editor(buffer.clone(), settings, cx)
|
build_editor(buffer.clone(), settings, cx)
|
||||||
});
|
});
|
||||||
|
@ -3067,7 +2932,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
|
fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx));
|
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| {
|
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||||
build_editor(buffer.clone(), settings, cx)
|
build_editor(buffer.clone(), settings, cx)
|
||||||
});
|
});
|
||||||
|
@ -3125,7 +2990,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) {
|
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 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| {
|
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||||
build_editor(buffer.clone(), settings, cx)
|
build_editor(buffer.clone(), settings, cx)
|
||||||
});
|
});
|
||||||
|
@ -3156,7 +3021,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) {
|
fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\n def", cx));
|
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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
view.select_display_ranges(
|
view.select_display_ranges(
|
||||||
|
@ -3299,7 +3164,7 @@ mod tests {
|
||||||
fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) {
|
fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer =
|
let buffer =
|
||||||
cx.add_model(|cx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", cx));
|
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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
view.select_display_ranges(
|
view.select_display_ranges(
|
||||||
|
@ -3439,11 +3304,11 @@ mod tests {
|
||||||
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) {
|
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer =
|
let buffer =
|
||||||
cx.add_model(|cx| Buffer::new(0, "use one::{\n two::three::four::five\n};", cx));
|
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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
|
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
view.set_wrap_width(130., cx);
|
view.set_wrap_width(140., cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.display_text(cx),
|
view.display_text(cx),
|
||||||
"use one::{\n two::three::\n four::five\n};"
|
"use one::{\n two::three::\n four::five\n};"
|
||||||
|
@ -3493,7 +3358,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_delete_to_word_boundary(cx: &mut gpui::MutableAppContext) {
|
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 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| {
|
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||||
build_editor(buffer.clone(), settings, cx)
|
build_editor(buffer.clone(), settings, cx)
|
||||||
});
|
});
|
||||||
|
@ -3540,7 +3405,7 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let settings = settings::test(&cx).1;
|
let settings = EditorSettings::test(&cx);
|
||||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||||
build_editor(buffer.clone(), settings, cx)
|
build_editor(buffer.clone(), settings, cx)
|
||||||
});
|
});
|
||||||
|
@ -3576,7 +3441,7 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let settings = settings::test(&cx).1;
|
let settings = EditorSettings::test(&cx);
|
||||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||||
build_editor(buffer.clone(), settings, cx)
|
build_editor(buffer.clone(), settings, cx)
|
||||||
});
|
});
|
||||||
|
@ -3605,7 +3470,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_delete_line(cx: &mut gpui::MutableAppContext) {
|
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 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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, 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 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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
|
@ -3646,7 +3511,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_duplicate_line(cx: &mut gpui::MutableAppContext) {
|
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 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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, 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 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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
|
@ -3699,7 +3564,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
|
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 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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
|
@ -3797,7 +3662,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_clipboard(cx: &mut gpui::MutableAppContext) {
|
fn test_clipboard(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, "one✅ two three four five six ", cx));
|
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
|
let view = cx
|
||||||
.add_window(Default::default(), |cx| {
|
.add_window(Default::default(), |cx| {
|
||||||
build_editor(buffer.clone(), settings, cx)
|
build_editor(buffer.clone(), settings, cx)
|
||||||
|
@ -3932,7 +3797,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_select_all(cx: &mut gpui::MutableAppContext) {
|
fn test_select_all(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\nde\nfgh", cx));
|
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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
view.select_all(&SelectAll, cx);
|
view.select_all(&SelectAll, cx);
|
||||||
|
@ -3945,7 +3810,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_select_line(cx: &mut gpui::MutableAppContext) {
|
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 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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
|
@ -3991,7 +3856,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
|
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 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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
|
@ -4059,7 +3924,7 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) {
|
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 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));
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||||
|
|
||||||
|
@ -4232,9 +4097,17 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) {
|
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#"
|
let text = r#"
|
||||||
use mod1::mod2::{mod3, mod4};
|
use mod1::mod2::{mod3, mod4};
|
||||||
|
|
||||||
|
@ -4245,9 +4118,9 @@ mod tests {
|
||||||
.unindent();
|
.unindent();
|
||||||
let buffer = cx.add_model(|cx| {
|
let buffer = cx.add_model(|cx| {
|
||||||
let history = History::new(text.into());
|
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())
|
view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -4388,36 +4261,10 @@ mod tests {
|
||||||
|
|
||||||
fn build_editor(
|
fn build_editor(
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
settings: watch::Receiver<Settings>,
|
settings: EditorSettings,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> Editor {
|
) -> Editor {
|
||||||
let style = {
|
Editor::for_buffer(buffer, move |_| settings.clone(), cx)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,7 @@ fn char_kind(c: char) -> CharKind {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::editor::{display_map::DisplayMap, Buffer};
|
use crate::{display_map::DisplayMap, Buffer};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) {
|
fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) {
|
39
crates/editor/src/test.rs
Normal file
39
crates/editor/src/test.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ mod rpc;
|
||||||
mod team;
|
mod team;
|
||||||
|
|
||||||
use self::errors::TideResultExt as _;
|
use self::errors::TideResultExt as _;
|
||||||
|
use ::rpc::Peer;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_std::net::TcpListener;
|
use async_std::net::TcpListener;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -26,7 +27,6 @@ use std::sync::Arc;
|
||||||
use surf::http::cookies::SameSite;
|
use surf::http::cookies::SameSite;
|
||||||
use tide::{log, sessions::SessionMiddleware};
|
use tide::{log, sessions::SessionMiddleware};
|
||||||
use tide_compress::CompressMiddleware;
|
use tide_compress::CompressMiddleware;
|
||||||
use rpc::Peer;
|
|
||||||
|
|
||||||
type Request = tide::Request<Arc<AppState>>;
|
type Request = tide::Request<Arc<AppState>>;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,10 @@ use async_std::{sync::RwLock, task};
|
||||||
use async_tungstenite::{tungstenite::protocol::Role, WebSocketStream};
|
use async_tungstenite::{tungstenite::protocol::Role, WebSocketStream};
|
||||||
use futures::{future::BoxFuture, FutureExt};
|
use futures::{future::BoxFuture, FutureExt};
|
||||||
use postage::{mpsc, prelude::Sink as _, prelude::Stream as _};
|
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 sha1::{Digest as _, Sha1};
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
|
@ -27,10 +31,6 @@ use tide::{
|
||||||
Request, Response,
|
Request, Response,
|
||||||
};
|
};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use rpc::{
|
|
||||||
proto::{self, AnyTypedEnvelope, EnvelopedMessage},
|
|
||||||
Connection, ConnectionId, Peer, TypedEnvelope,
|
|
||||||
};
|
|
||||||
|
|
||||||
type MessageHandler = Box<
|
type MessageHandler = Box<
|
||||||
dyn Send
|
dyn Send
|
||||||
|
@ -960,6 +960,7 @@ mod tests {
|
||||||
db::{tests::TestDb, UserId},
|
db::{tests::TestDb, UserId},
|
||||||
github, AppState, Config,
|
github, AppState, Config,
|
||||||
};
|
};
|
||||||
|
use ::rpc::Peer;
|
||||||
use async_std::{sync::RwLockReadGuard, task};
|
use async_std::{sync::RwLockReadGuard, task};
|
||||||
use gpui::{ModelHandle, TestAppContext};
|
use gpui::{ModelHandle, TestAppContext};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -977,23 +978,20 @@ mod tests {
|
||||||
use zed::{
|
use zed::{
|
||||||
buffer::LanguageRegistry,
|
buffer::LanguageRegistry,
|
||||||
channel::{Channel, ChannelDetails, ChannelList},
|
channel::{Channel, ChannelDetails, ChannelList},
|
||||||
editor::{Editor, EditorStyle, Insert},
|
editor::{Editor, EditorSettings, Insert},
|
||||||
fs::{FakeFs, Fs as _},
|
fs::{FakeFs, Fs as _},
|
||||||
people_panel::JoinWorktree,
|
people_panel::JoinWorktree,
|
||||||
project::ProjectPath,
|
project::ProjectPath,
|
||||||
rpc::{self, Client, Credentials, EstablishConnectionError},
|
rpc::{self, Client, Credentials, EstablishConnectionError},
|
||||||
settings,
|
|
||||||
test::FakeHttpClient,
|
test::FakeHttpClient,
|
||||||
user::UserStore,
|
user::UserStore,
|
||||||
workspace::Workspace,
|
workspace::Workspace,
|
||||||
worktree::Worktree,
|
worktree::Worktree,
|
||||||
};
|
};
|
||||||
use rpc::Peer;
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_share_worktree(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
|
async fn test_share_worktree(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
|
||||||
let (window_b, _) = cx_b.add_window(|_| EmptyView);
|
let (window_b, _) = cx_b.add_window(|_| EmptyView);
|
||||||
let settings = cx_b.read(settings::test).1;
|
|
||||||
let lang_registry = Arc::new(LanguageRegistry::new());
|
let lang_registry = Arc::new(LanguageRegistry::new());
|
||||||
|
|
||||||
// Connect to a server as 2 clients.
|
// 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.
|
// 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| {
|
let editor_b = cx_b.add_view(window_b, |cx| {
|
||||||
Editor::for_buffer(
|
Editor::for_buffer(buffer_b, |cx| EditorSettings::test(cx), cx)
|
||||||
buffer_b,
|
|
||||||
settings,
|
|
||||||
|cx| EditorStyle::test(cx.font_cache()),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
buffer_a
|
buffer_a
|
||||||
.condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 1)
|
.condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 1)
|
||||||
|
|
|
@ -33,6 +33,7 @@ clock = { path = "../clock" }
|
||||||
crossbeam-channel = "0.5.0"
|
crossbeam-channel = "0.5.0"
|
||||||
ctor = "0.1.20"
|
ctor = "0.1.20"
|
||||||
dirs = "3.0"
|
dirs = "3.0"
|
||||||
|
editor = { path = "../editor" }
|
||||||
easy-parallel = "3.1.0"
|
easy-parallel = "3.1.0"
|
||||||
fsevent = { path = "../fsevent" }
|
fsevent = { path = "../fsevent" }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
@ -70,7 +71,7 @@ tree-sitter = "0.19.5"
|
||||||
tree-sitter-rust = "0.19.0"
|
tree-sitter-rust = "0.19.0"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
worktree = { path = "../worktree" }
|
worktree = { path = "../worktree" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { path = "../rpc" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@ -80,6 +81,7 @@ serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
||||||
tempdir = { version = "0.3.7" }
|
tempdir = { version = "0.3.7" }
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
buffer = { path = "../buffer", features = ["test-support"] }
|
buffer = { path = "../buffer", features = ["test-support"] }
|
||||||
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
rpc_client = { path = "../rpc_client", features = ["test-support"] }
|
rpc_client = { path = "../rpc_client", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
|
|
|
@ -208,7 +208,7 @@ padding = { left = 16, right = 16, top = 8, bottom = 4 }
|
||||||
|
|
||||||
[selector.item]
|
[selector.item]
|
||||||
text = "$text.1"
|
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 }
|
padding = { left = 16, right = 16, top = 4, bottom = 4 }
|
||||||
corner_radius = 6
|
corner_radius = 6
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ guests = [
|
||||||
{ selection = "#3B874B33", cursor = "#3B874B" },
|
{ selection = "#3B874B33", cursor = "#3B874B" },
|
||||||
{ selection = "#BD7CB433", cursor = "#BD7CB4" },
|
{ selection = "#BD7CB433", cursor = "#BD7CB4" },
|
||||||
{ selection = "#EE823133", cursor = "#EE8231" },
|
{ selection = "#EE823133", cursor = "#EE8231" },
|
||||||
{ selection = "#5A2B9233", cursor = "#5A2B92" }
|
{ selection = "#5A2B9233", cursor = "#5A2B92" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[status]
|
[status]
|
||||||
|
@ -39,7 +39,7 @@ bad = "#b7372e"
|
||||||
active_line = "#00000033"
|
active_line = "#00000033"
|
||||||
hover = "#00000033"
|
hover = "#00000033"
|
||||||
|
|
||||||
[syntax]
|
[editor.syntax]
|
||||||
keyword = { color = "#0086c0", weight = "bold" }
|
keyword = { color = "#0086c0", weight = "bold" }
|
||||||
function = "#dcdcaa"
|
function = "#dcdcaa"
|
||||||
string = "#cb8f77"
|
string = "#cb8f77"
|
||||||
|
|
|
@ -26,7 +26,7 @@ guests = [
|
||||||
{ selection = "#3B874B33", cursor = "#3B874B" },
|
{ selection = "#3B874B33", cursor = "#3B874B" },
|
||||||
{ selection = "#BD7CB433", cursor = "#BD7CB4" },
|
{ selection = "#BD7CB433", cursor = "#BD7CB4" },
|
||||||
{ selection = "#EE823133", cursor = "#EE8231" },
|
{ selection = "#EE823133", cursor = "#EE8231" },
|
||||||
{ selection = "#5A2B9233", cursor = "#5A2B92" }
|
{ selection = "#5A2B9233", cursor = "#5A2B92" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[status]
|
[status]
|
||||||
|
@ -39,7 +39,7 @@ bad = "#b7372e"
|
||||||
active_line = "#00000022"
|
active_line = "#00000022"
|
||||||
hover = "#00000033"
|
hover = "#00000033"
|
||||||
|
|
||||||
[syntax]
|
[editor.syntax]
|
||||||
keyword = { color = "#0086c0", weight = "bold" }
|
keyword = { color = "#0086c0", weight = "bold" }
|
||||||
function = "#dcdcaa"
|
function = "#dcdcaa"
|
||||||
string = "#cb8f77"
|
string = "#cb8f77"
|
||||||
|
|
|
@ -26,7 +26,7 @@ guests = [
|
||||||
{ selection = "#3B874B33", cursor = "#3B874B" },
|
{ selection = "#3B874B33", cursor = "#3B874B" },
|
||||||
{ selection = "#BD7CB433", cursor = "#BD7CB4" },
|
{ selection = "#BD7CB433", cursor = "#BD7CB4" },
|
||||||
{ selection = "#EE823133", cursor = "#EE8231" },
|
{ selection = "#EE823133", cursor = "#EE8231" },
|
||||||
{ selection = "#5A2B9233", cursor = "#5A2B92" }
|
{ selection = "#5A2B9233", cursor = "#5A2B92" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[status]
|
[status]
|
||||||
|
@ -39,7 +39,7 @@ bad = "#b7372e"
|
||||||
active_line = "#00000008"
|
active_line = "#00000008"
|
||||||
hover = "#0000000D"
|
hover = "#0000000D"
|
||||||
|
|
||||||
[syntax]
|
[editor.syntax]
|
||||||
keyword = { color = "#0000fa", weight = "bold" }
|
keyword = { color = "#0000fa", weight = "bold" }
|
||||||
function = "#795e26"
|
function = "#795e26"
|
||||||
string = "#a82121"
|
string = "#a82121"
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
channel::{Channel, ChannelEvent, ChannelList, ChannelMessage},
|
channel::{Channel, ChannelEvent, ChannelList, ChannelMessage},
|
||||||
editor::Editor,
|
|
||||||
theme, Settings,
|
theme, Settings,
|
||||||
};
|
};
|
||||||
|
use editor::{Editor, EditorSettings};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action,
|
action,
|
||||||
elements::*,
|
elements::*,
|
||||||
|
@ -16,6 +14,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use postage::{prelude::Stream, watch};
|
use postage::{prelude::Stream, watch};
|
||||||
use rpc_client as rpc;
|
use rpc_client as rpc;
|
||||||
|
use std::sync::Arc;
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
|
|
||||||
|
@ -55,10 +54,15 @@ impl ChatPanel {
|
||||||
let input_editor = cx.add_view(|cx| {
|
let input_editor = cx.add_view(|cx| {
|
||||||
Editor::auto_height(
|
Editor::auto_height(
|
||||||
4,
|
4,
|
||||||
settings.clone(),
|
|
||||||
{
|
{
|
||||||
let settings = 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,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::{self, Editor},
|
|
||||||
fuzzy::PathMatch,
|
fuzzy::PathMatch,
|
||||||
project::{Project, ProjectPath},
|
project::{Project, ProjectPath},
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
workspace::Workspace,
|
workspace::Workspace,
|
||||||
};
|
};
|
||||||
|
use editor::{self, Editor, EditorSettings};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action,
|
action,
|
||||||
elements::*,
|
elements::*,
|
||||||
|
@ -271,10 +271,15 @@ impl FileFinder {
|
||||||
|
|
||||||
let query_editor = cx.add_view(|cx| {
|
let query_editor = cx.add_view(|cx| {
|
||||||
Editor::single_line(
|
Editor::single_line(
|
||||||
settings.clone(),
|
|
||||||
{
|
{
|
||||||
let settings = 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,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -420,11 +425,8 @@ impl FileFinder {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{test::test_app_state, workspace::Workspace};
|
||||||
editor::{self, Insert},
|
use editor::{self, Insert};
|
||||||
test::test_app_state,
|
|
||||||
workspace::Workspace,
|
|
||||||
};
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use worktree::fs::FakeFs;
|
use worktree::fs::FakeFs;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
pub mod assets;
|
pub mod assets;
|
||||||
pub mod channel;
|
pub mod channel;
|
||||||
pub mod chat_panel;
|
pub mod chat_panel;
|
||||||
pub mod editor;
|
|
||||||
pub mod file_finder;
|
pub mod file_finder;
|
||||||
mod fuzzy;
|
mod fuzzy;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
|
@ -21,6 +20,7 @@ pub mod workspace;
|
||||||
pub use buffer;
|
pub use buffer;
|
||||||
use buffer::LanguageRegistry;
|
use buffer::LanguageRegistry;
|
||||||
use channel::ChannelList;
|
use channel::ChannelList;
|
||||||
|
pub use editor;
|
||||||
use gpui::{action, keymap::Binding, ModelHandle};
|
use gpui::{action, keymap::Binding, ModelHandle};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
|
|
|
@ -33,7 +33,7 @@ fn main() {
|
||||||
let themes = settings::ThemeRegistry::new(Assets, app.font_cache());
|
let themes = settings::ThemeRegistry::new(Assets, app.font_cache());
|
||||||
let (settings_tx, settings) = settings::channel(&app.font_cache(), &themes).unwrap();
|
let (settings_tx, settings) = settings::channel(&app.font_cache(), &themes).unwrap();
|
||||||
let languages = Arc::new(language::build_language_registry());
|
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| {
|
app.run(move |cx| {
|
||||||
let rpc = rpc::Client::new();
|
let rpc = rpc::Client::new();
|
||||||
|
|
|
@ -4,8 +4,6 @@ use std::sync::Arc;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
|
pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
|
||||||
use crate::editor;
|
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
Menu {
|
Menu {
|
||||||
name: "Zed",
|
name: "Zed",
|
||||||
|
|
|
@ -10,11 +10,10 @@ use crate::{
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use buffer::LanguageRegistry;
|
use buffer::LanguageRegistry;
|
||||||
use futures::{future::BoxFuture, Future};
|
use futures::{future::BoxFuture, Future};
|
||||||
use gpui::{Entity, ModelHandle, MutableAppContext};
|
use gpui::MutableAppContext;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rpc_client as rpc;
|
use rpc_client as rpc;
|
||||||
use smol::channel;
|
use std::{fmt, sync::Arc};
|
||||||
use std::{fmt, marker::PhantomData, sync::Arc};
|
|
||||||
use worktree::fs::FakeFs;
|
use worktree::fs::FakeFs;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -23,19 +22,6 @@ fn init_logger() {
|
||||||
env_logger::init();
|
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> {
|
pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
|
||||||
let (settings_tx, settings) = settings::test(cx);
|
let (settings_tx, settings) = settings::test(cx);
|
||||||
let mut languages = LanguageRegistry::new();
|
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 {
|
pub struct FakeHttpClient {
|
||||||
handler:
|
handler:
|
||||||
Box<dyn 'static + Send + Sync + Fn(Request) -> BoxFuture<'static, Result<ServerResponse>>>,
|
Box<dyn 'static + Send + Sync + Fn(Request) -> BoxFuture<'static, Result<ServerResponse>>>,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
mod resolution;
|
mod resolution;
|
||||||
mod theme_registry;
|
mod theme_registry;
|
||||||
|
|
||||||
use crate::editor::{EditorStyle, SelectionStyle};
|
use editor::{EditorStyle, SelectionStyle};
|
||||||
use buffer::SyntaxTheme;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::Color,
|
color::Color,
|
||||||
elements::{ContainerStyle, ImageStyle, LabelStyle},
|
elements::{ContainerStyle, ImageStyle, LabelStyle},
|
||||||
|
@ -25,7 +24,6 @@ pub struct Theme {
|
||||||
pub project_panel: ProjectPanel,
|
pub project_panel: ProjectPanel,
|
||||||
pub selector: Selector,
|
pub selector: Selector,
|
||||||
pub editor: EditorStyle,
|
pub editor: EditorStyle,
|
||||||
pub syntax: SyntaxTheme,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -228,6 +226,7 @@ impl InputEditorStyle {
|
||||||
line_number: Default::default(),
|
line_number: Default::default(),
|
||||||
line_number_active: Default::default(),
|
line_number_active: Default::default(),
|
||||||
guest_selections: Default::default(),
|
guest_selections: Default::default(),
|
||||||
|
syntax: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::{cmp, sync::Arc};
|
use std::{cmp, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::{self, Editor},
|
|
||||||
fuzzy::{match_strings, StringMatch, StringMatchCandidate},
|
fuzzy::{match_strings, StringMatch, StringMatchCandidate},
|
||||||
settings::ThemeRegistry,
|
settings::ThemeRegistry,
|
||||||
workspace::Workspace,
|
workspace::Workspace,
|
||||||
AppState, Settings,
|
AppState, Settings,
|
||||||
};
|
};
|
||||||
|
use editor::{self, Editor, EditorSettings};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action,
|
action,
|
||||||
elements::*,
|
elements::*,
|
||||||
|
@ -59,10 +59,15 @@ impl ThemeSelector {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let query_editor = cx.add_view(|cx| {
|
let query_editor = cx.add_view(|cx| {
|
||||||
Editor::single_line(
|
Editor::single_line(
|
||||||
settings.clone(),
|
|
||||||
{
|
{
|
||||||
let settings = 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,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod items;
|
||||||
pub mod pane;
|
pub mod pane;
|
||||||
pub mod pane_group;
|
pub mod pane_group;
|
||||||
pub mod sidebar;
|
pub mod sidebar;
|
||||||
|
@ -1156,11 +1157,8 @@ impl WorkspaceHandle for ViewHandle<Workspace> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{fs::FakeFs, test::test_app_state};
|
||||||
editor::{Editor, Insert},
|
use editor::{Editor, Insert};
|
||||||
fs::FakeFs,
|
|
||||||
test::test_app_state,
|
|
||||||
};
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use util::test::temp_tree;
|
use util::test::temp_tree;
|
||||||
|
|
153
crates/zed/src/workspace/items.rs
Normal file
153
crates/zed/src/workspace/items.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue