Merge pull request #202 from zed-industries/crates

Break project into crates to improve incremental compilation time
This commit is contained in:
Max Brunsfeld 2021-10-05 10:54:15 -07:00 committed by GitHub
commit cefb90269e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
298 changed files with 199721 additions and 3228 deletions

306
Cargo.lock generated
View file

@ -742,6 +742,30 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "buffer"
version = "0.1.0"
dependencies = [
"anyhow",
"arrayvec 0.7.1",
"clock",
"gpui",
"lazy_static",
"log",
"parking_lot",
"rand 0.8.3",
"rpc",
"seahash",
"serde 1.0.125",
"similar",
"smallvec",
"sum_tree",
"theme",
"tree-sitter",
"tree-sitter-rust",
"unindent",
]
[[package]] [[package]]
name = "build_const" name = "build_const"
version = "0.2.2" version = "0.2.2"
@ -902,6 +926,20 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chat_panel"
version = "0.1.0"
dependencies = [
"client",
"editor",
"gpui",
"postage",
"theme",
"time 0.3.2",
"util",
"workspace",
]
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.19" version = "0.4.19"
@ -998,6 +1036,39 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "client"
version = "0.1.0"
dependencies = [
"anyhow",
"async-recursion",
"async-tungstenite",
"futures",
"gpui",
"image 0.23.14",
"lazy_static",
"log",
"parking_lot",
"postage",
"rand 0.8.3",
"rpc",
"smol",
"sum_tree",
"surf",
"thiserror",
"time 0.3.2",
"tiny_http",
"util",
]
[[package]]
name = "clock"
version = "0.1.0"
dependencies = [
"rpc",
"smallvec",
]
[[package]] [[package]]
name = "cloudabi" name = "cloudabi"
version = "0.0.3" version = "0.0.3"
@ -1544,6 +1615,30 @@ 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",
"theme",
"tree-sitter",
"tree-sitter-rust",
"unindent",
"util",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@ -1762,6 +1857,21 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "file_finder"
version = "0.1.0"
dependencies = [
"editor",
"fuzzy",
"gpui",
"postage",
"project",
"serde_json 1.0.64",
"theme",
"util",
"workspace",
]
[[package]] [[package]]
name = "filetime" name = "filetime"
version = "0.2.14" version = "0.2.14"
@ -2040,6 +2150,14 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "fuzzy"
version = "0.1.0"
dependencies = [
"gpui",
"util",
]
[[package]] [[package]]
name = "generator" name = "generator"
version = "0.6.23" version = "0.6.23"
@ -2175,7 +2293,6 @@ name = "gpui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arrayvec 0.7.1",
"async-task", "async-task",
"backtrace", "backtrace",
"bindgen", "bindgen",
@ -2212,6 +2329,7 @@ dependencies = [
"simplelog", "simplelog",
"smallvec", "smallvec",
"smol", "smol",
"sum_tree",
"time 0.3.2", "time 0.3.2",
"tiny-skia", "tiny-skia",
"tree-sitter", "tree-sitter",
@ -3324,6 +3442,17 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "people_panel"
version = "0.1.0"
dependencies = [
"client",
"gpui",
"postage",
"theme",
"workspace",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -3616,6 +3745,48 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "project"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"buffer",
"client",
"clock",
"fsevent",
"futures",
"fuzzy",
"gpui",
"ignore",
"lazy_static",
"libc",
"log",
"parking_lot",
"postage",
"rand 0.8.3",
"rpc",
"serde 1.0.125",
"serde_json 1.0.64",
"smol",
"sum_tree",
"tempdir",
"toml 0.5.8",
"util",
]
[[package]]
name = "project_panel"
version = "0.1.0"
dependencies = [
"gpui",
"postage",
"project",
"serde_json 1.0.64",
"theme",
"workspace",
]
[[package]] [[package]]
name = "prost" name = "prost"
version = "0.8.0" version = "0.8.0"
@ -4052,6 +4223,28 @@ dependencies = [
"xmlparser", "xmlparser",
] ]
[[package]]
name = "rpc"
version = "0.1.0"
dependencies = [
"anyhow",
"async-lock",
"async-tungstenite",
"base64 0.13.0",
"futures",
"log",
"parking_lot",
"postage",
"prost",
"prost-build",
"rand 0.8.3",
"rsa",
"serde 1.0.125",
"smol",
"tempdir",
"zstd",
]
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "0.4.0" version = "0.4.0"
@ -4940,6 +5133,14 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "sum_tree"
version = "0.1.0"
dependencies = [
"arrayvec 0.7.1",
"rand 0.8.3",
]
[[package]] [[package]]
name = "surf" name = "surf"
version = "2.2.0" version = "2.2.0"
@ -5112,6 +5313,35 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "theme"
version = "0.1.0"
dependencies = [
"anyhow",
"gpui",
"indexmap",
"parking_lot",
"serde 1.0.125",
"serde_json 1.0.64",
"serde_path_to_error",
"toml 0.5.8",
]
[[package]]
name = "theme_selector"
version = "0.1.0"
dependencies = [
"editor",
"fuzzy",
"gpui",
"log",
"parking_lot",
"postage",
"smol",
"theme",
"workspace",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.29" version = "1.0.29"
@ -5582,6 +5812,18 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "util"
version = "0.1.0"
dependencies = [
"anyhow",
"futures",
"log",
"serde_json 1.0.64",
"surf",
"tempdir",
]
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "0.5.1" version = "0.5.1"
@ -5854,6 +6096,24 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "workspace"
version = "0.1.0"
dependencies = [
"anyhow",
"buffer",
"client",
"editor",
"gpui",
"log",
"postage",
"project",
"serde_json 1.0.64",
"theme",
"tree-sitter",
"tree-sitter-rust",
]
[[package]] [[package]]
name = "wyz" name = "wyz"
version = "0.2.0" version = "0.2.0"
@ -5892,18 +6152,24 @@ name = "zed"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arrayvec 0.7.1",
"async-recursion", "async-recursion",
"async-trait", "async-trait",
"async-tungstenite", "async-tungstenite",
"buffer",
"cargo-bundle", "cargo-bundle",
"chat_panel",
"client",
"clock",
"crossbeam-channel", "crossbeam-channel",
"ctor", "ctor",
"dirs 3.0.1", "dirs 3.0.1",
"easy-parallel", "easy-parallel",
"editor",
"env_logger", "env_logger",
"file_finder",
"fsevent", "fsevent",
"futures", "futures",
"fuzzy",
"gpui", "gpui",
"http-auth-basic", "http-auth-basic",
"ignore", "ignore",
@ -5915,20 +6181,25 @@ dependencies = [
"log-panics", "log-panics",
"num_cpus", "num_cpus",
"parking_lot", "parking_lot",
"people_panel",
"postage", "postage",
"project",
"project_panel",
"rand 0.8.3", "rand 0.8.3",
"rpc",
"rsa", "rsa",
"rust-embed", "rust-embed",
"seahash",
"serde 1.0.125", "serde 1.0.125",
"serde_json 1.0.64", "serde_json 1.0.64",
"serde_path_to_error", "serde_path_to_error",
"similar",
"simplelog", "simplelog",
"smallvec", "smallvec",
"smol", "smol",
"sum_tree",
"surf", "surf",
"tempdir", "tempdir",
"theme",
"theme_selector",
"thiserror", "thiserror",
"time 0.3.2", "time 0.3.2",
"tiny_http", "tiny_http",
@ -5937,7 +6208,8 @@ dependencies = [
"tree-sitter-rust", "tree-sitter-rust",
"unindent", "unindent",
"url", "url",
"zrpc", "util",
"workspace",
] ]
[[package]] [[package]]
@ -5966,6 +6238,7 @@ dependencies = [
"parking_lot", "parking_lot",
"postage", "postage",
"rand 0.8.3", "rand 0.8.3",
"rpc",
"rust-embed", "rust-embed",
"scrypt", "scrypt",
"serde 1.0.125", "serde 1.0.125",
@ -5978,7 +6251,6 @@ dependencies = [
"time 0.2.25", "time 0.2.25",
"toml 0.5.8", "toml 0.5.8",
"zed", "zed",
"zrpc",
] ]
[[package]] [[package]]
@ -6002,28 +6274,6 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zrpc"
version = "0.1.0"
dependencies = [
"anyhow",
"async-lock",
"async-tungstenite",
"base64 0.13.0",
"futures",
"log",
"parking_lot",
"postage",
"prost",
"prost-build",
"rand 0.8.3",
"rsa",
"serde 1.0.125",
"smol",
"tempdir",
"zstd",
]
[[package]] [[package]]
name = "zstd" name = "zstd"
version = "0.9.0+zstd.1.5.0" version = "0.9.0+zstd.1.5.0"

View file

@ -1,6 +1,6 @@
[workspace] [workspace]
members = ["fsevent", "gpui", "gpui_macros", "server", "zed", "zrpc"] members = ["crates/*"]
default-members = ["zed"] default-members = ["crates/zed"]
[patch.crates-io] [patch.crates-io]
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }

30
crates/buffer/Cargo.toml Normal file
View file

@ -0,0 +1,30 @@
[package]
name = "buffer"
version = "0.1.0"
edition = "2018"
[features]
test-support = ["rand"]
[dependencies]
clock = { path = "../clock" }
gpui = { path = "../gpui" }
rpc = { path = "../rpc" }
sum_tree = { path = "../sum_tree" }
theme = { path = "../theme" }
anyhow = "1.0.38"
arrayvec = "0.7.1"
lazy_static = "1.4"
log = "0.4"
parking_lot = "0.11.1"
rand = { version = "0.8.3", optional = true }
seahash = "4.1"
serde = { version = "1", features = ["derive"] }
similar = "1.3"
smallvec = { version = "1.6", features = ["union"] }
tree-sitter = "0.19.5"
[dev-dependencies]
rand = "0.8.3"
tree-sitter-rust = "0.19.0"
unindent = "0.1.7"

View file

@ -1,13 +1,13 @@
use super::{Buffer, Content}; use super::{Buffer, Content};
use crate::{time, util::Bias};
use anyhow::Result; use anyhow::Result;
use std::{cmp::Ordering, ops::Range}; use std::{cmp::Ordering, ops::Range};
use sum_tree::Bias;
#[derive(Clone, Eq, PartialEq, Debug, Hash)] #[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub struct Anchor { pub struct Anchor {
pub offset: usize, pub offset: usize,
pub bias: Bias, pub bias: Bias,
pub version: time::Global, pub version: clock::Global,
} }
impl Anchor { impl Anchor {

View file

@ -1,5 +1,6 @@
use super::SyntaxTheme; use gpui::fonts::HighlightStyle;
use std::sync::Arc; use std::sync::Arc;
use theme::SyntaxTheme;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct HighlightMap(Arc<[HighlightId]>); pub struct HighlightMap(Arc<[HighlightId]>);
@ -49,6 +50,20 @@ impl HighlightMap {
} }
} }
impl HighlightId {
pub fn style(&self, theme: &SyntaxTheme) -> Option<HighlightStyle> {
theme
.highlights
.get(self.0 as usize)
.map(|entry| entry.1.clone())
}
#[cfg(any(test, feature = "test-support"))]
pub fn name<'a>(&self, theme: &'a SyntaxTheme) -> Option<&'a str> {
theme.highlights.get(self.0 as usize).map(|e| e.0.as_str())
}
}
impl Default for HighlightMap { impl Default for HighlightMap {
fn default() -> Self { fn default() -> Self {
Self(Arc::new([])) Self(Arc::new([]))
@ -89,8 +104,8 @@ mod tests {
]; ];
let map = HighlightMap::new(capture_names, &theme); let map = HighlightMap::new(capture_names, &theme);
assert_eq!(theme.highlight_name(map.get(0)), Some("function")); assert_eq!(map.get(0).name(&theme), Some("function"));
assert_eq!(theme.highlight_name(map.get(1)), Some("function.async")); assert_eq!(map.get(1).name(&theme), Some("function.async"));
assert_eq!(theme.highlight_name(map.get(2)), Some("variable.builtin")); assert_eq!(map.get(2).name(&theme), Some("variable.builtin"));
} }
} }

View file

@ -1,14 +1,10 @@
use crate::{settings::HighlightMap, theme::SyntaxTheme}; use crate::{HighlightMap};
use parking_lot::Mutex; use parking_lot::Mutex;
use rust_embed::RustEmbed;
use serde::Deserialize; use serde::Deserialize;
use std::{path::Path, str, sync::Arc}; use std::{path::Path, str, sync::Arc};
use tree_sitter::{Language as Grammar, Query}; use tree_sitter::{Language as Grammar, Query};
pub use tree_sitter::{Parser, Tree}; pub use tree_sitter::{Parser, Tree};
use theme::SyntaxTheme;
#[derive(RustEmbed)]
#[folder = "languages"]
pub struct LanguageDir;
#[derive(Default, Deserialize)] #[derive(Default, Deserialize)]
pub struct LanguageConfig { pub struct LanguageConfig {
@ -30,40 +26,18 @@ pub struct Language {
pub highlight_map: Mutex<HighlightMap>, pub highlight_map: Mutex<HighlightMap>,
} }
#[derive(Default)]
pub struct LanguageRegistry { pub struct LanguageRegistry {
languages: Vec<Arc<Language>>, languages: Vec<Arc<Language>>,
} }
impl Language {
pub fn name(&self) -> &str {
self.config.name.as_str()
}
pub fn highlight_map(&self) -> HighlightMap {
self.highlight_map.lock().clone()
}
pub fn set_theme(&self, theme: &SyntaxTheme) {
*self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme);
}
}
impl LanguageRegistry { impl LanguageRegistry {
pub fn new() -> Self { pub fn new() -> Self {
let grammar = tree_sitter_rust::language(); Self::default()
let rust_config = }
toml::from_slice(&LanguageDir::get("rust/config.toml").unwrap().data).unwrap();
let rust_language = Language {
config: rust_config,
grammar,
highlight_query: Self::load_query(grammar, "rust/highlights.scm"),
brackets_query: Self::load_query(grammar, "rust/brackets.scm"),
highlight_map: Mutex::new(HighlightMap::default()),
};
Self { pub fn add(&mut self, language: Arc<Language>) {
languages: vec![Arc::new(rust_language)], self.languages.push(language);
}
} }
pub fn set_theme(&self, theme: &SyntaxTheme) { pub fn set_theme(&self, theme: &SyntaxTheme) {
@ -85,19 +59,19 @@ impl LanguageRegistry {
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))) .any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
}) })
} }
fn load_query(grammar: tree_sitter::Language, path: &str) -> Query {
Query::new(
grammar,
str::from_utf8(&LanguageDir::get(path).unwrap().data).unwrap(),
)
.unwrap()
}
} }
impl Default for LanguageRegistry { impl Language {
fn default() -> Self { pub fn name(&self) -> &str {
Self::new() self.config.name.as_str()
}
pub fn highlight_map(&self) -> HighlightMap {
self.highlight_map.lock().clone()
}
pub fn set_theme(&self, theme: &SyntaxTheme) {
*self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,12 @@
use super::Operation; use super::Operation;
use crate::time;
use gpui::sum_tree::{Cursor, Dimension, Edit, Item, KeyedItem, SumTree, Summary};
use std::{fmt::Debug, ops::Add}; use std::{fmt::Debug, ops::Add};
use sum_tree::{Cursor, Dimension, Edit, Item, KeyedItem, SumTree, Summary};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct OperationQueue(SumTree<Operation>); pub struct OperationQueue(SumTree<Operation>);
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct OperationKey(time::Lamport); pub struct OperationKey(clock::Lamport);
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct OperationSummary { pub struct OperationSummary {
@ -16,7 +15,7 @@ pub struct OperationSummary {
} }
impl OperationKey { impl OperationKey {
pub fn new(timestamp: time::Lamport) -> Self { pub fn new(timestamp: clock::Lamport) -> Self {
Self(timestamp) Self(timestamp)
} }
} }
@ -102,7 +101,7 @@ mod tests {
#[test] #[test]
fn test_len() { fn test_len() {
let mut clock = time::Lamport::new(0); let mut clock = clock::Lamport::new(0);
let mut queue = OperationQueue::new(); let mut queue = OperationQueue::new();
assert_eq!(queue.len(), 0); assert_eq!(queue.len(), 0);
@ -124,5 +123,5 @@ mod tests {
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
struct TestOperation(time::Lamport); struct TestOperation(clock::Lamport);
} }

View file

@ -0,0 +1,28 @@
use rand::prelude::*;
pub struct RandomCharIter<T: Rng>(T);
impl<T: Rng> RandomCharIter<T> {
pub fn new(rng: T) -> Self {
Self(rng)
}
}
impl<T: Rng> Iterator for RandomCharIter<T> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
match self.0.gen_range(0..100) {
// whitespace
0..=19 => [' ', '\n', '\t'].choose(&mut self.0).copied(),
// two-byte greek letters
20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))),
// three-byte characters
33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(),
// four-byte characters
46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(),
// ascii letters
_ => Some(self.0.gen_range(b'a'..b'z' + 1).into()),
}
}
}

View file

@ -1,9 +1,8 @@
use super::Point; use super::Point;
use crate::util::Bias;
use arrayvec::ArrayString; use arrayvec::ArrayString;
use gpui::sum_tree::{self, SumTree};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{cmp, ops::Range, str}; use std::{cmp, ops::Range, str};
use sum_tree::{Bias, SumTree};
#[cfg(test)] #[cfg(test)]
const CHUNK_BASE: usize = 6; const CHUNK_BASE: usize = 6;
@ -520,7 +519,7 @@ fn find_split_ix(text: &str) -> usize {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::util::RandomCharIter; use crate::random_char_iter::RandomCharIter;
use rand::prelude::*; use rand::prelude::*;
use std::env; use std::env;
use Bias::{Left, Right}; use Bias::{Left, Right};

View file

@ -1,13 +1,7 @@
use crate::{ use crate::{Anchor, Buffer, Point, ToOffset as _, ToPoint as _};
editor::{
buffer::{Anchor, Buffer, Point, ToOffset as _, ToPoint as _},
Bias, DisplayMapSnapshot, DisplayPoint,
},
time,
};
use std::{cmp::Ordering, mem, ops::Range}; use std::{cmp::Ordering, mem, ops::Range};
pub type SelectionSetId = time::Lamport; pub type SelectionSetId = clock::Lamport;
pub type SelectionsVersion = usize; pub type SelectionsVersion = usize;
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@ -17,11 +11,6 @@ pub enum SelectionGoal {
ColumnRange { start: u32, end: u32 }, ColumnRange { start: u32, end: u32 },
} }
pub struct SpannedRows {
pub buffer_rows: Range<u32>,
pub display_rows: Range<u32>,
}
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct Selection { pub struct Selection {
pub id: usize, pub id: usize,
@ -83,38 +72,4 @@ impl Selection {
start..end start..end
} }
} }
pub fn display_range(&self, map: &DisplayMapSnapshot) -> Range<DisplayPoint> {
let start = self.start.to_display_point(map, Bias::Left);
let end = self.end.to_display_point(map, Bias::Left);
if self.reversed {
end..start
} else {
start..end
}
}
pub fn spanned_rows(
&self,
include_end_if_at_line_start: bool,
map: &DisplayMapSnapshot,
) -> SpannedRows {
let display_start = self.start.to_display_point(map, Bias::Left);
let mut display_end = self.end.to_display_point(map, Bias::Right);
if !include_end_if_at_line_start
&& display_end.row() != map.max_point().row()
&& display_start.row() != display_end.row()
&& display_end.column() == 0
{
*display_end.row_mut() -= 1;
}
let (display_start, buffer_start) = map.prev_row_boundary(display_start);
let (display_end, buffer_end) = map.next_row_boundary(display_end);
SpannedRows {
buffer_rows: buffer_start.row..buffer_end.row + 1,
display_rows: display_start.row()..display_end.row() + 1,
}
}
} }

View file

@ -0,0 +1,14 @@
[package]
name = "chat_panel"
version = "0.1.0"
edition = "2018"
[dependencies]
client = { path = "../client" }
editor = { path = "../editor" }
gpui = { path = "../gpui" }
theme = { path = "../theme" }
util = { path = "../util" }
workspace = { path = "../workspace" }
postage = { version = "0.4.1", features = ["futures-traits"] }
time = "0.3"

View file

@ -1,13 +1,8 @@
use std::sync::Arc; use client::{
use crate::{
channel::{Channel, ChannelEvent, ChannelList, ChannelMessage}, channel::{Channel, ChannelEvent, ChannelList, ChannelMessage},
editor::Editor, Client,
rpc::{self, Client},
theme,
util::{ResultExt, TryFutureExt},
Settings,
}; };
use editor::{Editor, EditorSettings};
use gpui::{ use gpui::{
action, action,
elements::*, elements::*,
@ -18,7 +13,10 @@ use gpui::{
ViewContext, ViewHandle, ViewContext, ViewHandle,
}; };
use postage::{prelude::Stream, watch}; use postage::{prelude::Stream, watch};
use std::sync::Arc;
use time::{OffsetDateTime, UtcOffset}; use time::{OffsetDateTime, UtcOffset};
use util::{ResultExt, TryFutureExt};
use workspace::Settings;
const MESSAGE_LOADING_THRESHOLD: usize = 50; const MESSAGE_LOADING_THRESHOLD: usize = 50;
@ -56,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,
) )
@ -406,7 +409,10 @@ impl View for ChatPanel {
} }
fn on_focus(&mut self, cx: &mut ViewContext<Self>) { fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
if matches!(*self.rpc.status().borrow(), rpc::Status::Connected { .. }) { if matches!(
*self.rpc.status().borrow(),
client::Status::Connected { .. }
) {
cx.focus(&self.input_editor); cx.focus(&self.input_editor);
} }
} }

28
crates/client/Cargo.toml Normal file
View file

@ -0,0 +1,28 @@
[package]
name = "client"
version = "0.1.0"
edition = "2018"
[features]
test-support = ["rpc/test-support"]
[dependencies]
gpui = { path = "../gpui" }
util = { path = "../util" }
rpc = { path = "../rpc" }
sum_tree = { path = "../sum_tree" }
anyhow = "1.0.38"
async-recursion = "0.3"
async-tungstenite = { version = "0.14", features = ["async-tls"] }
futures = "0.3"
image = "0.23"
lazy_static = "1.4.0"
log = "0.4"
parking_lot = "0.11.1"
postage = { version = "0.4.1", features = ["futures-traits"] }
rand = "0.8.3"
smol = "1.2.5"
surf = "2.2"
thiserror = "1.0.29"
time = "0.3"
tiny_http = "0.8"

View file

@ -1,11 +1,10 @@
use crate::{ use super::{
rpc::{self, Client}, proto,
user::{User, UserStore}, user::{User, UserStore},
util::{post_inc, TryFutureExt}, Client, Status, Subscription, TypedEnvelope,
}; };
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use gpui::{ use gpui::{
sum_tree::{self, Bias, SumTree},
AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, WeakModelHandle, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, WeakModelHandle,
}; };
use postage::prelude::Stream; use postage::prelude::Stream;
@ -16,16 +15,14 @@ use std::{
ops::Range, ops::Range,
sync::Arc, sync::Arc,
}; };
use sum_tree::{Bias, SumTree};
use time::OffsetDateTime; use time::OffsetDateTime;
use zrpc::{ use util::{post_inc, TryFutureExt};
proto::{self, ChannelMessageSent},
TypedEnvelope,
};
pub struct ChannelList { pub struct ChannelList {
available_channels: Option<Vec<ChannelDetails>>, available_channels: Option<Vec<ChannelDetails>>,
channels: HashMap<u64, WeakModelHandle<Channel>>, channels: HashMap<u64, WeakModelHandle<Channel>>,
rpc: Arc<Client>, client: Arc<Client>,
user_store: ModelHandle<UserStore>, user_store: ModelHandle<UserStore>,
_task: Task<Option<()>>, _task: Task<Option<()>>,
} }
@ -44,7 +41,7 @@ pub struct Channel {
user_store: ModelHandle<UserStore>, user_store: ModelHandle<UserStore>,
rpc: Arc<Client>, rpc: Arc<Client>,
rng: StdRng, rng: StdRng,
_subscription: rpc::Subscription, _subscription: Subscription,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -88,7 +85,7 @@ impl Entity for ChannelList {
impl ChannelList { impl ChannelList {
pub fn new( pub fn new(
user_store: ModelHandle<UserStore>, user_store: ModelHandle<UserStore>,
rpc: Arc<rpc::Client>, rpc: Arc<Client>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Self { ) -> Self {
let _task = cx.spawn_weak(|this, mut cx| { let _task = cx.spawn_weak(|this, mut cx| {
@ -97,7 +94,7 @@ impl ChannelList {
let mut status = rpc.status(); let mut status = rpc.status();
while let Some((status, this)) = status.recv().await.zip(this.upgrade(&cx)) { while let Some((status, this)) = status.recv().await.zip(this.upgrade(&cx)) {
match status { match status {
rpc::Status::Connected { .. } => { Status::Connected { .. } => {
let response = rpc let response = rpc
.request(proto::GetChannels {}) .request(proto::GetChannels {})
.await .await
@ -121,7 +118,7 @@ impl ChannelList {
cx.notify(); cx.notify();
}); });
} }
rpc::Status::SignedOut { .. } => { Status::SignedOut { .. } => {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.available_channels = None; this.available_channels = None;
this.channels.clear(); this.channels.clear();
@ -140,7 +137,7 @@ impl ChannelList {
available_channels: None, available_channels: None,
channels: Default::default(), channels: Default::default(),
user_store, user_store,
rpc, client: rpc,
_task, _task,
} }
} }
@ -160,8 +157,9 @@ impl ChannelList {
let channels = self.available_channels.as_ref()?; let channels = self.available_channels.as_ref()?;
let details = channels.iter().find(|details| details.id == id)?.clone(); let details = channels.iter().find(|details| details.id == id)?.clone();
let channel = let channel = cx.add_model(|cx| {
cx.add_model(|cx| Channel::new(details, self.user_store.clone(), self.rpc.clone(), cx)); Channel::new(details, self.user_store.clone(), self.client.clone(), cx)
});
self.channels.insert(id, channel.downgrade()); self.channels.insert(id, channel.downgrade());
Some(channel) Some(channel)
} }
@ -406,8 +404,8 @@ impl Channel {
fn handle_message_sent( fn handle_message_sent(
&mut self, &mut self,
message: TypedEnvelope<ChannelMessageSent>, message: TypedEnvelope<proto::ChannelMessageSent>,
_: Arc<rpc::Client>, _: Arc<Client>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Result<()> { ) -> Result<()> {
let user_store = self.user_store.clone(); let user_store = self.user_store.clone();

View file

@ -1,15 +1,22 @@
use crate::util::ResultExt; #[cfg(any(test, feature = "test-support"))]
pub mod test;
pub mod channel;
pub mod http;
pub mod user;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_recursion::async_recursion; use async_recursion::async_recursion;
use async_tungstenite::tungstenite::{ use async_tungstenite::tungstenite::{
error::Error as WebsocketError, error::Error as WebsocketError,
http::{Request, StatusCode}, http::{Request, StatusCode},
}; };
use gpui::{AsyncAppContext, Entity, ModelContext, Task}; use gpui::{action, AsyncAppContext, Entity, ModelContext, MutableAppContext, Task};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::RwLock; use parking_lot::RwLock;
use postage::{prelude::Stream, watch}; use postage::{prelude::Stream, watch};
use rand::prelude::*; use rand::prelude::*;
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage};
use std::{ use std::{
any::TypeId, any::TypeId,
collections::HashMap, collections::HashMap,
@ -21,11 +28,11 @@ use std::{
}; };
use surf::Url; use surf::Url;
use thiserror::Error; use thiserror::Error;
pub use zrpc::{proto, ConnectionId, PeerId, TypedEnvelope}; use util::{ResultExt, TryFutureExt};
use zrpc::{
proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage}, pub use channel::*;
Connection, Peer, Receipt, pub use rpc::*;
}; pub use user::*;
lazy_static! { lazy_static! {
static ref ZED_SERVER_URL: String = static ref ZED_SERVER_URL: String =
@ -35,6 +42,16 @@ lazy_static! {
.and_then(|s| if s.is_empty() { None } else { Some(s) }); .and_then(|s| if s.is_empty() { None } else { Some(s) });
} }
action!(Authenticate);
pub fn init(rpc: Arc<Client>, cx: &mut MutableAppContext) {
cx.add_global_action(move |_: &Authenticate, cx| {
let rpc = rpc.clone();
cx.spawn(|cx| async move { rpc.authenticate_and_connect(&cx).log_err().await })
.detach();
});
}
pub struct Client { pub struct Client {
peer: Arc<Peer>, peer: Arc<Peer>,
state: RwLock<ClientState>, state: RwLock<ClientState>,
@ -503,7 +520,7 @@ impl Client {
"Authorization", "Authorization",
format!("{} {}", credentials.user_id, credentials.access_token), format!("{} {}", credentials.user_id, credentials.access_token),
) )
.header("X-Zed-Protocol-Version", zrpc::PROTOCOL_VERSION); .header("X-Zed-Protocol-Version", rpc::PROTOCOL_VERSION);
cx.background().spawn(async move { cx.background().spawn(async move {
if let Some(host) = ZED_SERVER_URL.strip_prefix("https://") { if let Some(host) = ZED_SERVER_URL.strip_prefix("https://") {
let stream = smol::net::TcpStream::connect(host).await?; let stream = smol::net::TcpStream::connect(host).await?;
@ -533,7 +550,7 @@ impl Client {
// zed server to encrypt the user's access token, so that it can'be intercepted by // zed server to encrypt the user's access token, so that it can'be intercepted by
// any other app running on the user's device. // any other app running on the user's device.
let (public_key, private_key) = let (public_key, private_key) =
zrpc::auth::keypair().expect("failed to generate keypair for auth"); rpc::auth::keypair().expect("failed to generate keypair for auth");
let public_key_string = let public_key_string =
String::try_from(public_key).expect("failed to serialize public key for auth"); String::try_from(public_key).expect("failed to serialize public key for auth");

188
crates/client/src/test.rs Normal file
View file

@ -0,0 +1,188 @@
use super::Client;
use super::*;
use crate::http::{HttpClient, Request, Response, ServerResponse};
use futures::{future::BoxFuture, Future};
use gpui::TestAppContext;
use parking_lot::Mutex;
use postage::{mpsc, prelude::Stream};
use rpc::{proto, ConnectionId, Peer, Receipt, TypedEnvelope};
use std::fmt;
use std::sync::atomic::Ordering::SeqCst;
use std::sync::{
atomic::{AtomicBool, AtomicUsize},
Arc,
};
pub struct FakeServer {
peer: Arc<Peer>,
incoming: Mutex<Option<mpsc::Receiver<Box<dyn proto::AnyTypedEnvelope>>>>,
connection_id: Mutex<Option<ConnectionId>>,
forbid_connections: AtomicBool,
auth_count: AtomicUsize,
access_token: AtomicUsize,
user_id: u64,
}
impl FakeServer {
pub async fn for_client(
client_user_id: u64,
client: &mut Arc<Client>,
cx: &TestAppContext,
) -> Arc<Self> {
let server = Arc::new(Self {
peer: Peer::new(),
incoming: Default::default(),
connection_id: Default::default(),
forbid_connections: Default::default(),
auth_count: Default::default(),
access_token: Default::default(),
user_id: client_user_id,
});
Arc::get_mut(client)
.unwrap()
.override_authenticate({
let server = server.clone();
move |cx| {
server.auth_count.fetch_add(1, SeqCst);
let access_token = server.access_token.load(SeqCst).to_string();
cx.spawn(move |_| async move {
Ok(Credentials {
user_id: client_user_id,
access_token,
})
})
}
})
.override_establish_connection({
let server = server.clone();
move |credentials, cx| {
let credentials = credentials.clone();
cx.spawn({
let server = server.clone();
move |cx| async move { server.establish_connection(&credentials, &cx).await }
})
}
});
client
.authenticate_and_connect(&cx.to_async())
.await
.unwrap();
server
}
pub async fn disconnect(&self) {
self.peer.disconnect(self.connection_id()).await;
self.connection_id.lock().take();
self.incoming.lock().take();
}
async fn establish_connection(
&self,
credentials: &Credentials,
cx: &AsyncAppContext,
) -> Result<Connection, EstablishConnectionError> {
assert_eq!(credentials.user_id, self.user_id);
if self.forbid_connections.load(SeqCst) {
Err(EstablishConnectionError::Other(anyhow!(
"server is forbidding connections"
)))?
}
if credentials.access_token != self.access_token.load(SeqCst).to_string() {
Err(EstablishConnectionError::Unauthorized)?
}
let (client_conn, server_conn, _) = Connection::in_memory();
let (connection_id, io, incoming) = self.peer.add_connection(server_conn).await;
cx.background().spawn(io).detach();
*self.incoming.lock() = Some(incoming);
*self.connection_id.lock() = Some(connection_id);
Ok(client_conn)
}
pub fn auth_count(&self) -> usize {
self.auth_count.load(SeqCst)
}
pub fn roll_access_token(&self) {
self.access_token.fetch_add(1, SeqCst);
}
pub fn forbid_connections(&self) {
self.forbid_connections.store(true, SeqCst);
}
pub fn allow_connections(&self) {
self.forbid_connections.store(false, SeqCst);
}
pub async fn send<T: proto::EnvelopedMessage>(&self, message: T) {
self.peer.send(self.connection_id(), message).await.unwrap();
}
pub async fn receive<M: proto::EnvelopedMessage>(&self) -> Result<TypedEnvelope<M>> {
let message = self
.incoming
.lock()
.as_mut()
.expect("not connected")
.recv()
.await
.ok_or_else(|| anyhow!("other half hung up"))?;
let type_name = message.payload_type_name();
Ok(*message
.into_any()
.downcast::<TypedEnvelope<M>>()
.unwrap_or_else(|_| {
panic!(
"fake server received unexpected message type: {:?}",
type_name
);
}))
}
pub async fn respond<T: proto::RequestMessage>(
&self,
receipt: Receipt<T>,
response: T::Response,
) {
self.peer.respond(receipt, response).await.unwrap()
}
fn connection_id(&self) -> ConnectionId {
self.connection_id.lock().expect("not connected")
}
}
pub struct FakeHttpClient {
handler:
Box<dyn 'static + Send + Sync + Fn(Request) -> BoxFuture<'static, Result<ServerResponse>>>,
}
impl FakeHttpClient {
pub fn new<Fut, F>(handler: F) -> Arc<dyn HttpClient>
where
Fut: 'static + Send + Future<Output = Result<ServerResponse>>,
F: 'static + Send + Sync + Fn(Request) -> Fut,
{
Arc::new(Self {
handler: Box::new(move |req| Box::pin(handler(req))),
})
}
}
impl fmt::Debug for FakeHttpClient {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FakeHttpClient").finish()
}
}
impl HttpClient for FakeHttpClient {
fn send<'a>(&'a self, req: Request) -> BoxFuture<'a, Result<Response>> {
let future = (self.handler)(req);
Box::pin(async move { future.await.map(Into::into) })
}
}

View file

@ -1,7 +1,6 @@
use crate::{ use super::{
http::{HttpClient, Method, Request, Url}, http::{HttpClient, Method, Request, Url},
rpc::{Client, Status}, proto, Client, Status, TypedEnvelope,
util::TryFutureExt,
}; };
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use futures::future; use futures::future;
@ -11,7 +10,7 @@ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
sync::Arc, sync::Arc,
}; };
use zrpc::{proto, TypedEnvelope}; use util::TryFutureExt as _;
#[derive(Debug)] #[derive(Debug)]
pub struct User { pub struct User {
@ -208,7 +207,7 @@ impl User {
User { User {
id: message.id, id: message.id,
github_login: message.github_login, github_login: message.github_login,
avatar: fetch_avatar(http, &message.avatar_url).log_err().await, avatar: fetch_avatar(http, &message.avatar_url).warn_on_err().await,
} }
} }
} }
@ -256,6 +255,9 @@ async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result<Arc<ImageData>
.send(request) .send(request)
.await .await
.map_err(|e| anyhow!("failed to send user avatar request: {}", e))?; .map_err(|e| anyhow!("failed to send user avatar request: {}", e))?;
if !response.status().is_success() {
return Err(anyhow!("avatar request failed {:?}", response.status()));
}
let bytes = response let bytes = response
.body_bytes() .body_bytes()
.await .await

8
crates/clock/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "clock"
version = "0.1.0"
edition = "2018"
[dependencies]
smallvec = { version = "1.6", features = ["union"] }
rpc = { path = "../rpc" }

View file

@ -61,8 +61,8 @@ impl<'a> AddAssign<&'a Local> for Local {
#[derive(Clone, Default, Hash, Eq, PartialEq)] #[derive(Clone, Default, Hash, Eq, PartialEq)]
pub struct Global(SmallVec<[Local; 3]>); pub struct Global(SmallVec<[Local; 3]>);
impl From<Vec<zrpc::proto::VectorClockEntry>> for Global { impl From<Vec<rpc::proto::VectorClockEntry>> for Global {
fn from(message: Vec<zrpc::proto::VectorClockEntry>) -> Self { fn from(message: Vec<rpc::proto::VectorClockEntry>) -> Self {
let mut version = Self::new(); let mut version = Self::new();
for entry in message { for entry in message {
version.observe(Local { version.observe(Local {
@ -74,11 +74,11 @@ impl From<Vec<zrpc::proto::VectorClockEntry>> for Global {
} }
} }
impl<'a> From<&'a Global> for Vec<zrpc::proto::VectorClockEntry> { impl<'a> From<&'a Global> for Vec<rpc::proto::VectorClockEntry> {
fn from(version: &'a Global) -> Self { fn from(version: &'a Global) -> Self {
version version
.iter() .iter()
.map(|entry| zrpc::proto::VectorClockEntry { .map(|entry| rpc::proto::VectorClockEntry {
replica_id: entry.replica_id as u32, replica_id: entry.replica_id as u32,
timestamp: entry.value, timestamp: entry.value,
}) })

31
crates/editor/Cargo.toml Normal file
View file

@ -0,0 +1,31 @@
[package]
name = "editor"
version = "0.1.0"
edition = "2018"
[features]
test-support = ["buffer/test-support", "gpui/test-support"]
[dependencies]
buffer = { path = "../buffer" }
clock = { path = "../clock" }
gpui = { path = "../gpui" }
sum_tree = { path = "../sum_tree" }
theme = { path = "../theme" }
util = { path = "../util" }
anyhow = "1.0"
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"
[dev-dependencies]
buffer = { path = "../buffer", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
rand = "0.8"
unindent = "0.1.7"
tree-sitter = "0.19"
tree-sitter-rust = "0.19"

View file

@ -2,14 +2,19 @@ mod fold_map;
mod tab_map; mod tab_map;
mod wrap_map; mod wrap_map;
use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint}; use buffer::{Anchor, Buffer, Point, ToOffset, ToPoint};
use fold_map::FoldMap; use fold_map::{FoldMap, ToFoldPoint as _};
use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle}; use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
use std::ops::Range; use std::ops::Range;
use sum_tree::Bias;
use tab_map::TabMap; use tab_map::TabMap;
use wrap_map::WrapMap; use wrap_map::WrapMap;
pub use wrap_map::{BufferRows, HighlightedChunks}; pub use wrap_map::{BufferRows, HighlightedChunks};
pub trait ToDisplayPoint {
fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint;
}
pub struct DisplayMap { pub struct DisplayMap {
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
fold_map: FoldMap, fold_map: FoldMap,
@ -333,8 +338,8 @@ impl DisplayPoint {
} }
} }
impl Point { impl ToDisplayPoint for Point {
pub fn to_display_point(self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint { fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
let fold_point = self.to_fold_point(&map.folds_snapshot, bias); let fold_point = self.to_fold_point(&map.folds_snapshot, bias);
let tab_point = map.tabs_snapshot.to_tab_point(fold_point); let tab_point = map.tabs_snapshot.to_tab_point(fold_point);
let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point); let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point);
@ -342,8 +347,8 @@ impl Point {
} }
} }
impl Anchor { impl ToDisplayPoint for Anchor {
pub fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint { fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
self.to_point(&map.buffer_snapshot) self.to_point(&map.buffer_snapshot)
.to_display_point(map, bias) .to_display_point(map, bias)
} }
@ -352,17 +357,12 @@ impl Anchor {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{ use crate::{movement, test::*};
editor::movement, use buffer::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal};
language::{Language, LanguageConfig},
test::*,
theme::SyntaxTheme,
util::RandomCharIter,
};
use buffer::{History, SelectionGoal};
use gpui::{color::Color, MutableAppContext}; use gpui::{color::Color, MutableAppContext};
use rand::{prelude::StdRng, Rng}; use rand::{prelude::StdRng, Rng};
use std::{env, sync::Arc}; use std::{env, sync::Arc};
use theme::SyntaxTheme;
use Bias::*; use Bias::*;
#[gpui::test(iterations = 100)] #[gpui::test(iterations = 100)]
@ -977,7 +977,7 @@ mod tests {
let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx)); let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
let mut chunks: Vec<(String, Option<&str>)> = Vec::new(); let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
for (chunk, style_id) in snapshot.highlighted_chunks_for_rows(rows) { for (chunk, style_id) in snapshot.highlighted_chunks_for_rows(rows) {
let style_name = theme.highlight_name(style_id); let style_name = style_id.name(theme);
if let Some((last_chunk, last_style_name)) = chunks.last_mut() { if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
if style_name == *last_style_name { if style_name == *last_style_name {
last_chunk.push_str(chunk); last_chunk.push_str(chunk);

View file

@ -1,12 +1,5 @@
use super::{ use buffer::{Anchor, Buffer, Point, ToOffset, AnchorRangeExt, HighlightId, TextSummary};
buffer::{AnchorRangeExt, TextSummary}, use gpui::{AppContext, ModelHandle};
Anchor, Buffer, Point, ToOffset,
};
use crate::{editor::buffer, settings::HighlightId, time, util::Bias};
use gpui::{
sum_tree::{self, Cursor, FilterCursor, SumTree},
AppContext, ModelHandle,
};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
cmp::{self, Ordering}, cmp::{self, Ordering},
@ -14,6 +7,11 @@ use std::{
ops::Range, ops::Range,
sync::atomic::{AtomicUsize, Ordering::SeqCst}, sync::atomic::{AtomicUsize, Ordering::SeqCst},
}; };
use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
pub trait ToFoldPoint {
fn to_fold_point(&self, snapshot: &Snapshot, bias: Bias) -> FoldPoint;
}
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct FoldPoint(pub super::Point); pub struct FoldPoint(pub super::Point);
@ -75,8 +73,8 @@ impl FoldPoint {
} }
} }
impl Point { impl ToFoldPoint for Point {
pub fn to_fold_point(&self, snapshot: &Snapshot, bias: Bias) -> FoldPoint { fn to_fold_point(&self, snapshot: &Snapshot, bias: Bias) -> FoldPoint {
let mut cursor = snapshot.transforms.cursor::<(Point, FoldPoint)>(); let mut cursor = snapshot.transforms.cursor::<(Point, FoldPoint)>();
cursor.seek(self, Bias::Right, &()); cursor.seek(self, Bias::Right, &());
if cursor.item().map_or(false, |t| t.is_fold()) { if cursor.item().map_or(false, |t| t.is_fold()) {
@ -202,7 +200,7 @@ pub struct FoldMap {
#[derive(Clone)] #[derive(Clone)]
struct SyncState { struct SyncState {
version: time::Global, version: clock::Global,
parse_count: usize, parse_count: usize,
} }
@ -546,6 +544,7 @@ impl Snapshot {
summary summary
} }
#[cfg(test)]
pub fn len(&self) -> FoldOffset { pub fn len(&self) -> FoldOffset {
FoldOffset(self.transforms.summary().output.bytes) FoldOffset(self.transforms.summary().output.bytes)
} }
@ -1125,7 +1124,8 @@ impl FoldEdit {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{editor::ToPoint, test::sample_text, util::RandomCharIter}; use crate::{test::sample_text, ToPoint};
use buffer::RandomCharIter;
use rand::prelude::*; use rand::prelude::*;
use std::{env, mem}; use std::{env, mem};
use Bias::{Left, Right}; use Bias::{Left, Right};

View file

@ -1,8 +1,8 @@
use parking_lot::Mutex;
use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot}; use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot};
use crate::{editor::rope, settings::HighlightId, util::Bias}; use buffer::{rope, HighlightId};
use parking_lot::Mutex;
use std::{mem, ops::Range}; use std::{mem, ops::Range};
use sum_tree::Bias;
pub struct TabMap(Mutex<Snapshot>); pub struct TabMap(Mutex<Snapshot>);

View file

@ -2,16 +2,12 @@ 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, settings::HighlightId, util::Bias}; use buffer::{HighlightId, Point};
use gpui::{ use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task};
fonts::FontId,
sum_tree::{self, Cursor, SumTree},
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;
use std::{collections::VecDeque, ops::Range, time::Duration}; use std::{collections::VecDeque, ops::Range, time::Duration};
use sum_tree::{Bias, Cursor, SumTree};
pub struct WrapMap { pub struct WrapMap {
snapshot: Snapshot, snapshot: Snapshot,
@ -900,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,
util::RandomCharIter,
}; };
use buffer::{Buffer, RandomCharIter};
use rand::prelude::*; use rand::prelude::*;
use std::env; use std::env;

View file

@ -1,8 +1,9 @@
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 crate::{theme::HighlightId, time::ReplicaId}; use buffer::HighlightId;
use clock::ReplicaId;
use gpui::{ use gpui::{
color::Color, color::Color,
geometry::{ geometry::{
@ -27,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 {
@ -195,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.,
}); });
@ -230,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.,
}); });
@ -267,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;
@ -286,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 {
@ -382,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,
}, },
@ -406,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
@ -415,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);
@ -426,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,
}, },
@ -455,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()
@ -481,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();
@ -497,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();
@ -510,17 +513,19 @@ 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_ix
.syntax .style(&style.syntax)
.highlight_style(style_ix) .unwrap_or(style.text.clone().into());
.unwrap_or(self.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 {
@ -537,12 +542,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;
} }
} }
} }
@ -566,12 +571,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;
@ -579,8 +585,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| {
@ -676,7 +682,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,
@ -688,7 +694,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;
@ -1034,30 +1040,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);

View file

@ -1,28 +1,20 @@
pub mod buffer;
pub mod display_map; pub mod display_map;
mod element; mod element;
pub mod movement; pub mod movement;
use crate::{ #[cfg(test)]
language::Language, mod test;
settings::Settings,
theme::Theme, use buffer::*;
time::ReplicaId, use clock::ReplicaId;
util::{post_inc, Bias},
workspace,
worktree::{File, Worktree},
};
use anyhow::Result;
pub use buffer::*;
pub use display_map::DisplayPoint; pub use display_map::DisplayPoint;
use display_map::*; use display_map::*;
pub use element::*; 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;
@ -31,11 +23,13 @@ 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 theme::EditorStyle;
use util::post_inc;
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;
@ -250,6 +244,20 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::fold_selected_ranges); cx.add_action(Editor::fold_selected_ranges);
} }
trait SelectionExt {
fn display_range(&self, map: &DisplayMapSnapshot) -> Range<DisplayPoint>;
fn spanned_rows(
&self,
include_end_if_at_line_start: bool,
map: &DisplayMapSnapshot,
) -> SpannedRows;
}
struct SpannedRows {
buffer_rows: Range<u32>,
display_rows: Range<u32>,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SelectPhase { pub enum SelectPhase {
Begin { Begin {
@ -270,24 +278,10 @@ pub enum EditorMode {
Full, Full,
} }
#[derive(Clone, Deserialize)] #[derive(Clone)]
pub struct EditorStyle { pub struct EditorSettings {
pub text: TextStyle, pub tab_size: usize,
#[serde(default)] pub style: EditorStyle,
pub placeholder_text: Option<TextStyle>,
pub background: Color,
pub selection: SelectionStyle,
pub gutter_background: Color,
pub active_line_background: Color,
pub line_number: Color,
pub line_number_active: Color,
pub guest_selections: Vec<SelectionStyle>,
}
#[derive(Clone, Copy, Default, Deserialize)]
pub struct SelectionStyle {
pub cursor: Color,
pub selection: Color,
} }
pub struct Editor { pub struct Editor {
@ -302,8 +296,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,
@ -316,7 +309,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,
@ -335,50 +327,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,
) )
@ -410,11 +405,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,
@ -433,14 +427,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
@ -710,7 +701,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>>,
{ {
@ -2284,9 +2279,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
@ -2400,10 +2395,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,
@ -2437,39 +2428,42 @@ impl Snapshot {
} }
} }
impl EditorStyle { impl EditorSettings {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn test(font_cache: &gpui::FontCache) -> Self { pub fn test(cx: &AppContext) -> Self {
let font_family_name = Arc::from("Monaco");
let font_properties = Default::default();
let font_family_id = font_cache.load_family(&[&font_family_name]).unwrap();
let font_id = font_cache
.select_font(font_family_id, &font_properties)
.unwrap();
Self { Self {
text: TextStyle { tab_size: 4,
font_family_name, style: {
font_family_id, let font_cache: &gpui::FontCache = cx.font_cache();
font_id, let font_family_name = Arc::from("Monaco");
font_size: 14., let font_properties = Default::default();
color: Color::from_u32(0xff0000ff), let font_family_id = font_cache.load_family(&[&font_family_name]).unwrap();
font_properties, let font_id = font_cache
underline: false, .select_font(font_family_id, &font_properties)
.unwrap();
EditorStyle {
text: TextStyle {
font_family_name,
font_family_id,
font_id,
font_size: 14.,
color: Color::from_u32(0xff0000ff),
font_properties,
underline: false,
},
placeholder_text: None,
background: Default::default(),
gutter_background: Default::default(),
active_line_background: Default::default(),
line_number: Default::default(),
line_number_active: Default::default(),
selection: Default::default(),
guest_selections: Default::default(),
syntax: Default::default(),
}
}, },
placeholder_text: None,
background: Default::default(),
gutter_background: Default::default(),
active_line_background: Default::default(),
line_number: Default::default(),
line_number_active: Default::default(),
selection: Default::default(),
guest_selections: Default::default(),
} }
} }
fn placeholder_text(&self) -> &TextStyle {
self.placeholder_text.as_ref().unwrap_or(&self.text)
}
} }
fn compute_scroll_position( fn compute_scroll_position(
@ -2508,11 +2502,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 {
@ -2551,135 +2549,53 @@ impl View for Editor {
} }
} }
impl workspace::Item for Buffer { impl SelectionExt for Selection {
type View = Editor; fn display_range(&self, map: &DisplayMapSnapshot) -> Range<DisplayPoint> {
let start = self.start.to_display_point(map, Bias::Left);
fn file(&self) -> Option<&File> { let end = self.end.to_display_point(map, Bias::Left);
self.file() if self.reversed {
} end..start
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,
)
}
}
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 { } else {
"untitled".into() start..end
} }
} }
fn entry_id(&self, cx: &AppContext) -> Option<(usize, Arc<Path>)> { fn spanned_rows(
self.buffer.read(cx).file().map(|file| file.entry_id()) &self,
} include_end_if_at_line_start: bool,
map: &DisplayMapSnapshot,
) -> SpannedRows {
let display_start = self.start.to_display_point(map, Bias::Left);
let mut display_end = self.end.to_display_point(map, Bias::Right);
if !include_end_if_at_line_start
&& display_end.row() != map.max_point().row()
&& display_start.row() != display_end.row()
&& display_end.column() == 0
{
*display_end.row_mut() -= 1;
}
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self> let (display_start, buffer_start) = map.prev_row_boundary(display_start);
where let (display_end, buffer_end) = map.next_row_boundary(display_end);
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<()>>> { SpannedRows {
let save = self.buffer.update(cx, |b, cx| b.save(cx))?; buffer_rows: buffer_start.row..buffer_end.row + 1,
Ok(cx.spawn(|_, _| async move { display_rows: display_start.row()..display_end.row() + 1,
save.await?; }
Ok(())
}))
}
fn save_as(
&mut self,
worktree: &ModelHandle<Worktree>,
path: &Path,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.buffer
.update(cx, |b, cx| b.save_as(worktree, path, 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()
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{editor::Point, language::LanguageRegistry, settings, test::sample_text}; use crate::test::sample_text;
use buffer::History; use buffer::{History, Point};
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));
@ -2746,7 +2662,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| {
@ -2778,7 +2694,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| {
@ -2841,7 +2757,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)
}); });
@ -2909,7 +2825,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)
}); });
@ -2986,7 +2902,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)
}); });
@ -3044,7 +2960,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)
}); });
@ -3075,7 +2991,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(
@ -3218,7 +3134,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(
@ -3358,11 +3274,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};"
@ -3412,7 +3328,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)
}); });
@ -3459,7 +3375,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)
}); });
@ -3495,7 +3411,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)
}); });
@ -3524,7 +3440,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| {
@ -3548,7 +3464,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| {
@ -3565,7 +3481,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| {
@ -3592,7 +3508,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| {
@ -3618,7 +3534,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| {
@ -3716,7 +3632,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)
@ -3851,7 +3767,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);
@ -3864,7 +3780,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| {
@ -3910,7 +3826,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| {
@ -3978,7 +3894,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));
@ -4151,9 +4067,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 settings = cx.read(settings::test).1; let settings = cx.read(EditorSettings::test);
let languages = LanguageRegistry::new();
let lang = languages.select_language("z.rs"); 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 text = r#" let text = r#"
use mod1::mod2::{mod3, mod4}; use mod1::mod2::{mod3, mod4};
@ -4164,9 +4088,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, 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;
@ -4307,36 +4231,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)
} }
} }

View file

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

@ -0,0 +1,18 @@
[package]
name = "file_finder"
version = "0.1.0"
edition = "2018"
[dependencies]
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" }
project = { path = "../project" }
util = { path = "../util" }
theme = { path = "../theme" }
workspace = { path = "../workspace" }
postage = { version = "0.4.1", features = ["futures-traits"] }
[dev-dependencies]
serde_json = { version = "1.0.64", features = ["preserve_order"] }
workspace = { path = "../workspace", features = ["test-support"] }

View file

@ -1,10 +1,5 @@
use crate::{ use editor::{Editor, EditorSettings};
editor::{self, Editor}, use fuzzy::PathMatch;
settings::Settings,
util,
workspace::Workspace,
worktree::{match_paths, PathMatch},
};
use gpui::{ use gpui::{
action, action,
elements::*, elements::*,
@ -13,10 +8,11 @@ use gpui::{
menu::{SelectNext, SelectPrev}, menu::{SelectNext, SelectPrev},
Binding, Binding,
}, },
AppContext, Axis, Entity, MutableAppContext, RenderContext, Task, View, ViewContext, AppContext, Axis, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View,
ViewHandle, WeakViewHandle, ViewContext, ViewHandle, WeakViewHandle,
}; };
use postage::watch; use postage::watch;
use project::{Project, ProjectPath};
use std::{ use std::{
cmp, cmp,
path::Path, path::Path,
@ -25,11 +21,13 @@ use std::{
Arc, Arc,
}, },
}; };
use util::post_inc;
use workspace::{Settings, Workspace};
pub struct FileFinder { pub struct FileFinder {
handle: WeakViewHandle<Self>, handle: WeakViewHandle<Self>,
settings: watch::Receiver<Settings>, settings: watch::Receiver<Settings>,
workspace: WeakViewHandle<Workspace>, project: ModelHandle<Project>,
query_editor: ViewHandle<Editor>, query_editor: ViewHandle<Editor>,
search_count: usize, search_count: usize,
latest_search_id: usize, latest_search_id: usize,
@ -43,13 +41,7 @@ pub struct FileFinder {
action!(Toggle); action!(Toggle);
action!(Confirm); action!(Confirm);
action!(Select, Entry); action!(Select, ProjectPath);
#[derive(Clone)]
pub struct Entry {
worktree_id: usize,
path: Arc<Path>,
}
pub fn init(cx: &mut MutableAppContext) { pub fn init(cx: &mut MutableAppContext) {
cx.add_action(FileFinder::toggle); cx.add_action(FileFinder::toggle);
@ -66,7 +58,7 @@ pub fn init(cx: &mut MutableAppContext) {
} }
pub enum Event { pub enum Event {
Selected(usize, Arc<Path>), Selected(ProjectPath),
Dismissed, Dismissed,
} }
@ -202,8 +194,8 @@ impl FileFinder {
) )
.with_style(style.container); .with_style(style.container);
let action = Select(Entry { let action = Select(ProjectPath {
worktree_id: path_match.tree_id, worktree_id: path_match.worktree_id,
path: path_match.path.clone(), path: path_match.path.clone(),
}); });
EventHandler::new(container.boxed()) EventHandler::new(container.boxed())
@ -241,8 +233,8 @@ impl FileFinder {
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) { fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
workspace.toggle_modal(cx, |cx, workspace| { workspace.toggle_modal(cx, |cx, workspace| {
let handle = cx.handle(); let project = workspace.project().clone();
let finder = cx.add_view(|cx| Self::new(workspace.settings.clone(), handle, cx)); let finder = cx.add_view(|cx| Self::new(workspace.settings.clone(), project, cx));
cx.subscribe(&finder, Self::on_event).detach(); cx.subscribe(&finder, Self::on_event).detach();
finder finder
}); });
@ -255,9 +247,9 @@ impl FileFinder {
cx: &mut ViewContext<Workspace>, cx: &mut ViewContext<Workspace>,
) { ) {
match event { match event {
Event::Selected(tree_id, path) => { Event::Selected(project_path) => {
workspace workspace
.open_entry((*tree_id, path.clone()), cx) .open_entry(project_path.clone(), cx)
.map(|d| d.detach()); .map(|d| d.detach());
workspace.dismiss_modal(cx); workspace.dismiss_modal(cx);
} }
@ -269,17 +261,22 @@ impl FileFinder {
pub fn new( pub fn new(
settings: watch::Receiver<Settings>, settings: watch::Receiver<Settings>,
workspace: ViewHandle<Workspace>, project: ModelHandle<Project>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
cx.observe(&workspace, Self::workspace_updated).detach(); cx.observe(&project, Self::project_updated).detach();
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,
) )
@ -290,7 +287,7 @@ impl FileFinder {
Self { Self {
handle: cx.handle().downgrade(), handle: cx.handle().downgrade(),
settings, settings,
workspace: workspace.downgrade(), project,
query_editor, query_editor,
search_count: 0, search_count: 0,
latest_search_id: 0, latest_search_id: 0,
@ -303,7 +300,7 @@ impl FileFinder {
} }
} }
fn workspace_updated(&mut self, _: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) { fn project_updated(&mut self, _: ModelHandle<Project>, cx: &mut ViewContext<Self>) {
let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx)); let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx));
if let Some(task) = self.spawn_search(query, cx) { if let Some(task) = self.spawn_search(query, cx) {
task.detach(); task.detach();
@ -320,7 +317,7 @@ impl FileFinder {
editor::Event::Edited => { editor::Event::Edited => {
let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx)); let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx));
if query.is_empty() { if query.is_empty() {
self.latest_search_id = util::post_inc(&mut self.search_count); self.latest_search_id = post_inc(&mut self.search_count);
self.matches.clear(); self.matches.clear();
cx.notify(); cx.notify();
} else { } else {
@ -337,7 +334,7 @@ impl FileFinder {
fn selected_index(&self) -> usize { fn selected_index(&self) -> usize {
if let Some(selected) = self.selected.as_ref() { if let Some(selected) = self.selected.as_ref() {
for (ix, path_match) in self.matches.iter().enumerate() { for (ix, path_match) in self.matches.iter().enumerate() {
if (path_match.tree_id, path_match.path.as_ref()) if (path_match.worktree_id, path_match.path.as_ref())
== (selected.0, selected.1.as_ref()) == (selected.0, selected.1.as_ref())
{ {
return ix; return ix;
@ -352,7 +349,7 @@ impl FileFinder {
if selected_index > 0 { if selected_index > 0 {
selected_index -= 1; selected_index -= 1;
let mat = &self.matches[selected_index]; let mat = &self.matches[selected_index];
self.selected = Some((mat.tree_id, mat.path.clone())); self.selected = Some((mat.worktree_id, mat.path.clone()));
} }
self.list_state.scroll_to(selected_index); self.list_state.scroll_to(selected_index);
cx.notify(); cx.notify();
@ -363,7 +360,7 @@ impl FileFinder {
if selected_index + 1 < self.matches.len() { if selected_index + 1 < self.matches.len() {
selected_index += 1; selected_index += 1;
let mat = &self.matches[selected_index]; let mat = &self.matches[selected_index];
self.selected = Some((mat.tree_id, mat.path.clone())); self.selected = Some((mat.worktree_id, mat.path.clone()));
} }
self.list_state.scroll_to(selected_index); self.list_state.scroll_to(selected_index);
cx.notify(); cx.notify();
@ -371,40 +368,30 @@ impl FileFinder {
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) { fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
if let Some(m) = self.matches.get(self.selected_index()) { if let Some(m) = self.matches.get(self.selected_index()) {
cx.emit(Event::Selected(m.tree_id, m.path.clone())); cx.emit(Event::Selected(ProjectPath {
worktree_id: m.worktree_id,
path: m.path.clone(),
}));
} }
} }
fn select(&mut self, Select(entry): &Select, cx: &mut ViewContext<Self>) { fn select(&mut self, Select(project_path): &Select, cx: &mut ViewContext<Self>) {
cx.emit(Event::Selected(entry.worktree_id, entry.path.clone())); cx.emit(Event::Selected(project_path.clone()));
} }
#[must_use] #[must_use]
fn spawn_search(&mut self, query: String, cx: &mut ViewContext<Self>) -> Option<Task<()>> { fn spawn_search(&mut self, query: String, cx: &mut ViewContext<Self>) -> Option<Task<()>> {
let snapshots = self
.workspace
.upgrade(&cx)?
.read(cx)
.worktrees(cx)
.iter()
.map(|tree| tree.read(cx).snapshot())
.collect::<Vec<_>>();
let search_id = util::post_inc(&mut self.search_count); let search_id = util::post_inc(&mut self.search_count);
let background = cx.as_ref().background().clone();
self.cancel_flag.store(true, atomic::Ordering::Relaxed); self.cancel_flag.store(true, atomic::Ordering::Relaxed);
self.cancel_flag = Arc::new(AtomicBool::new(false)); self.cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag = self.cancel_flag.clone(); let cancel_flag = self.cancel_flag.clone();
let project = self.project.clone();
Some(cx.spawn(|this, mut cx| async move { Some(cx.spawn(|this, mut cx| async move {
let matches = match_paths( let matches = project
&snapshots, .read_with(&cx, |project, cx| {
&query, project.match_paths(&query, false, false, 100, cancel_flag.as_ref(), cx)
false, })
false, .await;
100,
cancel_flag.as_ref(),
background,
)
.await;
let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed); let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.update_matches((search_id, did_cancel, query, matches), cx) this.update_matches((search_id, did_cancel, query, matches), cx)
@ -435,19 +422,15 @@ impl FileFinder {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{ use editor::Insert;
editor::{self, Insert},
fs::FakeFs,
test::test_app_state,
workspace::Workspace,
};
use serde_json::json; use serde_json::json;
use std::path::PathBuf; use std::path::PathBuf;
use workspace::{Workspace, WorkspaceParams};
#[gpui::test] #[gpui::test]
async fn test_matching_paths(mut cx: gpui::TestAppContext) { async fn test_matching_paths(mut cx: gpui::TestAppContext) {
let app_state = cx.update(test_app_state); let params = cx.update(WorkspaceParams::test);
app_state params
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
@ -465,7 +448,7 @@ mod tests {
editor::init(cx); editor::init(cx);
}); });
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
workspace.add_worktree(Path::new("/root"), cx) workspace.add_worktree(Path::new("/root"), cx)
@ -509,7 +492,8 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_matching_cancellation(mut cx: gpui::TestAppContext) { async fn test_matching_cancellation(mut cx: gpui::TestAppContext) {
let fs = Arc::new(FakeFs::new()); let params = cx.update(WorkspaceParams::test);
let fs = params.fs.as_fake();
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -524,10 +508,7 @@ mod tests {
) )
.await; .await;
let mut app_state = cx.update(test_app_state); let (_, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
Arc::get_mut(&mut app_state).unwrap().fs = fs;
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
workspace.add_worktree("/dir".as_ref(), cx) workspace.add_worktree("/dir".as_ref(), cx)
@ -536,8 +517,13 @@ mod tests {
.unwrap(); .unwrap();
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx)) cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
.await; .await;
let (_, finder) = let (_, finder) = cx.add_window(|cx| {
cx.add_window(|cx| FileFinder::new(app_state.settings.clone(), workspace.clone(), cx)); FileFinder::new(
params.settings.clone(),
workspace.read(cx).project().clone(),
cx,
)
});
let query = "hi".to_string(); let query = "hi".to_string();
finder finder
@ -580,14 +566,14 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_single_file_worktrees(mut cx: gpui::TestAppContext) { async fn test_single_file_worktrees(mut cx: gpui::TestAppContext) {
let app_state = cx.update(test_app_state); let params = cx.update(WorkspaceParams::test);
app_state params
.fs .fs
.as_fake() .as_fake()
.insert_tree("/root", json!({ "the-parent-dir": { "the-file": "" } })) .insert_tree("/root", json!({ "the-parent-dir": { "the-file": "" } }))
.await; .await;
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
workspace.add_worktree(Path::new("/root/the-parent-dir/the-file"), cx) workspace.add_worktree(Path::new("/root/the-parent-dir/the-file"), cx)
@ -596,8 +582,13 @@ mod tests {
.unwrap(); .unwrap();
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx)) cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
.await; .await;
let (_, finder) = let (_, finder) = cx.add_window(|cx| {
cx.add_window(|cx| FileFinder::new(app_state.settings.clone(), workspace.clone(), cx)); FileFinder::new(
params.settings.clone(),
workspace.read(cx).project().clone(),
cx,
)
});
// Even though there is only one worktree, that worktree's filename // Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file. // is included in the matching, because the worktree is a single file.
@ -628,8 +619,8 @@ mod tests {
#[gpui::test(retries = 5)] #[gpui::test(retries = 5)]
async fn test_multiple_matches_with_same_relative_path(mut cx: gpui::TestAppContext) { async fn test_multiple_matches_with_same_relative_path(mut cx: gpui::TestAppContext) {
let app_state = cx.update(test_app_state); let params = cx.update(WorkspaceParams::test);
app_state params
.fs .fs
.as_fake() .as_fake()
.insert_tree( .insert_tree(
@ -641,7 +632,7 @@ mod tests {
) )
.await; .await;
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
@ -654,8 +645,13 @@ mod tests {
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx)) cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
.await; .await;
let (_, finder) = let (_, finder) = cx.add_window(|cx| {
cx.add_window(|cx| FileFinder::new(app_state.settings.clone(), workspace.clone(), cx)); FileFinder::new(
params.settings.clone(),
workspace.read(cx).project().clone(),
cx,
)
});
// Run a search that matches two files with the same relative path. // Run a search that matches two files with the same relative path.
finder finder

8
crates/fuzzy/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "fuzzy"
version = "0.1.0"
edition = "2018"
[dependencies]
gpui = { path = "../gpui" }
util = { path = "../util" }

View file

@ -1,13 +1,9 @@
mod char_bag; mod char_bag;
use crate::{
util,
worktree::{EntryKind, Snapshot},
};
use gpui::executor; use gpui::executor;
use std::{ use std::{
borrow::Cow, borrow::Cow,
cmp::{max, min, Ordering}, cmp::{self, Ordering},
path::Path, path::Path,
sync::atomic::{self, AtomicBool}, sync::atomic::{self, AtomicBool},
sync::Arc, sync::Arc,
@ -19,7 +15,7 @@ const BASE_DISTANCE_PENALTY: f64 = 0.6;
const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05; const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05;
const MIN_DISTANCE_PENALTY: f64 = 0.2; const MIN_DISTANCE_PENALTY: f64 = 0.2;
struct Matcher<'a> { pub struct Matcher<'a> {
query: &'a [char], query: &'a [char],
lowercase_query: &'a [char], lowercase_query: &'a [char],
query_char_bag: CharBag, query_char_bag: CharBag,
@ -52,7 +48,7 @@ pub struct PathMatchCandidate<'a> {
pub struct PathMatch { pub struct PathMatch {
pub score: f64, pub score: f64,
pub positions: Vec<usize>, pub positions: Vec<usize>,
pub tree_id: usize, pub worktree_id: usize,
pub path: Arc<Path>, pub path: Arc<Path>,
pub path_prefix: Arc<str>, pub path_prefix: Arc<str>,
} }
@ -63,6 +59,14 @@ pub struct StringMatchCandidate {
pub char_bag: CharBag, pub char_bag: CharBag,
} }
pub trait PathMatchCandidateSet<'a>: Send + Sync {
type Candidates: Iterator<Item = PathMatchCandidate<'a>>;
fn id(&self) -> usize;
fn len(&self) -> usize;
fn prefix(&self) -> Arc<str>;
fn candidates(&'a self, start: usize) -> Self::Candidates;
}
impl Match for PathMatch { impl Match for PathMatch {
fn score(&self) -> f64 { fn score(&self) -> f64 {
self.score self.score
@ -152,7 +156,7 @@ impl Ord for PathMatch {
self.score self.score
.partial_cmp(&other.score) .partial_cmp(&other.score)
.unwrap_or(Ordering::Equal) .unwrap_or(Ordering::Equal)
.then_with(|| self.tree_id.cmp(&other.tree_id)) .then_with(|| self.worktree_id.cmp(&other.worktree_id))
.then_with(|| Arc::as_ptr(&self.path).cmp(&Arc::as_ptr(&other.path))) .then_with(|| Arc::as_ptr(&self.path).cmp(&Arc::as_ptr(&other.path)))
} }
} }
@ -213,20 +217,15 @@ pub async fn match_strings(
results results
} }
pub async fn match_paths( pub async fn match_paths<'a, Set: PathMatchCandidateSet<'a>>(
snapshots: &[Snapshot], candidate_sets: &'a [Set],
query: &str, query: &str,
include_ignored: bool,
smart_case: bool, smart_case: bool,
max_results: usize, max_results: usize,
cancel_flag: &AtomicBool, cancel_flag: &AtomicBool,
background: Arc<executor::Background>, background: Arc<executor::Background>,
) -> Vec<PathMatch> { ) -> Vec<PathMatch> {
let path_count: usize = if include_ignored { let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum();
snapshots.iter().map(Snapshot::file_count).sum()
} else {
snapshots.iter().map(Snapshot::visible_file_count).sum()
};
if path_count == 0 { if path_count == 0 {
return Vec::new(); return Vec::new();
} }
@ -259,43 +258,18 @@ pub async fn match_paths(
); );
let mut tree_start = 0; let mut tree_start = 0;
for snapshot in snapshots { for candidate_set in candidate_sets {
let tree_end = if include_ignored { let tree_end = tree_start + candidate_set.len();
tree_start + snapshot.file_count()
} else {
tree_start + snapshot.visible_file_count()
};
if tree_start < segment_end && segment_start < tree_end { if tree_start < segment_end && segment_start < tree_end {
let path_prefix: Arc<str> = let start = cmp::max(tree_start, segment_start) - tree_start;
if snapshot.root_entry().map_or(false, |e| e.is_file()) { let end = cmp::min(tree_end, segment_end) - tree_start;
snapshot.root_name().into() let candidates = candidate_set.candidates(start).take(end - start);
} else if snapshots.len() > 1 {
format!("{}/", snapshot.root_name()).into()
} else {
"".into()
};
let start = max(tree_start, segment_start) - tree_start;
let end = min(tree_end, segment_end) - tree_start;
let paths = snapshot
.files(include_ignored, start)
.take(end - start)
.map(|entry| {
if let EntryKind::File(char_bag) = entry.kind {
PathMatchCandidate {
path: &entry.path,
char_bag,
}
} else {
unreachable!()
}
});
matcher.match_paths( matcher.match_paths(
snapshot.id(), candidate_set.id(),
path_prefix, candidate_set.prefix(),
paths, candidates,
results, results,
&cancel_flag, &cancel_flag,
); );
@ -322,7 +296,7 @@ pub async fn match_paths(
} }
impl<'a> Matcher<'a> { impl<'a> Matcher<'a> {
fn new( pub fn new(
query: &'a [char], query: &'a [char],
lowercase_query: &'a [char], lowercase_query: &'a [char],
query_char_bag: CharBag, query_char_bag: CharBag,
@ -343,7 +317,7 @@ impl<'a> Matcher<'a> {
} }
} }
fn match_strings( pub fn match_strings(
&mut self, &mut self,
candidates: &[StringMatchCandidate], candidates: &[StringMatchCandidate],
results: &mut Vec<StringMatch>, results: &mut Vec<StringMatch>,
@ -363,11 +337,11 @@ impl<'a> Matcher<'a> {
) )
} }
fn match_paths( pub fn match_paths<'c: 'a>(
&mut self, &mut self,
tree_id: usize, tree_id: usize,
path_prefix: Arc<str>, path_prefix: Arc<str>,
path_entries: impl Iterator<Item = PathMatchCandidate<'a>>, path_entries: impl Iterator<Item = PathMatchCandidate<'c>>,
results: &mut Vec<PathMatch>, results: &mut Vec<PathMatch>,
cancel_flag: &AtomicBool, cancel_flag: &AtomicBool,
) { ) {
@ -384,7 +358,7 @@ impl<'a> Matcher<'a> {
cancel_flag, cancel_flag,
|candidate, score| PathMatch { |candidate, score| PathMatch {
score, score,
tree_id, worktree_id: tree_id,
positions: Vec::new(), positions: Vec::new(),
path: candidate.path.clone(), path: candidate.path.clone(),
path_prefix: path_prefix.clone(), path_prefix: path_prefix.clone(),

View file

@ -5,15 +5,16 @@ name = "gpui"
version = "0.1.0" version = "0.1.0"
[features] [features]
test-support = [] test-support = ["env_logger"]
[dependencies] [dependencies]
arrayvec = "0.7.1" gpui_macros = { path = "../gpui_macros" }
sum_tree = { path = "../sum_tree" }
async-task = "4.0.3" async-task = "4.0.3"
backtrace = "0.3" backtrace = "0.3"
ctor = "0.1" ctor = "0.1"
env_logger = { version = "0.8", optional = true }
etagere = "0.2" etagere = "0.2"
gpui_macros = { path = "../gpui_macros" }
image = "0.23" image = "0.23"
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = "0.4" log = "0.4"

View file

@ -18,7 +18,7 @@ pub struct Label {
highlight_indices: Vec<usize>, highlight_indices: Vec<usize>,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize, Default)]
pub struct LabelStyle { pub struct LabelStyle {
pub text: TextStyle, pub text: TextStyle,
pub highlight_text: Option<TextStyle>, pub highlight_text: Option<TextStyle>,

View file

@ -4,11 +4,11 @@ use crate::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json::json, json::json,
sum_tree::{self, Bias, SumTree},
DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext, DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint, SizeConstraint,
}; };
use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc}; use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
pub struct List { pub struct List {
state: ListState, state: ListState,

View file

@ -167,6 +167,32 @@ impl From<TextStyle> for HighlightStyle {
} }
} }
impl Default for TextStyle {
fn default() -> Self {
FONT_CACHE.with(|font_cache| {
let font_cache = font_cache.borrow();
let font_cache = font_cache
.as_ref()
.expect("TextStyle::default can only be called within a call to with_font_cache");
let font_family_name = Arc::from("Courier");
let font_family_id = font_cache.load_family(&[&font_family_name]).unwrap();
let font_id = font_cache
.select_font(font_family_id, &Default::default())
.unwrap();
Self {
color: Default::default(),
font_family_name,
font_family_id,
font_id,
font_size: 14.,
font_properties: Default::default(),
underline: Default::default(),
}
})
}
}
impl HighlightStyle { impl HighlightStyle {
fn from_json(json: HighlightStyleJson) -> Self { fn from_json(json: HighlightStyleJson) -> Self {
let font_properties = properties_from_json(json.weight, json.italic); let font_properties = properties_from_json(json.weight, json.italic);

View file

@ -1,9 +1,8 @@
mod app; mod app;
pub use app::*; pub use app::*;
mod assets; mod assets;
pub mod sum_tree; #[cfg(any(test, feature = "test-support"))]
#[cfg(test)] pub mod test;
mod test;
pub use assets::*; pub use assets::*;
pub mod elements; pub mod elements;
pub mod font_cache; pub mod font_cache;

View file

@ -4,7 +4,7 @@ use crate::geometry::{
}; };
use etagere::BucketedAtlasAllocator; use etagere::BucketedAtlasAllocator;
use foreign_types::ForeignType; use foreign_types::ForeignType;
use metal::{self, Device, TextureDescriptor}; use metal::{Device, TextureDescriptor};
use objc::{msg_send, sel, sel_impl}; use objc::{msg_send, sel, sel_impl};
pub struct AtlasAllocator { pub struct AtlasAllocator {

Some files were not shown because too many files have changed in this diff Show more