Merge pull request #202 from zed-industries/crates

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

306
Cargo.lock generated
View file

@ -742,6 +742,30 @@ dependencies = [
"memchr",
]
[[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"

View file

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

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

View file

@ -1,13 +1,13 @@
use super::{Buffer, Content};
use 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 {

View file

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

View file

@ -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 {
languages: vec![Arc::new(rust_language)],
Self::default()
}
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

View file

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

View file

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

View file

@ -1,9 +1,8 @@
use super::Point;
use 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};

View file

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

View file

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

View file

@ -1,13 +1,8 @@
use std::sync::Arc;
use 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
View file

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

View file

@ -1,11 +1,10 @@
use crate::{
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();

View file

@ -1,15 +1,22 @@
use crate::util::ResultExt;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
pub mod channel;
pub mod http;
pub mod user;
use anyhow::{anyhow, Context, Result};
use 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
View file

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

View file

@ -1,7 +1,6 @@
use crate::{
use super::{
http::{HttpClient, Method, Request, Url},
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
View file

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

View file

@ -61,8 +61,8 @@ impl<'a> AddAssign<&'a Local> for Local {
#[derive(Clone, Default, Hash, Eq, PartialEq)]
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
View file

@ -0,0 +1,31 @@
[package]
name = "editor"
version = "0.1.0"
edition = "2018"
[features]
test-support = ["buffer/test-support", "gpui/test-support"]
[dependencies]
buffer = { path = "../buffer" }
clock = { path = "../clock" }
gpui = { path = "../gpui" }
sum_tree = { path = "../sum_tree" }
theme = { path = "../theme" }
util = { path = "../util" }
anyhow = "1.0"
lazy_static = "1.4"
log = "0.4"
parking_lot = "0.11"
postage = { version = "0.4", features = ["futures-traits"] }
serde = { version = "1", features = ["derive", "rc"] }
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
[dev-dependencies]
buffer = { path = "../buffer", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
rand = "0.8"
unindent = "0.1.7"
tree-sitter = "0.19"
tree-sitter-rust = "0.19"

View file

@ -2,14 +2,19 @@ mod fold_map;
mod tab_map;
mod 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);

View file

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

View file

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

View file

@ -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,
},
test::Observer,
util::RandomCharIter,
};
use buffer::{Buffer, RandomCharIter};
use rand::prelude::*;
use std::env;

View file

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

View file

@ -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,16 +2428,20 @@ impl Snapshot {
}
}
impl EditorStyle {
impl EditorSettings {
#[cfg(any(test, feature = "test-support"))]
pub fn test(font_cache: &gpui::FontCache) -> Self {
pub fn test(cx: &AppContext) -> Self {
Self {
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();
Self {
EditorStyle {
text: TextStyle {
font_family_name,
font_family_id,
@ -2464,11 +2459,10 @@ impl EditorStyle {
line_number_active: Default::default(),
selection: Default::default(),
guest_selections: Default::default(),
syntax: Default::default(),
}
},
}
fn placeholder_text(&self) -> &TextStyle {
self.placeholder_text.as_ref().unwrap_or(&self.text)
}
}
@ -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 clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
where
Self: Sized,
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
{
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)
*display_end.row_mut() -= 1;
}
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(())
}))
}
let (display_start, buffer_start) = map.prev_row_boundary(display_start);
let (display_end, buffer_end) = map.next_row_boundary(display_end);
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))
SpannedRows {
buffer_rows: buffer_start.row..buffer_end.row + 1,
display_rows: display_start.row()..display_end.row() + 1,
}
fn is_dirty(&self, cx: &AppContext) -> bool {
self.buffer.read(cx).is_dirty()
}
fn has_conflict(&self, cx: &AppContext) -> bool {
self.buffer.read(cx).has_conflict()
}
}
#[cfg(test)]
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)
}
}

View file

@ -196,7 +196,7 @@ fn char_kind(c: char) -> CharKind {
#[cfg(test)]
mod tests {
use super::*;
use crate::editor::{display_map::DisplayMap, Buffer};
use crate::{display_map::DisplayMap, Buffer};
#[gpui::test]
fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) {

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

@ -0,0 +1,39 @@
use gpui::{Entity, ModelHandle};
use smol::channel;
use std::marker::PhantomData;
pub fn sample_text(rows: usize, cols: usize) -> String {
let mut text = String::new();
for row in 0..rows {
let c: char = ('a' as u32 + row as u32) as u8 as char;
let mut line = c.to_string().repeat(cols);
if row < rows - 1 {
line.push('\n');
}
text += &line;
}
text
}
pub struct Observer<T>(PhantomData<T>);
impl<T: 'static> Entity for Observer<T> {
type Event = ();
}
impl<T: Entity> Observer<T> {
pub fn new(
handle: &ModelHandle<T>,
cx: &mut gpui::TestAppContext,
) -> (ModelHandle<Self>, channel::Receiver<()>) {
let (notify_tx, notify_rx) = channel::unbounded();
let observer = cx.add_model(|cx| {
cx.observe(handle, move |_, _, _| {
let _ = notify_tx.try_send(());
})
.detach();
Observer(PhantomData)
});
(observer, notify_rx)
}
}

View file

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

View file

@ -1,10 +1,5 @@
use crate::{
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,39 +368,29 @@ 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,
)
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| {
@ -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(&params, 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(&params, 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(&params, 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(&params, 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
View file

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

View file

@ -1,13 +1,9 @@
mod char_bag;
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(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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