Merge pull request #202 from zed-industries/crates
Break project into crates to improve incremental compilation time
This commit is contained in:
commit
cefb90269e
298 changed files with 199721 additions and 3228 deletions
306
Cargo.lock
generated
306
Cargo.lock
generated
|
@ -742,6 +742,30 @@ dependencies = [
|
|||
"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]]
|
||||
name = "build_const"
|
||||
version = "0.2.2"
|
||||
|
@ -902,6 +926,20 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chat_panel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"client",
|
||||
"editor",
|
||||
"gpui",
|
||||
"postage",
|
||||
"theme",
|
||||
"time 0.3.2",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
|
@ -998,6 +1036,39 @@ dependencies = [
|
|||
"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]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
|
@ -1544,6 +1615,30 @@ dependencies = [
|
|||
"getrandom 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "editor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"buffer",
|
||||
"clock",
|
||||
"gpui",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"rand 0.8.3",
|
||||
"serde 1.0.125",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"theme",
|
||||
"tree-sitter",
|
||||
"tree-sitter-rust",
|
||||
"unindent",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
|
@ -1762,6 +1857,21 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "file_finder"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"editor",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"postage",
|
||||
"project",
|
||||
"serde_json 1.0.64",
|
||||
"theme",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.14"
|
||||
|
@ -2040,6 +2150,14 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuzzy"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.6.23"
|
||||
|
@ -2175,7 +2293,6 @@ name = "gpui"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arrayvec 0.7.1",
|
||||
"async-task",
|
||||
"backtrace",
|
||||
"bindgen",
|
||||
|
@ -2212,6 +2329,7 @@ dependencies = [
|
|||
"simplelog",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"time 0.3.2",
|
||||
"tiny-skia",
|
||||
"tree-sitter",
|
||||
|
@ -3324,6 +3442,17 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "people_panel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"client",
|
||||
"gpui",
|
||||
"postage",
|
||||
"theme",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
|
@ -3616,6 +3745,48 @@ dependencies = [
|
|||
"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]]
|
||||
name = "prost"
|
||||
version = "0.8.0"
|
||||
|
@ -4052,6 +4223,28 @@ dependencies = [
|
|||
"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]]
|
||||
name = "rsa"
|
||||
version = "0.4.0"
|
||||
|
@ -4940,6 +5133,14 @@ version = "2.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
|
||||
|
||||
[[package]]
|
||||
name = "sum_tree"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.1",
|
||||
"rand 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "surf"
|
||||
version = "2.2.0"
|
||||
|
@ -5112,6 +5313,35 @@ dependencies = [
|
|||
"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]]
|
||||
name = "thiserror"
|
||||
version = "1.0.29"
|
||||
|
@ -5582,6 +5812,18 @@ version = "0.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "util"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
"log",
|
||||
"serde_json 1.0.64",
|
||||
"surf",
|
||||
"tempdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.5.1"
|
||||
|
@ -5854,6 +6096,24 @@ dependencies = [
|
|||
"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]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
|
@ -5892,18 +6152,24 @@ name = "zed"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arrayvec 0.7.1",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"async-tungstenite",
|
||||
"buffer",
|
||||
"cargo-bundle",
|
||||
"chat_panel",
|
||||
"client",
|
||||
"clock",
|
||||
"crossbeam-channel",
|
||||
"ctor",
|
||||
"dirs 3.0.1",
|
||||
"easy-parallel",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"file_finder",
|
||||
"fsevent",
|
||||
"futures",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"http-auth-basic",
|
||||
"ignore",
|
||||
|
@ -5915,20 +6181,25 @@ dependencies = [
|
|||
"log-panics",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"people_panel",
|
||||
"postage",
|
||||
"project",
|
||||
"project_panel",
|
||||
"rand 0.8.3",
|
||||
"rpc",
|
||||
"rsa",
|
||||
"rust-embed",
|
||||
"seahash",
|
||||
"serde 1.0.125",
|
||||
"serde_json 1.0.64",
|
||||
"serde_path_to_error",
|
||||
"similar",
|
||||
"simplelog",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"surf",
|
||||
"tempdir",
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"thiserror",
|
||||
"time 0.3.2",
|
||||
"tiny_http",
|
||||
|
@ -5937,7 +6208,8 @@ dependencies = [
|
|||
"tree-sitter-rust",
|
||||
"unindent",
|
||||
"url",
|
||||
"zrpc",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5966,6 +6238,7 @@ dependencies = [
|
|||
"parking_lot",
|
||||
"postage",
|
||||
"rand 0.8.3",
|
||||
"rpc",
|
||||
"rust-embed",
|
||||
"scrypt",
|
||||
"serde 1.0.125",
|
||||
|
@ -5978,7 +6251,6 @@ dependencies = [
|
|||
"time 0.2.25",
|
||||
"toml 0.5.8",
|
||||
"zed",
|
||||
"zrpc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6002,28 +6274,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "zstd"
|
||||
version = "0.9.0+zstd.1.5.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[workspace]
|
||||
members = ["fsevent", "gpui", "gpui_macros", "server", "zed", "zrpc"]
|
||||
default-members = ["zed"]
|
||||
members = ["crates/*"]
|
||||
default-members = ["crates/zed"]
|
||||
|
||||
[patch.crates-io]
|
||||
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
|
||||
|
|
30
crates/buffer/Cargo.toml
Normal file
30
crates/buffer/Cargo.toml
Normal 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"
|
|
@ -1,13 +1,13 @@
|
|||
use super::{Buffer, Content};
|
||||
use crate::{time, util::Bias};
|
||||
use anyhow::Result;
|
||||
use std::{cmp::Ordering, ops::Range};
|
||||
use sum_tree::Bias;
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
|
||||
pub struct Anchor {
|
||||
pub offset: usize,
|
||||
pub bias: Bias,
|
||||
pub version: time::Global,
|
||||
pub version: clock::Global,
|
||||
}
|
||||
|
||||
impl Anchor {
|
|
@ -1,5 +1,6 @@
|
|||
use super::SyntaxTheme;
|
||||
use gpui::fonts::HighlightStyle;
|
||||
use std::sync::Arc;
|
||||
use theme::SyntaxTheme;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self(Arc::new([]))
|
||||
|
@ -89,8 +104,8 @@ mod tests {
|
|||
];
|
||||
|
||||
let map = HighlightMap::new(capture_names, &theme);
|
||||
assert_eq!(theme.highlight_name(map.get(0)), Some("function"));
|
||||
assert_eq!(theme.highlight_name(map.get(1)), Some("function.async"));
|
||||
assert_eq!(theme.highlight_name(map.get(2)), Some("variable.builtin"));
|
||||
assert_eq!(map.get(0).name(&theme), Some("function"));
|
||||
assert_eq!(map.get(1).name(&theme), Some("function.async"));
|
||||
assert_eq!(map.get(2).name(&theme), Some("variable.builtin"));
|
||||
}
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
use crate::{settings::HighlightMap, theme::SyntaxTheme};
|
||||
use crate::{HighlightMap};
|
||||
use parking_lot::Mutex;
|
||||
use rust_embed::RustEmbed;
|
||||
use serde::Deserialize;
|
||||
use std::{path::Path, str, sync::Arc};
|
||||
use tree_sitter::{Language as Grammar, Query};
|
||||
pub use tree_sitter::{Parser, Tree};
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "languages"]
|
||||
pub struct LanguageDir;
|
||||
use theme::SyntaxTheme;
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct LanguageConfig {
|
||||
|
@ -30,40 +26,18 @@ pub struct Language {
|
|||
pub highlight_map: Mutex<HighlightMap>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LanguageRegistry {
|
||||
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 {
|
||||
pub fn new() -> Self {
|
||||
let grammar = tree_sitter_rust::language();
|
||||
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::default()
|
||||
}
|
||||
|
||||
Self {
|
||||
languages: vec![Arc::new(rust_language)],
|
||||
}
|
||||
pub fn add(&mut self, language: Arc<Language>) {
|
||||
self.languages.push(language);
|
||||
}
|
||||
|
||||
pub fn set_theme(&self, theme: &SyntaxTheme) {
|
||||
|
@ -85,19 +59,19 @@ impl LanguageRegistry {
|
|||
.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 {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load diff
|
@ -1,13 +1,12 @@
|
|||
use super::Operation;
|
||||
use crate::time;
|
||||
use gpui::sum_tree::{Cursor, Dimension, Edit, Item, KeyedItem, SumTree, Summary};
|
||||
use std::{fmt::Debug, ops::Add};
|
||||
use sum_tree::{Cursor, Dimension, Edit, Item, KeyedItem, SumTree, Summary};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OperationQueue(SumTree<Operation>);
|
||||
|
||||
#[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)]
|
||||
pub struct OperationSummary {
|
||||
|
@ -16,7 +15,7 @@ pub struct OperationSummary {
|
|||
}
|
||||
|
||||
impl OperationKey {
|
||||
pub fn new(timestamp: time::Lamport) -> Self {
|
||||
pub fn new(timestamp: clock::Lamport) -> Self {
|
||||
Self(timestamp)
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +101,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_len() {
|
||||
let mut clock = time::Lamport::new(0);
|
||||
let mut clock = clock::Lamport::new(0);
|
||||
|
||||
let mut queue = OperationQueue::new();
|
||||
assert_eq!(queue.len(), 0);
|
||||
|
@ -124,5 +123,5 @@ mod tests {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct TestOperation(time::Lamport);
|
||||
struct TestOperation(clock::Lamport);
|
||||
}
|
28
crates/buffer/src/random_char_iter.rs
Normal file
28
crates/buffer/src/random_char_iter.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
use super::Point;
|
||||
use crate::util::Bias;
|
||||
use arrayvec::ArrayString;
|
||||
use gpui::sum_tree::{self, SumTree};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp, ops::Range, str};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
|
||||
#[cfg(test)]
|
||||
const CHUNK_BASE: usize = 6;
|
||||
|
@ -520,7 +519,7 @@ fn find_split_ix(text: &str) -> usize {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::util::RandomCharIter;
|
||||
use crate::random_char_iter::RandomCharIter;
|
||||
use rand::prelude::*;
|
||||
use std::env;
|
||||
use Bias::{Left, Right};
|
|
@ -1,13 +1,7 @@
|
|||
use crate::{
|
||||
editor::{
|
||||
buffer::{Anchor, Buffer, Point, ToOffset as _, ToPoint as _},
|
||||
Bias, DisplayMapSnapshot, DisplayPoint,
|
||||
},
|
||||
time,
|
||||
};
|
||||
use crate::{Anchor, Buffer, Point, ToOffset as _, ToPoint as _};
|
||||
use std::{cmp::Ordering, mem, ops::Range};
|
||||
|
||||
pub type SelectionSetId = time::Lamport;
|
||||
pub type SelectionSetId = clock::Lamport;
|
||||
pub type SelectionsVersion = usize;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
|
@ -17,11 +11,6 @@ pub enum SelectionGoal {
|
|||
ColumnRange { start: u32, end: u32 },
|
||||
}
|
||||
|
||||
pub struct SpannedRows {
|
||||
pub buffer_rows: Range<u32>,
|
||||
pub display_rows: Range<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Selection {
|
||||
pub id: usize,
|
||||
|
@ -83,38 +72,4 @@ impl Selection {
|
|||
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,
|
||||
}
|
||||
}
|
||||
}
|
14
crates/chat_panel/Cargo.toml
Normal file
14
crates/chat_panel/Cargo.toml
Normal 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"
|
|
@ -1,13 +1,8 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
use client::{
|
||||
channel::{Channel, ChannelEvent, ChannelList, ChannelMessage},
|
||||
editor::Editor,
|
||||
rpc::{self, Client},
|
||||
theme,
|
||||
util::{ResultExt, TryFutureExt},
|
||||
Settings,
|
||||
Client,
|
||||
};
|
||||
use editor::{Editor, EditorSettings};
|
||||
use gpui::{
|
||||
action,
|
||||
elements::*,
|
||||
|
@ -18,7 +13,10 @@ use gpui::{
|
|||
ViewContext, ViewHandle,
|
||||
};
|
||||
use postage::{prelude::Stream, watch};
|
||||
use std::sync::Arc;
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use workspace::Settings;
|
||||
|
||||
const MESSAGE_LOADING_THRESHOLD: usize = 50;
|
||||
|
||||
|
@ -56,10 +54,15 @@ impl ChatPanel {
|
|||
let input_editor = cx.add_view(|cx| {
|
||||
Editor::auto_height(
|
||||
4,
|
||||
settings.clone(),
|
||||
{
|
||||
let settings = settings.clone();
|
||||
move |_| settings.borrow().theme.chat_panel.input_editor.as_editor()
|
||||
move |_| {
|
||||
let settings = settings.borrow();
|
||||
EditorSettings {
|
||||
tab_size: settings.tab_size,
|
||||
style: settings.theme.chat_panel.input_editor.as_editor(),
|
||||
}
|
||||
}
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|
@ -406,7 +409,10 @@ impl View for ChatPanel {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
28
crates/client/Cargo.toml
Normal file
28
crates/client/Cargo.toml
Normal 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"
|
|
@ -1,11 +1,10 @@
|
|||
use crate::{
|
||||
rpc::{self, Client},
|
||||
use super::{
|
||||
proto,
|
||||
user::{User, UserStore},
|
||||
util::{post_inc, TryFutureExt},
|
||||
Client, Status, Subscription, TypedEnvelope,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use gpui::{
|
||||
sum_tree::{self, Bias, SumTree},
|
||||
AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, WeakModelHandle,
|
||||
};
|
||||
use postage::prelude::Stream;
|
||||
|
@ -16,16 +15,14 @@ use std::{
|
|||
ops::Range,
|
||||
sync::Arc,
|
||||
};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
use time::OffsetDateTime;
|
||||
use zrpc::{
|
||||
proto::{self, ChannelMessageSent},
|
||||
TypedEnvelope,
|
||||
};
|
||||
use util::{post_inc, TryFutureExt};
|
||||
|
||||
pub struct ChannelList {
|
||||
available_channels: Option<Vec<ChannelDetails>>,
|
||||
channels: HashMap<u64, WeakModelHandle<Channel>>,
|
||||
rpc: Arc<Client>,
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
_task: Task<Option<()>>,
|
||||
}
|
||||
|
@ -44,7 +41,7 @@ pub struct Channel {
|
|||
user_store: ModelHandle<UserStore>,
|
||||
rpc: Arc<Client>,
|
||||
rng: StdRng,
|
||||
_subscription: rpc::Subscription,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -88,7 +85,7 @@ impl Entity for ChannelList {
|
|||
impl ChannelList {
|
||||
pub fn new(
|
||||
user_store: ModelHandle<UserStore>,
|
||||
rpc: Arc<rpc::Client>,
|
||||
rpc: Arc<Client>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let _task = cx.spawn_weak(|this, mut cx| {
|
||||
|
@ -97,7 +94,7 @@ impl ChannelList {
|
|||
let mut status = rpc.status();
|
||||
while let Some((status, this)) = status.recv().await.zip(this.upgrade(&cx)) {
|
||||
match status {
|
||||
rpc::Status::Connected { .. } => {
|
||||
Status::Connected { .. } => {
|
||||
let response = rpc
|
||||
.request(proto::GetChannels {})
|
||||
.await
|
||||
|
@ -121,7 +118,7 @@ impl ChannelList {
|
|||
cx.notify();
|
||||
});
|
||||
}
|
||||
rpc::Status::SignedOut { .. } => {
|
||||
Status::SignedOut { .. } => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.available_channels = None;
|
||||
this.channels.clear();
|
||||
|
@ -140,7 +137,7 @@ impl ChannelList {
|
|||
available_channels: None,
|
||||
channels: Default::default(),
|
||||
user_store,
|
||||
rpc,
|
||||
client: rpc,
|
||||
_task,
|
||||
}
|
||||
}
|
||||
|
@ -160,8 +157,9 @@ impl ChannelList {
|
|||
|
||||
let channels = self.available_channels.as_ref()?;
|
||||
let details = channels.iter().find(|details| details.id == id)?.clone();
|
||||
let channel =
|
||||
cx.add_model(|cx| Channel::new(details, self.user_store.clone(), self.rpc.clone(), cx));
|
||||
let channel = cx.add_model(|cx| {
|
||||
Channel::new(details, self.user_store.clone(), self.client.clone(), cx)
|
||||
});
|
||||
self.channels.insert(id, channel.downgrade());
|
||||
Some(channel)
|
||||
}
|
||||
|
@ -406,8 +404,8 @@ impl Channel {
|
|||
|
||||
fn handle_message_sent(
|
||||
&mut self,
|
||||
message: TypedEnvelope<ChannelMessageSent>,
|
||||
_: Arc<rpc::Client>,
|
||||
message: TypedEnvelope<proto::ChannelMessageSent>,
|
||||
_: Arc<Client>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
let user_store = self.user_store.clone();
|
|
@ -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 async_recursion::async_recursion;
|
||||
use async_tungstenite::tungstenite::{
|
||||
error::Error as WebsocketError,
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
use gpui::{AsyncAppContext, Entity, ModelContext, Task};
|
||||
use gpui::{action, AsyncAppContext, Entity, ModelContext, MutableAppContext, Task};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use postage::{prelude::Stream, watch};
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::HashMap,
|
||||
|
@ -21,11 +28,11 @@ use std::{
|
|||
};
|
||||
use surf::Url;
|
||||
use thiserror::Error;
|
||||
pub use zrpc::{proto, ConnectionId, PeerId, TypedEnvelope};
|
||||
use zrpc::{
|
||||
proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage},
|
||||
Connection, Peer, Receipt,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub use channel::*;
|
||||
pub use rpc::*;
|
||||
pub use user::*;
|
||||
|
||||
lazy_static! {
|
||||
static ref ZED_SERVER_URL: String =
|
||||
|
@ -35,6 +42,16 @@ lazy_static! {
|
|||
.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 {
|
||||
peer: Arc<Peer>,
|
||||
state: RwLock<ClientState>,
|
||||
|
@ -503,7 +520,7 @@ impl Client {
|
|||
"Authorization",
|
||||
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 {
|
||||
if let Some(host) = ZED_SERVER_URL.strip_prefix("https://") {
|
||||
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
|
||||
// any other app running on the user's device.
|
||||
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 =
|
||||
String::try_from(public_key).expect("failed to serialize public key for auth");
|
||||
|
188
crates/client/src/test.rs
Normal file
188
crates/client/src/test.rs
Normal 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) })
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
use crate::{
|
||||
use super::{
|
||||
http::{HttpClient, Method, Request, Url},
|
||||
rpc::{Client, Status},
|
||||
util::TryFutureExt,
|
||||
proto, Client, Status, TypedEnvelope,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use futures::future;
|
||||
|
@ -11,7 +10,7 @@ use std::{
|
|||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
use zrpc::{proto, TypedEnvelope};
|
||||
use util::TryFutureExt as _;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct User {
|
||||
|
@ -208,7 +207,7 @@ impl User {
|
|||
User {
|
||||
id: message.id,
|
||||
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)
|
||||
.await
|
||||
.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
|
||||
.body_bytes()
|
||||
.await
|
8
crates/clock/Cargo.toml
Normal file
8
crates/clock/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "clock"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
rpc = { path = "../rpc" }
|
|
@ -61,8 +61,8 @@ impl<'a> AddAssign<&'a Local> for Local {
|
|||
#[derive(Clone, Default, Hash, Eq, PartialEq)]
|
||||
pub struct Global(SmallVec<[Local; 3]>);
|
||||
|
||||
impl From<Vec<zrpc::proto::VectorClockEntry>> for Global {
|
||||
fn from(message: Vec<zrpc::proto::VectorClockEntry>) -> Self {
|
||||
impl From<Vec<rpc::proto::VectorClockEntry>> for Global {
|
||||
fn from(message: Vec<rpc::proto::VectorClockEntry>) -> Self {
|
||||
let mut version = Self::new();
|
||||
for entry in message {
|
||||
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 {
|
||||
version
|
||||
.iter()
|
||||
.map(|entry| zrpc::proto::VectorClockEntry {
|
||||
.map(|entry| rpc::proto::VectorClockEntry {
|
||||
replica_id: entry.replica_id as u32,
|
||||
timestamp: entry.value,
|
||||
})
|
31
crates/editor/Cargo.toml
Normal file
31
crates/editor/Cargo.toml
Normal 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"
|
|
@ -2,14 +2,19 @@ mod fold_map;
|
|||
mod tab_map;
|
||||
mod wrap_map;
|
||||
|
||||
use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint};
|
||||
use fold_map::FoldMap;
|
||||
use buffer::{Anchor, Buffer, Point, ToOffset, ToPoint};
|
||||
use fold_map::{FoldMap, ToFoldPoint as _};
|
||||
use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
|
||||
use std::ops::Range;
|
||||
use sum_tree::Bias;
|
||||
use tab_map::TabMap;
|
||||
use wrap_map::WrapMap;
|
||||
pub use wrap_map::{BufferRows, HighlightedChunks};
|
||||
|
||||
pub trait ToDisplayPoint {
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint;
|
||||
}
|
||||
|
||||
pub struct DisplayMap {
|
||||
buffer: ModelHandle<Buffer>,
|
||||
fold_map: FoldMap,
|
||||
|
@ -333,8 +338,8 @@ impl DisplayPoint {
|
|||
}
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn to_display_point(self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
|
||||
impl ToDisplayPoint for Point {
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
|
||||
let fold_point = self.to_fold_point(&map.folds_snapshot, bias);
|
||||
let tab_point = map.tabs_snapshot.to_tab_point(fold_point);
|
||||
let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point);
|
||||
|
@ -342,8 +347,8 @@ impl Point {
|
|||
}
|
||||
}
|
||||
|
||||
impl Anchor {
|
||||
pub fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
|
||||
impl ToDisplayPoint for Anchor {
|
||||
fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint {
|
||||
self.to_point(&map.buffer_snapshot)
|
||||
.to_display_point(map, bias)
|
||||
}
|
||||
|
@ -352,17 +357,12 @@ impl Anchor {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
editor::movement,
|
||||
language::{Language, LanguageConfig},
|
||||
test::*,
|
||||
theme::SyntaxTheme,
|
||||
util::RandomCharIter,
|
||||
};
|
||||
use buffer::{History, SelectionGoal};
|
||||
use crate::{movement, test::*};
|
||||
use buffer::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal};
|
||||
use gpui::{color::Color, MutableAppContext};
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
use std::{env, sync::Arc};
|
||||
use theme::SyntaxTheme;
|
||||
use Bias::*;
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
|
@ -977,7 +977,7 @@ mod tests {
|
|||
let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
|
||||
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 style_name == *last_style_name {
|
||||
last_chunk.push_str(chunk);
|
|
@ -1,12 +1,5 @@
|
|||
use super::{
|
||||
buffer::{AnchorRangeExt, TextSummary},
|
||||
Anchor, Buffer, Point, ToOffset,
|
||||
};
|
||||
use crate::{editor::buffer, settings::HighlightId, time, util::Bias};
|
||||
use gpui::{
|
||||
sum_tree::{self, Cursor, FilterCursor, SumTree},
|
||||
AppContext, ModelHandle,
|
||||
};
|
||||
use buffer::{Anchor, Buffer, Point, ToOffset, AnchorRangeExt, HighlightId, TextSummary};
|
||||
use gpui::{AppContext, ModelHandle};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
|
@ -14,6 +7,11 @@ use std::{
|
|||
ops::Range,
|
||||
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)]
|
||||
pub struct FoldPoint(pub super::Point);
|
||||
|
@ -75,8 +73,8 @@ impl FoldPoint {
|
|||
}
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn to_fold_point(&self, snapshot: &Snapshot, bias: Bias) -> FoldPoint {
|
||||
impl ToFoldPoint for Point {
|
||||
fn to_fold_point(&self, snapshot: &Snapshot, bias: Bias) -> FoldPoint {
|
||||
let mut cursor = snapshot.transforms.cursor::<(Point, FoldPoint)>();
|
||||
cursor.seek(self, Bias::Right, &());
|
||||
if cursor.item().map_or(false, |t| t.is_fold()) {
|
||||
|
@ -202,7 +200,7 @@ pub struct FoldMap {
|
|||
|
||||
#[derive(Clone)]
|
||||
struct SyncState {
|
||||
version: time::Global,
|
||||
version: clock::Global,
|
||||
parse_count: usize,
|
||||
}
|
||||
|
||||
|
@ -546,6 +544,7 @@ impl Snapshot {
|
|||
summary
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn len(&self) -> FoldOffset {
|
||||
FoldOffset(self.transforms.summary().output.bytes)
|
||||
}
|
||||
|
@ -1125,7 +1124,8 @@ impl FoldEdit {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{editor::ToPoint, test::sample_text, util::RandomCharIter};
|
||||
use crate::{test::sample_text, ToPoint};
|
||||
use buffer::RandomCharIter;
|
||||
use rand::prelude::*;
|
||||
use std::{env, mem};
|
||||
use Bias::{Left, Right};
|
|
@ -1,8 +1,8 @@
|
|||
use parking_lot::Mutex;
|
||||
|
||||
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 sum_tree::Bias;
|
||||
|
||||
pub struct TabMap(Mutex<Snapshot>);
|
||||
|
|
@ -2,16 +2,12 @@ use super::{
|
|||
fold_map,
|
||||
tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary},
|
||||
};
|
||||
use crate::{editor::Point, settings::HighlightId, util::Bias};
|
||||
use gpui::{
|
||||
fonts::FontId,
|
||||
sum_tree::{self, Cursor, SumTree},
|
||||
text_layout::LineWrapper,
|
||||
Entity, ModelContext, Task,
|
||||
};
|
||||
use buffer::{HighlightId, Point};
|
||||
use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task};
|
||||
use lazy_static::lazy_static;
|
||||
use smol::future::yield_now;
|
||||
use std::{collections::VecDeque, ops::Range, time::Duration};
|
||||
use sum_tree::{Bias, Cursor, SumTree};
|
||||
|
||||
pub struct WrapMap {
|
||||
snapshot: Snapshot,
|
||||
|
@ -900,13 +896,10 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
editor::{
|
||||
display_map::{fold_map::FoldMap, tab_map::TabMap},
|
||||
Buffer,
|
||||
},
|
||||
display_map::{fold_map::FoldMap, tab_map::TabMap},
|
||||
test::Observer,
|
||||
util::RandomCharIter,
|
||||
};
|
||||
use buffer::{Buffer, RandomCharIter};
|
||||
use rand::prelude::*;
|
||||
use std::env;
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
use super::{
|
||||
DisplayPoint, Editor, EditorMode, EditorStyle, Insert, Scroll, Select, SelectPhase, Snapshot,
|
||||
MAX_LINE_LEN,
|
||||
DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Insert, Scroll, Select,
|
||||
SelectPhase, Snapshot, MAX_LINE_LEN,
|
||||
};
|
||||
use crate::{theme::HighlightId, time::ReplicaId};
|
||||
use buffer::HighlightId;
|
||||
use clock::ReplicaId;
|
||||
use gpui::{
|
||||
color::Color,
|
||||
geometry::{
|
||||
|
@ -27,12 +28,12 @@ use std::{
|
|||
|
||||
pub struct EditorElement {
|
||||
view: WeakViewHandle<Editor>,
|
||||
style: EditorStyle,
|
||||
settings: EditorSettings,
|
||||
}
|
||||
|
||||
impl EditorElement {
|
||||
pub fn new(view: WeakViewHandle<Editor>, style: EditorStyle) -> Self {
|
||||
Self { view, style }
|
||||
pub fn new(view: WeakViewHandle<Editor>, settings: EditorSettings) -> Self {
|
||||
Self { view, settings }
|
||||
}
|
||||
|
||||
fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
|
||||
|
@ -195,15 +196,16 @@ impl EditorElement {
|
|||
let bounds = gutter_bounds.union_rect(text_bounds);
|
||||
let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
|
||||
let editor = self.view(cx.app);
|
||||
let style = &self.settings.style;
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: gutter_bounds,
|
||||
background: Some(self.style.gutter_background),
|
||||
background: Some(style.gutter_background),
|
||||
border: Border::new(0., Color::transparent_black()),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: text_bounds,
|
||||
background: Some(self.style.background),
|
||||
background: Some(style.background),
|
||||
border: Border::new(0., Color::transparent_black()),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
|
@ -230,7 +232,7 @@ impl EditorElement {
|
|||
);
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: RectF::new(origin, size),
|
||||
background: Some(self.style.active_line_background),
|
||||
background: Some(style.active_line_background),
|
||||
border: Border::default(),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
|
@ -267,8 +269,7 @@ impl EditorElement {
|
|||
cx: &mut PaintContext,
|
||||
) {
|
||||
let view = self.view(cx.app);
|
||||
let settings = self.view(cx.app).settings.borrow();
|
||||
let theme = &settings.theme.editor;
|
||||
let style = &self.settings.style;
|
||||
let local_replica_id = view.replica_id(cx);
|
||||
let scroll_position = layout.snapshot.scroll_position();
|
||||
let start_row = scroll_position.y() as u32;
|
||||
|
@ -286,11 +287,11 @@ impl EditorElement {
|
|||
let content_origin = bounds.origin() + layout.text_offset;
|
||||
|
||||
for (replica_id, selections) in &layout.selections {
|
||||
let style_ix = *replica_id as usize % (theme.guest_selections.len() + 1);
|
||||
let style_ix = *replica_id as usize % (style.guest_selections.len() + 1);
|
||||
let style = if style_ix == 0 {
|
||||
&theme.selection
|
||||
&style.selection
|
||||
} else {
|
||||
&theme.guest_selections[style_ix - 1]
|
||||
&style.guest_selections[style_ix - 1]
|
||||
};
|
||||
|
||||
for selection in selections {
|
||||
|
@ -382,15 +383,16 @@ impl EditorElement {
|
|||
|
||||
fn max_line_number_width(&self, snapshot: &Snapshot, cx: &LayoutContext) -> f32 {
|
||||
let digit_count = (snapshot.buffer_row_count() as f32).log10().floor() as usize + 1;
|
||||
let style = &self.settings.style;
|
||||
|
||||
cx.text_layout_cache
|
||||
.layout_str(
|
||||
"1".repeat(digit_count).as_str(),
|
||||
self.style.text.font_size,
|
||||
style.text.font_size,
|
||||
&[(
|
||||
digit_count,
|
||||
RunStyle {
|
||||
font_id: self.style.text.font_id,
|
||||
font_id: style.text.font_id,
|
||||
color: Color::black(),
|
||||
underline: false,
|
||||
},
|
||||
|
@ -406,6 +408,7 @@ impl EditorElement {
|
|||
snapshot: &Snapshot,
|
||||
cx: &LayoutContext,
|
||||
) -> Vec<Option<text_layout::Line>> {
|
||||
let style = &self.settings.style;
|
||||
let mut layouts = Vec::with_capacity(rows.len());
|
||||
let mut line_number = String::new();
|
||||
for (ix, (buffer_row, soft_wrapped)) in snapshot
|
||||
|
@ -415,9 +418,9 @@ impl EditorElement {
|
|||
{
|
||||
let display_row = rows.start + ix as u32;
|
||||
let color = if active_rows.contains_key(&display_row) {
|
||||
self.style.line_number_active
|
||||
style.line_number_active
|
||||
} else {
|
||||
self.style.line_number
|
||||
style.line_number
|
||||
};
|
||||
if soft_wrapped {
|
||||
layouts.push(None);
|
||||
|
@ -426,11 +429,11 @@ impl EditorElement {
|
|||
write!(&mut line_number, "{}", buffer_row + 1).unwrap();
|
||||
layouts.push(Some(cx.text_layout_cache.layout_str(
|
||||
&line_number,
|
||||
self.style.text.font_size,
|
||||
style.text.font_size,
|
||||
&[(
|
||||
line_number.len(),
|
||||
RunStyle {
|
||||
font_id: self.style.text.font_id,
|
||||
font_id: style.text.font_id,
|
||||
color,
|
||||
underline: false,
|
||||
},
|
||||
|
@ -455,7 +458,7 @@ impl EditorElement {
|
|||
|
||||
// When the editor is empty and unfocused, then show the placeholder.
|
||||
if snapshot.is_empty() && !snapshot.is_focused() {
|
||||
let placeholder_style = self.style.placeholder_text();
|
||||
let placeholder_style = self.settings.style.placeholder_text();
|
||||
let placeholder_text = snapshot.placeholder_text();
|
||||
let placeholder_lines = placeholder_text
|
||||
.as_ref()
|
||||
|
@ -481,10 +484,10 @@ impl EditorElement {
|
|||
.collect();
|
||||
}
|
||||
|
||||
let mut prev_font_properties = self.style.text.font_properties.clone();
|
||||
let mut prev_font_id = self.style.text.font_id;
|
||||
let style = &self.settings.style;
|
||||
let mut prev_font_properties = style.text.font_properties.clone();
|
||||
let mut prev_font_id = style.text.font_id;
|
||||
|
||||
let theme = snapshot.theme().clone();
|
||||
let mut layouts = Vec::with_capacity(rows.len());
|
||||
let mut line = String::new();
|
||||
let mut styles = Vec::new();
|
||||
|
@ -497,7 +500,7 @@ impl EditorElement {
|
|||
if ix > 0 {
|
||||
layouts.push(cx.text_layout_cache.layout_str(
|
||||
&line,
|
||||
self.style.text.font_size,
|
||||
style.text.font_size,
|
||||
&styles,
|
||||
));
|
||||
line.clear();
|
||||
|
@ -510,17 +513,19 @@ impl EditorElement {
|
|||
}
|
||||
|
||||
if !line_chunk.is_empty() && !line_exceeded_max_len {
|
||||
let style = theme
|
||||
.syntax
|
||||
.highlight_style(style_ix)
|
||||
.unwrap_or(self.style.text.clone().into());
|
||||
let highlight_style = style_ix
|
||||
.style(&style.syntax)
|
||||
.unwrap_or(style.text.clone().into());
|
||||
// Avoid a lookup if the font properties match the previous ones.
|
||||
let font_id = if style.font_properties == prev_font_properties {
|
||||
let font_id = if highlight_style.font_properties == prev_font_properties {
|
||||
prev_font_id
|
||||
} else {
|
||||
cx.font_cache
|
||||
.select_font(self.style.text.font_family_id, &style.font_properties)
|
||||
.unwrap_or(self.style.text.font_id)
|
||||
.select_font(
|
||||
style.text.font_family_id,
|
||||
&highlight_style.font_properties,
|
||||
)
|
||||
.unwrap_or(style.text.font_id)
|
||||
};
|
||||
|
||||
if line.len() + line_chunk.len() > MAX_LINE_LEN {
|
||||
|
@ -537,12 +542,12 @@ impl EditorElement {
|
|||
line_chunk.len(),
|
||||
RunStyle {
|
||||
font_id,
|
||||
color: style.color,
|
||||
underline: style.underline,
|
||||
color: highlight_style.color,
|
||||
underline: highlight_style.underline,
|
||||
},
|
||||
));
|
||||
prev_font_id = font_id;
|
||||
prev_font_properties = style.font_properties;
|
||||
prev_font_properties = highlight_style.font_properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -566,12 +571,13 @@ impl Element for EditorElement {
|
|||
}
|
||||
|
||||
let snapshot = self.snapshot(cx.app);
|
||||
let line_height = self.style.text.line_height(cx.font_cache);
|
||||
let style = self.settings.style.clone();
|
||||
let line_height = style.text.line_height(cx.font_cache);
|
||||
|
||||
let gutter_padding;
|
||||
let gutter_width;
|
||||
if snapshot.mode == EditorMode::Full {
|
||||
gutter_padding = self.style.text.em_width(cx.font_cache);
|
||||
gutter_padding = style.text.em_width(cx.font_cache);
|
||||
gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
|
||||
} else {
|
||||
gutter_padding = 0.0;
|
||||
|
@ -579,8 +585,8 @@ impl Element for EditorElement {
|
|||
};
|
||||
|
||||
let text_width = size.x() - gutter_width;
|
||||
let text_offset = vec2f(-self.style.text.descent(cx.font_cache), 0.);
|
||||
let em_width = self.style.text.em_width(cx.font_cache);
|
||||
let text_offset = vec2f(-style.text.descent(cx.font_cache), 0.);
|
||||
let em_width = style.text.em_width(cx.font_cache);
|
||||
let overscroll = vec2f(em_width, 0.);
|
||||
let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width;
|
||||
let snapshot = self.update_view(cx.app, |view, cx| {
|
||||
|
@ -676,7 +682,7 @@ impl Element for EditorElement {
|
|||
overscroll,
|
||||
text_offset,
|
||||
snapshot,
|
||||
style: self.style.clone(),
|
||||
style: self.settings.style.clone(),
|
||||
active_rows,
|
||||
line_layouts,
|
||||
line_number_layouts,
|
||||
|
@ -688,7 +694,7 @@ impl Element for EditorElement {
|
|||
|
||||
let scroll_max = layout.scroll_max(cx.font_cache, cx.text_layout_cache).x();
|
||||
let scroll_width = layout.scroll_width(cx.text_layout_cache);
|
||||
let max_glyph_width = self.style.text.em_width(&cx.font_cache);
|
||||
let max_glyph_width = style.text.em_width(&cx.font_cache);
|
||||
self.update_view(cx.app, |view, cx| {
|
||||
let clamped = view.clamp_scroll_left(scroll_max);
|
||||
let autoscrolled;
|
||||
|
@ -1034,30 +1040,27 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
editor::{Buffer, Editor, EditorStyle},
|
||||
settings,
|
||||
test::sample_text,
|
||||
{Editor, EditorSettings},
|
||||
};
|
||||
use buffer::Buffer;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
|
||||
let font_cache = cx.font_cache().clone();
|
||||
let settings = settings::test(&cx).1;
|
||||
let style = EditorStyle::test(&font_cache);
|
||||
let settings = EditorSettings::test(cx);
|
||||
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
|
||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||
Editor::for_buffer(
|
||||
buffer,
|
||||
settings.clone(),
|
||||
{
|
||||
let style = style.clone();
|
||||
move |_| style.clone()
|
||||
let settings = settings.clone();
|
||||
move |_| settings.clone()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let element = EditorElement::new(editor.downgrade(), style);
|
||||
let element = EditorElement::new(editor.downgrade(), settings);
|
||||
|
||||
let layouts = editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
|
@ -1,28 +1,20 @@
|
|||
pub mod buffer;
|
||||
pub mod display_map;
|
||||
mod element;
|
||||
pub mod movement;
|
||||
|
||||
use crate::{
|
||||
language::Language,
|
||||
settings::Settings,
|
||||
theme::Theme,
|
||||
time::ReplicaId,
|
||||
util::{post_inc, Bias},
|
||||
workspace,
|
||||
worktree::{File, Worktree},
|
||||
};
|
||||
use anyhow::Result;
|
||||
pub use buffer::*;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use buffer::*;
|
||||
use clock::ReplicaId;
|
||||
pub use display_map::DisplayPoint;
|
||||
use display_map::*;
|
||||
pub use element::*;
|
||||
use gpui::{
|
||||
action, color::Color, fonts::TextStyle, geometry::vector::Vector2F, keymap::Binding,
|
||||
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
|
||||
MutableAppContext, RenderContext, Task, View, ViewContext, WeakViewHandle,
|
||||
MutableAppContext, RenderContext, View, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use postage::watch;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use smol::Timer;
|
||||
|
@ -31,11 +23,13 @@ use std::{
|
|||
cmp::{self, Ordering},
|
||||
mem,
|
||||
ops::{Range, RangeInclusive},
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use sum_tree::Bias;
|
||||
use theme::EditorStyle;
|
||||
use util::post_inc;
|
||||
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
const MAX_LINE_LEN: usize = 1024;
|
||||
|
@ -250,6 +244,20 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||
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)]
|
||||
pub enum SelectPhase {
|
||||
Begin {
|
||||
|
@ -270,24 +278,10 @@ pub enum EditorMode {
|
|||
Full,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct EditorStyle {
|
||||
pub text: TextStyle,
|
||||
#[serde(default)]
|
||||
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,
|
||||
#[derive(Clone)]
|
||||
pub struct EditorSettings {
|
||||
pub tab_size: usize,
|
||||
pub style: EditorStyle,
|
||||
}
|
||||
|
||||
pub struct Editor {
|
||||
|
@ -302,8 +296,7 @@ pub struct Editor {
|
|||
scroll_position: Vector2F,
|
||||
scroll_top_anchor: Anchor,
|
||||
autoscroll_requested: bool,
|
||||
build_style: Rc<RefCell<dyn FnMut(&mut MutableAppContext) -> EditorStyle>>,
|
||||
settings: watch::Receiver<Settings>,
|
||||
build_settings: Rc<RefCell<dyn Fn(&AppContext) -> EditorSettings>>,
|
||||
focused: bool,
|
||||
show_local_cursors: bool,
|
||||
blink_epoch: usize,
|
||||
|
@ -316,7 +309,6 @@ pub struct Snapshot {
|
|||
pub mode: EditorMode,
|
||||
pub display_snapshot: DisplayMapSnapshot,
|
||||
pub placeholder_text: Option<Arc<str>>,
|
||||
pub theme: Arc<Theme>,
|
||||
is_focused: bool,
|
||||
scroll_position: Vector2F,
|
||||
scroll_top_anchor: Anchor,
|
||||
|
@ -335,50 +327,53 @@ struct ClipboardSelection {
|
|||
|
||||
impl Editor {
|
||||
pub fn single_line(
|
||||
settings: watch::Receiver<Settings>,
|
||||
build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle,
|
||||
build_settings: impl 'static + Fn(&AppContext) -> EditorSettings,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
|
||||
let mut view = Self::for_buffer(buffer, settings, build_style, cx);
|
||||
let mut view = Self::for_buffer(buffer, build_settings, cx);
|
||||
view.mode = EditorMode::SingleLine;
|
||||
view
|
||||
}
|
||||
|
||||
pub fn auto_height(
|
||||
max_lines: usize,
|
||||
settings: watch::Receiver<Settings>,
|
||||
build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle,
|
||||
build_settings: impl 'static + Fn(&AppContext) -> EditorSettings,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
|
||||
let mut view = Self::for_buffer(buffer, settings, build_style, cx);
|
||||
let mut view = Self::for_buffer(buffer, build_settings, cx);
|
||||
view.mode = EditorMode::AutoHeight { max_lines };
|
||||
view
|
||||
}
|
||||
|
||||
pub fn for_buffer(
|
||||
buffer: ModelHandle<Buffer>,
|
||||
settings: watch::Receiver<Settings>,
|
||||
build_style: impl 'static + FnMut(&mut MutableAppContext) -> EditorStyle,
|
||||
build_settings: impl 'static + Fn(&AppContext) -> EditorSettings,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
Self::new(buffer, settings, Rc::new(RefCell::new(build_style)), cx)
|
||||
Self::new(buffer, Rc::new(RefCell::new(build_settings)), cx)
|
||||
}
|
||||
|
||||
fn new(
|
||||
pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
|
||||
let mut clone = Self::new(self.buffer.clone(), self.build_settings.clone(), cx);
|
||||
clone.scroll_position = self.scroll_position;
|
||||
clone.scroll_top_anchor = self.scroll_top_anchor.clone();
|
||||
clone
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
buffer: ModelHandle<Buffer>,
|
||||
settings: watch::Receiver<Settings>,
|
||||
build_style: Rc<RefCell<dyn FnMut(&mut MutableAppContext) -> EditorStyle>>,
|
||||
build_settings: Rc<RefCell<dyn Fn(&AppContext) -> EditorSettings>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let style = build_style.borrow_mut()(cx);
|
||||
let settings = build_settings.borrow_mut()(cx);
|
||||
let display_map = cx.add_model(|cx| {
|
||||
DisplayMap::new(
|
||||
buffer.clone(),
|
||||
settings.borrow().tab_size,
|
||||
style.text.font_id,
|
||||
style.text.font_size,
|
||||
settings.tab_size,
|
||||
settings.style.text.font_id,
|
||||
settings.style.text.font_size,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
|
@ -410,11 +405,10 @@ impl Editor {
|
|||
next_selection_id,
|
||||
add_selections_state: None,
|
||||
select_larger_syntax_node_stack: Vec::new(),
|
||||
build_style,
|
||||
build_settings,
|
||||
scroll_position: Vector2F::zero(),
|
||||
scroll_top_anchor: Anchor::min(),
|
||||
autoscroll_requested: false,
|
||||
settings,
|
||||
focused: false,
|
||||
show_local_cursors: false,
|
||||
blink_epoch: 0,
|
||||
|
@ -433,14 +427,11 @@ impl Editor {
|
|||
}
|
||||
|
||||
pub fn snapshot(&mut self, cx: &mut MutableAppContext) -> Snapshot {
|
||||
let settings = self.settings.borrow();
|
||||
|
||||
Snapshot {
|
||||
mode: self.mode,
|
||||
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
||||
scroll_position: self.scroll_position,
|
||||
scroll_top_anchor: self.scroll_top_anchor.clone(),
|
||||
theme: settings.theme.clone(),
|
||||
placeholder_text: self.placeholder_text.clone(),
|
||||
is_focused: self
|
||||
.handle
|
||||
|
@ -710,7 +701,11 @@ impl Editor {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn select_display_ranges<'a, T>(&mut self, ranges: T, cx: &mut ViewContext<Self>) -> Result<()>
|
||||
fn select_display_ranges<'a, T>(
|
||||
&mut self,
|
||||
ranges: T,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
T: IntoIterator<Item = &'a Range<DisplayPoint>>,
|
||||
{
|
||||
|
@ -2284,9 +2279,9 @@ impl Editor {
|
|||
.text()
|
||||
}
|
||||
|
||||
pub fn font_size(&self) -> f32 {
|
||||
self.settings.borrow().buffer_font_size
|
||||
}
|
||||
// pub fn font_size(&self) -> f32 {
|
||||
// self.settings.font_size
|
||||
// }
|
||||
|
||||
pub fn set_wrap_width(&self, width: f32, cx: &mut MutableAppContext) -> bool {
|
||||
self.display_map
|
||||
|
@ -2400,10 +2395,6 @@ impl Snapshot {
|
|||
.highlighted_chunks_for_rows(display_rows)
|
||||
}
|
||||
|
||||
pub fn theme(&self) -> &Arc<Theme> {
|
||||
&self.theme
|
||||
}
|
||||
|
||||
pub fn scroll_position(&self) -> Vector2F {
|
||||
compute_scroll_position(
|
||||
&self.display_snapshot,
|
||||
|
@ -2437,39 +2428,42 @@ impl Snapshot {
|
|||
}
|
||||
}
|
||||
|
||||
impl EditorStyle {
|
||||
impl EditorSettings {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test(font_cache: &gpui::FontCache) -> 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();
|
||||
pub fn test(cx: &AppContext) -> Self {
|
||||
Self {
|
||||
text: TextStyle {
|
||||
font_family_name,
|
||||
font_family_id,
|
||||
font_id,
|
||||
font_size: 14.,
|
||||
color: Color::from_u32(0xff0000ff),
|
||||
font_properties,
|
||||
underline: false,
|
||||
tab_size: 4,
|
||||
style: {
|
||||
let font_cache: &gpui::FontCache = cx.font_cache();
|
||||
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();
|
||||
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(
|
||||
|
@ -2508,11 +2502,15 @@ impl Entity for Editor {
|
|||
|
||||
impl View for Editor {
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let style = self.build_style.borrow_mut()(cx);
|
||||
let settings = self.build_settings.borrow_mut()(cx);
|
||||
self.display_map.update(cx, |map, cx| {
|
||||
map.set_font(style.text.font_id, style.text.font_size, cx)
|
||||
map.set_font(
|
||||
settings.style.text.font_id,
|
||||
settings.style.text.font_size,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
EditorElement::new(self.handle.clone(), style).boxed()
|
||||
EditorElement::new(self.handle.clone(), settings).boxed()
|
||||
}
|
||||
|
||||
fn ui_name() -> &'static str {
|
||||
|
@ -2551,135 +2549,53 @@ impl View for Editor {
|
|||
}
|
||||
}
|
||||
|
||||
impl workspace::Item for Buffer {
|
||||
type View = Editor;
|
||||
|
||||
fn file(&self) -> Option<&File> {
|
||||
self.file()
|
||||
}
|
||||
|
||||
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()
|
||||
impl SelectionExt for Selection {
|
||||
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 {
|
||||
"untitled".into()
|
||||
start..end
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_id(&self, cx: &AppContext) -> Option<(usize, Arc<Path>)> {
|
||||
self.buffer.read(cx).file().map(|file| file.entry_id())
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut clone = Editor::new(
|
||||
self.buffer.clone(),
|
||||
self.settings.clone(),
|
||||
self.build_style.clone(),
|
||||
cx,
|
||||
);
|
||||
clone.scroll_position = self.scroll_position;
|
||||
clone.scroll_top_anchor = self.scroll_top_anchor.clone();
|
||||
Some(clone)
|
||||
}
|
||||
let (display_start, buffer_start) = map.prev_row_boundary(display_start);
|
||||
let (display_end, buffer_end) = map.next_row_boundary(display_end);
|
||||
|
||||
fn save(&mut self, cx: &mut ViewContext<Self>) -> Result<Task<Result<()>>> {
|
||||
let save = self.buffer.update(cx, |b, cx| b.save(cx))?;
|
||||
Ok(cx.spawn(|_, _| async move {
|
||||
save.await?;
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
fn save_as(
|
||||
&mut self,
|
||||
worktree: &ModelHandle<Worktree>,
|
||||
path: &Path,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.buffer
|
||||
.update(cx, |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()
|
||||
SpannedRows {
|
||||
buffer_rows: buffer_start.row..buffer_end.row + 1,
|
||||
display_rows: display_start.row()..display_end.row() + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{editor::Point, language::LanguageRegistry, settings, test::sample_text};
|
||||
use buffer::History;
|
||||
use crate::test::sample_text;
|
||||
use buffer::{History, Point};
|
||||
use unindent::Unindent;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(cx);
|
||||
let (_, editor) =
|
||||
cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
|
||||
|
@ -2746,7 +2662,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
|
@ -2778,7 +2694,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_cancel(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
|
@ -2841,7 +2757,7 @@ mod tests {
|
|||
cx,
|
||||
)
|
||||
});
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||
build_editor(buffer.clone(), settings, cx)
|
||||
});
|
||||
|
@ -2909,7 +2825,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_move_cursor(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||
build_editor(buffer.clone(), settings, cx)
|
||||
});
|
||||
|
@ -2986,7 +2902,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||
build_editor(buffer.clone(), settings, cx)
|
||||
});
|
||||
|
@ -3044,7 +2960,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||
build_editor(buffer.clone(), settings, cx)
|
||||
});
|
||||
|
@ -3075,7 +2991,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\n def", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
view.select_display_ranges(
|
||||
|
@ -3218,7 +3134,7 @@ mod tests {
|
|||
fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer =
|
||||
cx.add_model(|cx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
view.select_display_ranges(
|
||||
|
@ -3358,11 +3274,11 @@ mod tests {
|
|||
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer =
|
||||
cx.add_model(|cx| Buffer::new(0, "use one::{\n two::three::four::five\n};", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
view.set_wrap_width(130., cx);
|
||||
view.set_wrap_width(140., cx);
|
||||
assert_eq!(
|
||||
view.display_text(cx),
|
||||
"use one::{\n two::three::\n four::five\n};"
|
||||
|
@ -3412,7 +3328,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_delete_to_word_boundary(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "one two three four", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||
build_editor(buffer.clone(), settings, cx)
|
||||
});
|
||||
|
@ -3459,7 +3375,7 @@ mod tests {
|
|||
cx,
|
||||
)
|
||||
});
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||
build_editor(buffer.clone(), settings, cx)
|
||||
});
|
||||
|
@ -3495,7 +3411,7 @@ mod tests {
|
|||
cx,
|
||||
)
|
||||
});
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||
build_editor(buffer.clone(), settings, cx)
|
||||
});
|
||||
|
@ -3524,7 +3440,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_delete_line(cx: &mut gpui::MutableAppContext) {
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx));
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
|
@ -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 (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
|
@ -3565,7 +3481,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_duplicate_line(cx: &mut gpui::MutableAppContext) {
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx));
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
|
@ -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 (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
|
@ -3618,7 +3534,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(10, 5), cx));
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
|
@ -3716,7 +3632,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_clipboard(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "one✅ two three four five six ", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let view = cx
|
||||
.add_window(Default::default(), |cx| {
|
||||
build_editor(buffer.clone(), settings, cx)
|
||||
|
@ -3851,7 +3767,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_select_all(cx: &mut gpui::MutableAppContext) {
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\nde\nfgh", cx));
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
view.select_all(&SelectAll, cx);
|
||||
|
@ -3864,7 +3780,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_select_line(cx: &mut gpui::MutableAppContext) {
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 5), cx));
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
|
@ -3910,7 +3826,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(9, 5), cx));
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
view.update(cx, |view, cx| {
|
||||
|
@ -3978,7 +3894,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) {
|
||||
let settings = settings::test(&cx).1;
|
||||
let settings = EditorSettings::test(&cx);
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndefghi\n\njk\nlmno\n", cx));
|
||||
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
|
||||
|
||||
|
@ -4151,9 +4067,17 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) {
|
||||
let settings = cx.read(settings::test).1;
|
||||
let languages = LanguageRegistry::new();
|
||||
let lang = languages.select_language("z.rs");
|
||||
let settings = cx.read(EditorSettings::test);
|
||||
|
||||
let grammar = tree_sitter_rust::language();
|
||||
let language = Arc::new(Language {
|
||||
config: LanguageConfig::default(),
|
||||
brackets_query: tree_sitter::Query::new(grammar, "").unwrap(),
|
||||
highlight_query: tree_sitter::Query::new(grammar, "").unwrap(),
|
||||
highlight_map: Default::default(),
|
||||
grammar,
|
||||
});
|
||||
|
||||
let text = r#"
|
||||
use mod1::mod2::{mod3, mod4};
|
||||
|
||||
|
@ -4164,9 +4088,9 @@ mod tests {
|
|||
.unindent();
|
||||
let buffer = cx.add_model(|cx| {
|
||||
let history = History::new(text.into());
|
||||
Buffer::from_history(0, history, None, lang.cloned(), cx)
|
||||
Buffer::from_history(0, history, None, Some(language), cx)
|
||||
});
|
||||
let (_, view) = cx.add_window(|cx| build_editor(buffer, settings.clone(), cx));
|
||||
let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
|
||||
view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing())
|
||||
.await;
|
||||
|
||||
|
@ -4307,36 +4231,10 @@ mod tests {
|
|||
|
||||
fn build_editor(
|
||||
buffer: ModelHandle<Buffer>,
|
||||
settings: watch::Receiver<Settings>,
|
||||
settings: EditorSettings,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Editor {
|
||||
let style = {
|
||||
let font_cache = cx.font_cache();
|
||||
let settings = settings.borrow();
|
||||
EditorStyle {
|
||||
text: TextStyle {
|
||||
color: Default::default(),
|
||||
font_family_name: font_cache.family_name(settings.buffer_font_family).unwrap(),
|
||||
font_family_id: settings.buffer_font_family,
|
||||
font_id: font_cache
|
||||
.select_font(settings.buffer_font_family, &Default::default())
|
||||
.unwrap(),
|
||||
font_size: settings.buffer_font_size,
|
||||
font_properties: Default::default(),
|
||||
underline: false,
|
||||
},
|
||||
placeholder_text: None,
|
||||
background: Default::default(),
|
||||
selection: Default::default(),
|
||||
gutter_background: Default::default(),
|
||||
active_line_background: Default::default(),
|
||||
line_number: Default::default(),
|
||||
line_number_active: Default::default(),
|
||||
guest_selections: Default::default(),
|
||||
}
|
||||
};
|
||||
|
||||
Editor::for_buffer(buffer, settings, move |_| style.clone(), cx)
|
||||
Editor::for_buffer(buffer, move |_| settings.clone(), cx)
|
||||
}
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ fn char_kind(c: char) -> CharKind {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::editor::{display_map::DisplayMap, Buffer};
|
||||
use crate::{display_map::DisplayMap, Buffer};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) {
|
39
crates/editor/src/test.rs
Normal file
39
crates/editor/src/test.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use gpui::{Entity, ModelHandle};
|
||||
use smol::channel;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub fn sample_text(rows: usize, cols: usize) -> String {
|
||||
let mut text = String::new();
|
||||
for row in 0..rows {
|
||||
let c: char = ('a' as u32 + row as u32) as u8 as char;
|
||||
let mut line = c.to_string().repeat(cols);
|
||||
if row < rows - 1 {
|
||||
line.push('\n');
|
||||
}
|
||||
text += &line;
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
pub struct Observer<T>(PhantomData<T>);
|
||||
|
||||
impl<T: 'static> Entity for Observer<T> {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl<T: Entity> Observer<T> {
|
||||
pub fn new(
|
||||
handle: &ModelHandle<T>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) -> (ModelHandle<Self>, channel::Receiver<()>) {
|
||||
let (notify_tx, notify_rx) = channel::unbounded();
|
||||
let observer = cx.add_model(|cx| {
|
||||
cx.observe(handle, move |_, _, _| {
|
||||
let _ = notify_tx.try_send(());
|
||||
})
|
||||
.detach();
|
||||
Observer(PhantomData)
|
||||
});
|
||||
(observer, notify_rx)
|
||||
}
|
||||
}
|
18
crates/file_finder/Cargo.toml
Normal file
18
crates/file_finder/Cargo.toml
Normal 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"] }
|
|
@ -1,10 +1,5 @@
|
|||
use crate::{
|
||||
editor::{self, Editor},
|
||||
settings::Settings,
|
||||
util,
|
||||
workspace::Workspace,
|
||||
worktree::{match_paths, PathMatch},
|
||||
};
|
||||
use editor::{Editor, EditorSettings};
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{
|
||||
action,
|
||||
elements::*,
|
||||
|
@ -13,10 +8,11 @@ use gpui::{
|
|||
menu::{SelectNext, SelectPrev},
|
||||
Binding,
|
||||
},
|
||||
AppContext, Axis, Entity, MutableAppContext, RenderContext, Task, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
AppContext, Axis, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use postage::watch;
|
||||
use project::{Project, ProjectPath};
|
||||
use std::{
|
||||
cmp,
|
||||
path::Path,
|
||||
|
@ -25,11 +21,13 @@ use std::{
|
|||
Arc,
|
||||
},
|
||||
};
|
||||
use util::post_inc;
|
||||
use workspace::{Settings, Workspace};
|
||||
|
||||
pub struct FileFinder {
|
||||
handle: WeakViewHandle<Self>,
|
||||
settings: watch::Receiver<Settings>,
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
project: ModelHandle<Project>,
|
||||
query_editor: ViewHandle<Editor>,
|
||||
search_count: usize,
|
||||
latest_search_id: usize,
|
||||
|
@ -43,13 +41,7 @@ pub struct FileFinder {
|
|||
|
||||
action!(Toggle);
|
||||
action!(Confirm);
|
||||
action!(Select, Entry);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Entry {
|
||||
worktree_id: usize,
|
||||
path: Arc<Path>,
|
||||
}
|
||||
action!(Select, ProjectPath);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(FileFinder::toggle);
|
||||
|
@ -66,7 +58,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||
}
|
||||
|
||||
pub enum Event {
|
||||
Selected(usize, Arc<Path>),
|
||||
Selected(ProjectPath),
|
||||
Dismissed,
|
||||
}
|
||||
|
||||
|
@ -202,8 +194,8 @@ impl FileFinder {
|
|||
)
|
||||
.with_style(style.container);
|
||||
|
||||
let action = Select(Entry {
|
||||
worktree_id: path_match.tree_id,
|
||||
let action = Select(ProjectPath {
|
||||
worktree_id: path_match.worktree_id,
|
||||
path: path_match.path.clone(),
|
||||
});
|
||||
EventHandler::new(container.boxed())
|
||||
|
@ -241,8 +233,8 @@ impl FileFinder {
|
|||
|
||||
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
workspace.toggle_modal(cx, |cx, workspace| {
|
||||
let handle = cx.handle();
|
||||
let finder = cx.add_view(|cx| Self::new(workspace.settings.clone(), handle, cx));
|
||||
let project = workspace.project().clone();
|
||||
let finder = cx.add_view(|cx| Self::new(workspace.settings.clone(), project, cx));
|
||||
cx.subscribe(&finder, Self::on_event).detach();
|
||||
finder
|
||||
});
|
||||
|
@ -255,9 +247,9 @@ impl FileFinder {
|
|||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
match event {
|
||||
Event::Selected(tree_id, path) => {
|
||||
Event::Selected(project_path) => {
|
||||
workspace
|
||||
.open_entry((*tree_id, path.clone()), cx)
|
||||
.open_entry(project_path.clone(), cx)
|
||||
.map(|d| d.detach());
|
||||
workspace.dismiss_modal(cx);
|
||||
}
|
||||
|
@ -269,17 +261,22 @@ impl FileFinder {
|
|||
|
||||
pub fn new(
|
||||
settings: watch::Receiver<Settings>,
|
||||
workspace: ViewHandle<Workspace>,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
cx.observe(&workspace, Self::workspace_updated).detach();
|
||||
cx.observe(&project, Self::project_updated).detach();
|
||||
|
||||
let query_editor = cx.add_view(|cx| {
|
||||
Editor::single_line(
|
||||
settings.clone(),
|
||||
{
|
||||
let settings = settings.clone();
|
||||
move |_| settings.borrow().theme.selector.input_editor.as_editor()
|
||||
move |_| {
|
||||
let settings = settings.borrow();
|
||||
EditorSettings {
|
||||
style: settings.theme.selector.input_editor.as_editor(),
|
||||
tab_size: settings.tab_size,
|
||||
}
|
||||
}
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|
@ -290,7 +287,7 @@ impl FileFinder {
|
|||
Self {
|
||||
handle: cx.handle().downgrade(),
|
||||
settings,
|
||||
workspace: workspace.downgrade(),
|
||||
project,
|
||||
query_editor,
|
||||
search_count: 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));
|
||||
if let Some(task) = self.spawn_search(query, cx) {
|
||||
task.detach();
|
||||
|
@ -320,7 +317,7 @@ impl FileFinder {
|
|||
editor::Event::Edited => {
|
||||
let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx));
|
||||
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();
|
||||
cx.notify();
|
||||
} else {
|
||||
|
@ -337,7 +334,7 @@ impl FileFinder {
|
|||
fn selected_index(&self) -> usize {
|
||||
if let Some(selected) = self.selected.as_ref() {
|
||||
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())
|
||||
{
|
||||
return ix;
|
||||
|
@ -352,7 +349,7 @@ impl FileFinder {
|
|||
if selected_index > 0 {
|
||||
selected_index -= 1;
|
||||
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);
|
||||
cx.notify();
|
||||
|
@ -363,7 +360,7 @@ impl FileFinder {
|
|||
if selected_index + 1 < self.matches.len() {
|
||||
selected_index += 1;
|
||||
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);
|
||||
cx.notify();
|
||||
|
@ -371,40 +368,30 @@ impl FileFinder {
|
|||
|
||||
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
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>) {
|
||||
cx.emit(Event::Selected(entry.worktree_id, entry.path.clone()));
|
||||
fn select(&mut self, Select(project_path): &Select, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(Event::Selected(project_path.clone()));
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
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 background = cx.as_ref().background().clone();
|
||||
self.cancel_flag.store(true, atomic::Ordering::Relaxed);
|
||||
self.cancel_flag = Arc::new(AtomicBool::new(false));
|
||||
let cancel_flag = self.cancel_flag.clone();
|
||||
let project = self.project.clone();
|
||||
Some(cx.spawn(|this, mut cx| async move {
|
||||
let matches = match_paths(
|
||||
&snapshots,
|
||||
&query,
|
||||
false,
|
||||
false,
|
||||
100,
|
||||
cancel_flag.as_ref(),
|
||||
background,
|
||||
)
|
||||
.await;
|
||||
let matches = project
|
||||
.read_with(&cx, |project, cx| {
|
||||
project.match_paths(&query, false, false, 100, cancel_flag.as_ref(), cx)
|
||||
})
|
||||
.await;
|
||||
let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update_matches((search_id, did_cancel, query, matches), cx)
|
||||
|
@ -435,19 +422,15 @@ impl FileFinder {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
editor::{self, Insert},
|
||||
fs::FakeFs,
|
||||
test::test_app_state,
|
||||
workspace::Workspace,
|
||||
};
|
||||
use editor::Insert;
|
||||
use serde_json::json;
|
||||
use std::path::PathBuf;
|
||||
use workspace::{Workspace, WorkspaceParams};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_matching_paths(mut cx: gpui::TestAppContext) {
|
||||
let app_state = cx.update(test_app_state);
|
||||
app_state
|
||||
let params = cx.update(WorkspaceParams::test);
|
||||
params
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
|
@ -465,7 +448,7 @@ mod tests {
|
|||
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(¶ms, cx));
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.add_worktree(Path::new("/root"), cx)
|
||||
|
@ -509,7 +492,8 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
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(
|
||||
"/dir",
|
||||
json!({
|
||||
|
@ -524,10 +508,7 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
|
||||
let mut app_state = cx.update(test_app_state);
|
||||
Arc::get_mut(&mut app_state).unwrap().fs = fs;
|
||||
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.add_worktree("/dir".as_ref(), cx)
|
||||
|
@ -536,8 +517,13 @@ mod tests {
|
|||
.unwrap();
|
||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||
.await;
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(app_state.settings.clone(), workspace.clone(), cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
FileFinder::new(
|
||||
params.settings.clone(),
|
||||
workspace.read(cx).project().clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let query = "hi".to_string();
|
||||
finder
|
||||
|
@ -580,14 +566,14 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
async fn test_single_file_worktrees(mut cx: gpui::TestAppContext) {
|
||||
let app_state = cx.update(test_app_state);
|
||||
app_state
|
||||
let params = cx.update(WorkspaceParams::test);
|
||||
params
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree("/root", json!({ "the-parent-dir": { "the-file": "" } }))
|
||||
.await;
|
||||
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.add_worktree(Path::new("/root/the-parent-dir/the-file"), cx)
|
||||
|
@ -596,8 +582,13 @@ mod tests {
|
|||
.unwrap();
|
||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||
.await;
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(app_state.settings.clone(), workspace.clone(), cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
FileFinder::new(
|
||||
params.settings.clone(),
|
||||
workspace.read(cx).project().clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Even though there is only one worktree, that worktree's filename
|
||||
// is included in the matching, because the worktree is a single file.
|
||||
|
@ -628,8 +619,8 @@ mod tests {
|
|||
|
||||
#[gpui::test(retries = 5)]
|
||||
async fn test_multiple_matches_with_same_relative_path(mut cx: gpui::TestAppContext) {
|
||||
let app_state = cx.update(test_app_state);
|
||||
app_state
|
||||
let params = cx.update(WorkspaceParams::test);
|
||||
params
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
|
@ -641,7 +632,7 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
||||
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
|
@ -654,8 +645,13 @@ mod tests {
|
|||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||
.await;
|
||||
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(app_state.settings.clone(), workspace.clone(), cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
FileFinder::new(
|
||||
params.settings.clone(),
|
||||
workspace.read(cx).project().clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Run a search that matches two files with the same relative path.
|
||||
finder
|
8
crates/fuzzy/Cargo.toml
Normal file
8
crates/fuzzy/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "fuzzy"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
util = { path = "../util" }
|
|
@ -1,13 +1,9 @@
|
|||
mod char_bag;
|
||||
|
||||
use crate::{
|
||||
util,
|
||||
worktree::{EntryKind, Snapshot},
|
||||
};
|
||||
use gpui::executor;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{max, min, Ordering},
|
||||
cmp::{self, Ordering},
|
||||
path::Path,
|
||||
sync::atomic::{self, AtomicBool},
|
||||
sync::Arc,
|
||||
|
@ -19,7 +15,7 @@ const BASE_DISTANCE_PENALTY: f64 = 0.6;
|
|||
const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05;
|
||||
const MIN_DISTANCE_PENALTY: f64 = 0.2;
|
||||
|
||||
struct Matcher<'a> {
|
||||
pub struct Matcher<'a> {
|
||||
query: &'a [char],
|
||||
lowercase_query: &'a [char],
|
||||
query_char_bag: CharBag,
|
||||
|
@ -52,7 +48,7 @@ pub struct PathMatchCandidate<'a> {
|
|||
pub struct PathMatch {
|
||||
pub score: f64,
|
||||
pub positions: Vec<usize>,
|
||||
pub tree_id: usize,
|
||||
pub worktree_id: usize,
|
||||
pub path: Arc<Path>,
|
||||
pub path_prefix: Arc<str>,
|
||||
}
|
||||
|
@ -63,6 +59,14 @@ pub struct StringMatchCandidate {
|
|||
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 {
|
||||
fn score(&self) -> f64 {
|
||||
self.score
|
||||
|
@ -152,7 +156,7 @@ impl Ord for PathMatch {
|
|||
self.score
|
||||
.partial_cmp(&other.score)
|
||||
.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)))
|
||||
}
|
||||
}
|
||||
|
@ -213,20 +217,15 @@ pub async fn match_strings(
|
|||
results
|
||||
}
|
||||
|
||||
pub async fn match_paths(
|
||||
snapshots: &[Snapshot],
|
||||
pub async fn match_paths<'a, Set: PathMatchCandidateSet<'a>>(
|
||||
candidate_sets: &'a [Set],
|
||||
query: &str,
|
||||
include_ignored: bool,
|
||||
smart_case: bool,
|
||||
max_results: usize,
|
||||
cancel_flag: &AtomicBool,
|
||||
background: Arc<executor::Background>,
|
||||
) -> Vec<PathMatch> {
|
||||
let path_count: usize = if include_ignored {
|
||||
snapshots.iter().map(Snapshot::file_count).sum()
|
||||
} else {
|
||||
snapshots.iter().map(Snapshot::visible_file_count).sum()
|
||||
};
|
||||
let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum();
|
||||
if path_count == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
@ -259,43 +258,18 @@ pub async fn match_paths(
|
|||
);
|
||||
|
||||
let mut tree_start = 0;
|
||||
for snapshot in snapshots {
|
||||
let tree_end = if include_ignored {
|
||||
tree_start + snapshot.file_count()
|
||||
} else {
|
||||
tree_start + snapshot.visible_file_count()
|
||||
};
|
||||
for candidate_set in candidate_sets {
|
||||
let tree_end = tree_start + candidate_set.len();
|
||||
|
||||
if tree_start < segment_end && segment_start < tree_end {
|
||||
let path_prefix: Arc<str> =
|
||||
if snapshot.root_entry().map_or(false, |e| e.is_file()) {
|
||||
snapshot.root_name().into()
|
||||
} 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!()
|
||||
}
|
||||
});
|
||||
let start = cmp::max(tree_start, segment_start) - tree_start;
|
||||
let end = cmp::min(tree_end, segment_end) - tree_start;
|
||||
let candidates = candidate_set.candidates(start).take(end - start);
|
||||
|
||||
matcher.match_paths(
|
||||
snapshot.id(),
|
||||
path_prefix,
|
||||
paths,
|
||||
candidate_set.id(),
|
||||
candidate_set.prefix(),
|
||||
candidates,
|
||||
results,
|
||||
&cancel_flag,
|
||||
);
|
||||
|
@ -322,7 +296,7 @@ pub async fn match_paths(
|
|||
}
|
||||
|
||||
impl<'a> Matcher<'a> {
|
||||
fn new(
|
||||
pub fn new(
|
||||
query: &'a [char],
|
||||
lowercase_query: &'a [char],
|
||||
query_char_bag: CharBag,
|
||||
|
@ -343,7 +317,7 @@ impl<'a> Matcher<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn match_strings(
|
||||
pub fn match_strings(
|
||||
&mut self,
|
||||
candidates: &[StringMatchCandidate],
|
||||
results: &mut Vec<StringMatch>,
|
||||
|
@ -363,11 +337,11 @@ impl<'a> Matcher<'a> {
|
|||
)
|
||||
}
|
||||
|
||||
fn match_paths(
|
||||
pub fn match_paths<'c: 'a>(
|
||||
&mut self,
|
||||
tree_id: usize,
|
||||
path_prefix: Arc<str>,
|
||||
path_entries: impl Iterator<Item = PathMatchCandidate<'a>>,
|
||||
path_entries: impl Iterator<Item = PathMatchCandidate<'c>>,
|
||||
results: &mut Vec<PathMatch>,
|
||||
cancel_flag: &AtomicBool,
|
||||
) {
|
||||
|
@ -384,7 +358,7 @@ impl<'a> Matcher<'a> {
|
|||
cancel_flag,
|
||||
|candidate, score| PathMatch {
|
||||
score,
|
||||
tree_id,
|
||||
worktree_id: tree_id,
|
||||
positions: Vec::new(),
|
||||
path: candidate.path.clone(),
|
||||
path_prefix: path_prefix.clone(),
|
|
@ -5,15 +5,16 @@ name = "gpui"
|
|||
version = "0.1.0"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
test-support = ["env_logger"]
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.1"
|
||||
gpui_macros = { path = "../gpui_macros" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
async-task = "4.0.3"
|
||||
backtrace = "0.3"
|
||||
ctor = "0.1"
|
||||
env_logger = { version = "0.8", optional = true }
|
||||
etagere = "0.2"
|
||||
gpui_macros = { path = "../gpui_macros" }
|
||||
image = "0.23"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
|
@ -18,7 +18,7 @@ pub struct Label {
|
|||
highlight_indices: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Default)]
|
||||
pub struct LabelStyle {
|
||||
pub text: TextStyle,
|
||||
pub highlight_text: Option<TextStyle>,
|
|
@ -4,11 +4,11 @@ use crate::{
|
|||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::json,
|
||||
sum_tree::{self, Bias, SumTree},
|
||||
DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext,
|
||||
SizeConstraint,
|
||||
};
|
||||
use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
|
||||
pub struct List {
|
||||
state: ListState,
|
|
@ -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 {
|
||||
fn from_json(json: HighlightStyleJson) -> Self {
|
||||
let font_properties = properties_from_json(json.weight, json.italic);
|
|
@ -1,9 +1,8 @@
|
|||
mod app;
|
||||
pub use app::*;
|
||||
mod assets;
|
||||
pub mod sum_tree;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test;
|
||||
pub use assets::*;
|
||||
pub mod elements;
|
||||
pub mod font_cache;
|
|
@ -4,7 +4,7 @@ use crate::geometry::{
|
|||
};
|
||||
use etagere::BucketedAtlasAllocator;
|
||||
use foreign_types::ForeignType;
|
||||
use metal::{self, Device, TextureDescriptor};
|
||||
use metal::{Device, TextureDescriptor};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
pub struct AtlasAllocator {
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue