Merge branch 'master' into menus
This commit is contained in:
commit
00d7dafbba
38 changed files with 1105 additions and 186 deletions
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -924,6 +924,8 @@ dependencies = [
|
||||||
"rand 0.8.3",
|
"rand 0.8.3",
|
||||||
"replace_with",
|
"replace_with",
|
||||||
"resvg",
|
"resvg",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"simplelog",
|
"simplelog",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smol",
|
"smol",
|
||||||
|
@ -932,6 +934,12 @@ dependencies = [
|
||||||
"usvg",
|
"usvg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
|
@ -965,6 +973,16 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
|
@ -1668,6 +1686,12 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "seahash"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -1685,9 +1709,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.124"
|
version = "1.0.125"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f"
|
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
|
@ -1695,6 +1719,7 @@ version = "1.0.64"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
|
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2245,6 +2270,7 @@ dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rand 0.8.3",
|
"rand 0.8.3",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
|
"seahash",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"simplelog",
|
"simplelog",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
|
|
@ -4,7 +4,11 @@
|
||||||
|
|
||||||
Welcome to Zed, a lightning-fast, collaborative code editor that makes your dreams come true.
|
Welcome to Zed, a lightning-fast, collaborative code editor that makes your dreams come true.
|
||||||
|
|
||||||
Everything is under construction, including this README, but in the meantime, here is a high-level roadmap:
|
## Development tips
|
||||||
|
|
||||||
|
### Dump element JSON
|
||||||
|
|
||||||
|
If you trigger `cmd-shift-i`, Zed will copy a JSON representation of the current window contents to the clipboard. You can paste this in a tool like [DJSON](https://chrome.google.com/webstore/detail/djson-json-viewer-formatt/chaeijjekipecdajnijdldjjipaegdjc?hl=en) to navigate the state of on-screen elements in a structured way.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ pathfinder_geometry = "0.5"
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
replace_with = "0.1.7"
|
replace_with = "0.1.7"
|
||||||
resvg = "0.14"
|
resvg = "0.14"
|
||||||
|
serde = "1.0.125"
|
||||||
|
serde_json = "1.0.64"
|
||||||
smallvec = "1.6.1"
|
smallvec = "1.6.1"
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
tiny-skia = "0.5"
|
tiny-skia = "0.5"
|
||||||
|
|
|
@ -2,9 +2,10 @@ use gpui::{
|
||||||
color::ColorU,
|
color::ColorU,
|
||||||
fonts::{Properties, Weight},
|
fonts::{Properties, Weight},
|
||||||
platform::{current as platform, Runner},
|
platform::{current as platform, Runner},
|
||||||
Element as _, Quad,
|
DebugContext, Element as _, Quad,
|
||||||
};
|
};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
|
use pathfinder_geometry::rect::RectF;
|
||||||
use simplelog::SimpleLogger;
|
use simplelog::SimpleLogger;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -59,7 +60,7 @@ impl gpui::Element for TextElement {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: pathfinder_geometry::rect::RectF,
|
bounds: RectF,
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
ctx: &mut gpui::PaintContext,
|
ctx: &mut gpui::PaintContext,
|
||||||
) -> Self::PaintState {
|
) -> Self::PaintState {
|
||||||
|
@ -109,11 +110,21 @@ impl gpui::Element for TextElement {
|
||||||
fn dispatch_event(
|
fn dispatch_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &gpui::Event,
|
_: &gpui::Event,
|
||||||
_: pathfinder_geometry::rect::RectF,
|
_: RectF,
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
_: &mut Self::PaintState,
|
_: &mut Self::PaintState,
|
||||||
_: &mut gpui::EventContext,
|
_: &mut gpui::EventContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &DebugContext,
|
||||||
|
) -> gpui::json::Value {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -324,6 +324,7 @@ pub struct MutableAppContext {
|
||||||
window_invalidations: HashMap<usize, WindowInvalidation>,
|
window_invalidations: HashMap<usize, WindowInvalidation>,
|
||||||
invalidation_callbacks:
|
invalidation_callbacks:
|
||||||
HashMap<usize, Box<dyn FnMut(WindowInvalidation, &mut MutableAppContext)>>,
|
HashMap<usize, Box<dyn FnMut(WindowInvalidation, &mut MutableAppContext)>>,
|
||||||
|
debug_elements_callbacks: HashMap<usize, Box<dyn Fn(&AppContext) -> crate::json::Value>>,
|
||||||
foreground: Rc<executor::Foreground>,
|
foreground: Rc<executor::Foreground>,
|
||||||
future_handlers: Rc<RefCell<HashMap<usize, FutureHandler>>>,
|
future_handlers: Rc<RefCell<HashMap<usize, FutureHandler>>>,
|
||||||
stream_handlers: Rc<RefCell<HashMap<usize, StreamHandler>>>,
|
stream_handlers: Rc<RefCell<HashMap<usize, StreamHandler>>>,
|
||||||
|
@ -361,6 +362,7 @@ impl MutableAppContext {
|
||||||
observations: HashMap::new(),
|
observations: HashMap::new(),
|
||||||
window_invalidations: HashMap::new(),
|
window_invalidations: HashMap::new(),
|
||||||
invalidation_callbacks: HashMap::new(),
|
invalidation_callbacks: HashMap::new(),
|
||||||
|
debug_elements_callbacks: HashMap::new(),
|
||||||
foreground,
|
foreground,
|
||||||
future_handlers: Default::default(),
|
future_handlers: Default::default(),
|
||||||
stream_handlers: Default::default(),
|
stream_handlers: Default::default(),
|
||||||
|
@ -391,16 +393,29 @@ impl MutableAppContext {
|
||||||
&self.ctx.background
|
&self.ctx.background
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_window_invalidated<F: 'static + FnMut(WindowInvalidation, &mut MutableAppContext)>(
|
pub fn on_window_invalidated<F>(&mut self, window_id: usize, callback: F)
|
||||||
&mut self,
|
where
|
||||||
window_id: usize,
|
F: 'static + FnMut(WindowInvalidation, &mut MutableAppContext),
|
||||||
callback: F,
|
{
|
||||||
) {
|
|
||||||
self.invalidation_callbacks
|
self.invalidation_callbacks
|
||||||
.insert(window_id, Box::new(callback));
|
.insert(window_id, Box::new(callback));
|
||||||
self.update_windows();
|
self.update_windows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_debug_elements<F>(&mut self, window_id: usize, callback: F)
|
||||||
|
where
|
||||||
|
F: 'static + Fn(&AppContext) -> crate::json::Value,
|
||||||
|
{
|
||||||
|
self.debug_elements_callbacks
|
||||||
|
.insert(window_id, Box::new(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debug_elements(&self, window_id: usize) -> Option<crate::json::Value> {
|
||||||
|
self.debug_elements_callbacks
|
||||||
|
.get(&window_id)
|
||||||
|
.map(|debug_elements| debug_elements(&self.ctx))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_action<S, V, T, F>(&mut self, name: S, mut handler: F)
|
pub fn add_action<S, V, T, F>(&mut self, name: S, mut handler: F)
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
|
@ -710,11 +725,19 @@ impl MutableAppContext {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.on_window_invalidated(window_id, move |invalidation, ctx| {
|
{
|
||||||
let mut presenter = presenter.borrow_mut();
|
let presenter = presenter.clone();
|
||||||
presenter.invalidate(invalidation, ctx.downgrade());
|
self.on_window_invalidated(window_id, move |invalidation, ctx| {
|
||||||
let scene = presenter.build_scene(window.size(), window.scale_factor(), ctx);
|
let mut presenter = presenter.borrow_mut();
|
||||||
window.present_scene(scene);
|
presenter.invalidate(invalidation, ctx.downgrade());
|
||||||
|
let scene =
|
||||||
|
presenter.build_scene(window.size(), window.scale_factor(), ctx);
|
||||||
|
window.present_scene(scene);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.on_debug_elements(window_id, move |ctx| {
|
||||||
|
presenter.borrow().debug_elements(ctx).unwrap()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1115,6 +1138,10 @@ impl MutableAppContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn copy(&self, text: &str) {
|
||||||
|
self.platform.copy(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelAsRef for MutableAppContext {
|
impl ModelAsRef for MutableAppContext {
|
||||||
|
@ -1591,6 +1618,10 @@ impl<'a, T: View> ViewContext<'a, T> {
|
||||||
&self.app.ctx.background
|
&self.app.ctx.background
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn debug_elements(&self) -> crate::json::Value {
|
||||||
|
self.app.debug_elements(self.window_id).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn focus<S>(&mut self, handle: S)
|
pub fn focus<S>(&mut self, handle: S)
|
||||||
where
|
where
|
||||||
S: Into<AnyViewHandle>,
|
S: Into<AnyViewHandle>,
|
||||||
|
|
9
gpui/src/color.rs
Normal file
9
gpui/src/color.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use crate::json::ToJson;
|
||||||
|
pub use pathfinder_color::*;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
impl ToJson for ColorU {
|
||||||
|
fn to_json(&self) -> serde_json::Value {
|
||||||
|
json!(format!("0x{:x}{:x}{:x}", self.r, self.g, self.b))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
json, AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext,
|
||||||
SizeConstraint,
|
LayoutContext, PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
use json::ToJson;
|
||||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
pub struct Align {
|
pub struct Align {
|
||||||
child: ElementBox,
|
child: ElementBox,
|
||||||
|
@ -79,4 +81,19 @@ impl Element for Align {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
self.child.dispatch_event(event, ctx)
|
self.child.dispatch_event(event, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: pathfinder_geometry::rect::RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &DebugContext,
|
||||||
|
) -> json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "Align",
|
||||||
|
"bounds": bounds.to_json(),
|
||||||
|
"alignment": self.alignment.to_json(),
|
||||||
|
"child": self.child.debug(ctx),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
use super::Element;
|
use super::Element;
|
||||||
use crate::PaintContext;
|
use crate::{
|
||||||
|
json::{self, json},
|
||||||
|
DebugContext, PaintContext,
|
||||||
|
};
|
||||||
|
use json::ToJson;
|
||||||
use pathfinder_geometry::{
|
use pathfinder_geometry::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
|
@ -70,4 +74,14 @@ where
|
||||||
) -> bool {
|
) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &DebugContext,
|
||||||
|
) -> json::Value {
|
||||||
|
json!({"type": "Canvas", "bounds": bounds.to_json()})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
use json::ToJson;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
SizeConstraint,
|
json, AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext,
|
||||||
|
LayoutContext, PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
use pathfinder_geometry::vector::Vector2F;
|
|
||||||
|
|
||||||
pub struct ConstrainedBox {
|
pub struct ConstrainedBox {
|
||||||
child: ElementBox,
|
child: ElementBox,
|
||||||
|
@ -63,7 +66,7 @@ impl Element for ConstrainedBox {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: pathfinder_geometry::rect::RectF,
|
bounds: RectF,
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
ctx: &mut PaintContext,
|
ctx: &mut PaintContext,
|
||||||
) -> Self::PaintState {
|
) -> Self::PaintState {
|
||||||
|
@ -73,11 +76,21 @@ impl Element for ConstrainedBox {
|
||||||
fn dispatch_event(
|
fn dispatch_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
_: pathfinder_geometry::rect::RectF,
|
_: RectF,
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
_: &mut Self::PaintState,
|
_: &mut Self::PaintState,
|
||||||
ctx: &mut EventContext,
|
ctx: &mut EventContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
self.child.dispatch_event(event, ctx)
|
self.child.dispatch_event(event, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &DebugContext,
|
||||||
|
) -> json::Value {
|
||||||
|
json!({"type": "ConstrainedBox", "constraint": self.constraint.to_json(), "child": self.child.debug(ctx)})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use pathfinder_geometry::rect::RectF;
|
use pathfinder_geometry::rect::RectF;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color::ColorU,
|
color::ColorU,
|
||||||
geometry::vector::{vec2f, Vector2F},
|
geometry::vector::{vec2f, Vector2F},
|
||||||
|
json::ToJson,
|
||||||
scene::{self, Border, Quad},
|
scene::{self, Border, Quad},
|
||||||
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
||||||
SizeConstraint,
|
SizeConstraint,
|
||||||
|
@ -189,6 +191,28 @@ impl Element for Container {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
self.child.dispatch_event(event, ctx)
|
self.child.dispatch_event(event, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &crate::DebugContext,
|
||||||
|
) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "Container",
|
||||||
|
"bounds": bounds.to_json(),
|
||||||
|
"details": {
|
||||||
|
"margin": self.margin.to_json(),
|
||||||
|
"padding": self.padding.to_json(),
|
||||||
|
"background_color": self.background_color.to_json(),
|
||||||
|
"border": self.border.to_json(),
|
||||||
|
"corner_radius": self.corner_radius,
|
||||||
|
"shadow": self.shadow.to_json(),
|
||||||
|
},
|
||||||
|
"child": self.child.debug(ctx),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -199,6 +223,25 @@ pub struct Margin {
|
||||||
right: f32,
|
right: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToJson for Margin {
|
||||||
|
fn to_json(&self) -> serde_json::Value {
|
||||||
|
let mut value = json!({});
|
||||||
|
if self.top > 0. {
|
||||||
|
value["top"] = json!(self.top);
|
||||||
|
}
|
||||||
|
if self.right > 0. {
|
||||||
|
value["right"] = json!(self.right);
|
||||||
|
}
|
||||||
|
if self.bottom > 0. {
|
||||||
|
value["bottom"] = json!(self.bottom);
|
||||||
|
}
|
||||||
|
if self.left > 0. {
|
||||||
|
value["left"] = json!(self.left);
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Padding {
|
pub struct Padding {
|
||||||
top: f32,
|
top: f32,
|
||||||
|
@ -207,9 +250,38 @@ pub struct Padding {
|
||||||
right: f32,
|
right: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToJson for Padding {
|
||||||
|
fn to_json(&self) -> serde_json::Value {
|
||||||
|
let mut value = json!({});
|
||||||
|
if self.top > 0. {
|
||||||
|
value["top"] = json!(self.top);
|
||||||
|
}
|
||||||
|
if self.right > 0. {
|
||||||
|
value["right"] = json!(self.right);
|
||||||
|
}
|
||||||
|
if self.bottom > 0. {
|
||||||
|
value["bottom"] = json!(self.bottom);
|
||||||
|
}
|
||||||
|
if self.left > 0. {
|
||||||
|
value["left"] = json!(self.left);
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Shadow {
|
pub struct Shadow {
|
||||||
offset: Vector2F,
|
offset: Vector2F,
|
||||||
blur: f32,
|
blur: f32,
|
||||||
color: ColorU,
|
color: ColorU,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToJson for Shadow {
|
||||||
|
fn to_json(&self) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"offset": self.offset.to_json(),
|
||||||
|
"blur": self.blur,
|
||||||
|
"color": self.color.to_json()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
use crate::geometry::{
|
use crate::{
|
||||||
rect::RectF,
|
geometry::{
|
||||||
vector::{vec2f, Vector2F},
|
rect::RectF,
|
||||||
|
vector::{vec2f, Vector2F},
|
||||||
|
},
|
||||||
|
json::{json, ToJson},
|
||||||
|
DebugContext,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
||||||
|
@ -58,4 +62,17 @@ impl Element for Empty {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &DebugContext,
|
||||||
|
) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "Empty",
|
||||||
|
"bounds": bounds.to_json(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
use pathfinder_geometry::rect::RectF;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::vector::Vector2F, AfterLayoutContext, Element, ElementBox, Event, EventContext,
|
geometry::vector::Vector2F, AfterLayoutContext, DebugContext, Element, ElementBox, Event,
|
||||||
LayoutContext, PaintContext, SizeConstraint,
|
EventContext, LayoutContext, PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
|
@ -49,7 +52,7 @@ impl Element for EventHandler {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: pathfinder_geometry::rect::RectF,
|
bounds: RectF,
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
ctx: &mut PaintContext,
|
ctx: &mut PaintContext,
|
||||||
) -> Self::PaintState {
|
) -> Self::PaintState {
|
||||||
|
@ -59,7 +62,7 @@ impl Element for EventHandler {
|
||||||
fn dispatch_event(
|
fn dispatch_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
bounds: pathfinder_geometry::rect::RectF,
|
bounds: RectF,
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
_: &mut Self::PaintState,
|
_: &mut Self::PaintState,
|
||||||
ctx: &mut EventContext,
|
ctx: &mut EventContext,
|
||||||
|
@ -80,4 +83,17 @@ impl Element for EventHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &DebugContext,
|
||||||
|
) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "EventHandler",
|
||||||
|
"child": self.child.debug(ctx),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AfterLayoutContext, Axis, Element, ElementBox, Event, EventContext, LayoutContext,
|
json::{self, ToJson, Value},
|
||||||
PaintContext, SizeConstraint, Vector2FExt,
|
AfterLayoutContext, Axis, DebugContext, Element, ElementBox, Event, EventContext,
|
||||||
|
LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
|
||||||
};
|
};
|
||||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
use pathfinder_geometry::{
|
||||||
|
rect::RectF,
|
||||||
|
vector::{vec2f, Vector2F},
|
||||||
|
};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
pub struct Flex {
|
pub struct Flex {
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
|
@ -130,7 +135,7 @@ impl Element for Flex {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: pathfinder_geometry::rect::RectF,
|
bounds: RectF,
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
ctx: &mut PaintContext,
|
ctx: &mut PaintContext,
|
||||||
) -> Self::PaintState {
|
) -> Self::PaintState {
|
||||||
|
@ -147,7 +152,7 @@ impl Element for Flex {
|
||||||
fn dispatch_event(
|
fn dispatch_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
_: pathfinder_geometry::rect::RectF,
|
_: RectF,
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
_: &mut Self::PaintState,
|
_: &mut Self::PaintState,
|
||||||
ctx: &mut EventContext,
|
ctx: &mut EventContext,
|
||||||
|
@ -158,6 +163,21 @@ impl Element for Flex {
|
||||||
}
|
}
|
||||||
handled
|
handled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &DebugContext,
|
||||||
|
) -> json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "Flex",
|
||||||
|
"bounds": bounds.to_json(),
|
||||||
|
"axis": self.axis.to_json(),
|
||||||
|
"children": self.children.iter().map(|child| child.debug(ctx)).collect::<Vec<json::Value>>()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FlexParentData {
|
struct FlexParentData {
|
||||||
|
@ -202,7 +222,7 @@ impl Element for Expanded {
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: pathfinder_geometry::rect::RectF,
|
bounds: RectF,
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
ctx: &mut PaintContext,
|
ctx: &mut PaintContext,
|
||||||
) -> Self::PaintState {
|
) -> Self::PaintState {
|
||||||
|
@ -212,7 +232,7 @@ impl Element for Expanded {
|
||||||
fn dispatch_event(
|
fn dispatch_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
_: pathfinder_geometry::rect::RectF,
|
_: RectF,
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
_: &mut Self::PaintState,
|
_: &mut Self::PaintState,
|
||||||
ctx: &mut EventContext,
|
ctx: &mut EventContext,
|
||||||
|
@ -223,4 +243,18 @@ impl Element for Expanded {
|
||||||
fn metadata(&self) -> Option<&dyn Any> {
|
fn metadata(&self) -> Option<&dyn Any> {
|
||||||
Some(&self.metadata)
|
Some(&self.metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &DebugContext,
|
||||||
|
) -> Value {
|
||||||
|
json!({
|
||||||
|
"type": "Expanded",
|
||||||
|
"flex": self.metadata.flex,
|
||||||
|
"child": self.child.debug(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color::ColorU,
|
color::ColorU,
|
||||||
font_cache::FamilyId,
|
font_cache::FamilyId,
|
||||||
|
@ -6,8 +8,10 @@ use crate::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
|
json::{ToJson, Value},
|
||||||
text_layout::Line,
|
text_layout::Line,
|
||||||
AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
AfterLayoutContext, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext,
|
||||||
|
SizeConstraint,
|
||||||
};
|
};
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
|
|
||||||
|
@ -152,4 +156,32 @@ impl Element for Label {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &DebugContext,
|
||||||
|
) -> Value {
|
||||||
|
json!({
|
||||||
|
"type": "Label",
|
||||||
|
"bounds": bounds.to_json(),
|
||||||
|
"font_family": ctx.font_cache.family_name(self.family_id).unwrap(),
|
||||||
|
"font_size": self.font_size,
|
||||||
|
"font_properties": self.font_properties.to_json(),
|
||||||
|
"text": &self.text,
|
||||||
|
"highlights": self.highlights.to_json(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToJson for Highlights {
|
||||||
|
fn to_json(&self) -> Value {
|
||||||
|
json!({
|
||||||
|
"color": self.color.to_json(),
|
||||||
|
"indices": self.indices,
|
||||||
|
"font_properties": self.font_properties.to_json(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
font_cache::FamilyId,
|
font_cache::FamilyId,
|
||||||
fonts::Properties,
|
fonts::Properties,
|
||||||
geometry::vector::{vec2f, Vector2F},
|
geometry::{
|
||||||
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
rect::RectF,
|
||||||
SizeConstraint,
|
vector::{vec2f, Vector2F},
|
||||||
|
},
|
||||||
|
json::{json, ToJson},
|
||||||
|
AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext,
|
||||||
|
PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct LineBox {
|
pub struct LineBox {
|
||||||
|
@ -85,4 +89,20 @@ impl Element for LineBox {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
self.child.dispatch_event(event, ctx)
|
self.child.dispatch_event(event, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &DebugContext,
|
||||||
|
) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"bounds": bounds.to_json(),
|
||||||
|
"font_family": ctx.font_cache.family_name(self.family_id).unwrap(),
|
||||||
|
"font_size": self.font_size,
|
||||||
|
"font_properties": self.font_properties.to_json(),
|
||||||
|
"child": self.child.debug(ctx),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
AfterLayoutContext, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
json, AfterLayoutContext, DebugContext, Event, EventContext, LayoutContext, PaintContext,
|
||||||
|
SizeConstraint,
|
||||||
};
|
};
|
||||||
use core::panic;
|
use core::panic;
|
||||||
use replace_with::replace_with_or_abort;
|
use replace_with::replace_with_or_abort;
|
||||||
use std::any::Any;
|
use std::{any::Any, borrow::Cow};
|
||||||
|
|
||||||
trait AnyElement {
|
trait AnyElement {
|
||||||
fn layout(&mut self, constraint: SizeConstraint, ctx: &mut LayoutContext) -> Vector2F;
|
fn layout(&mut self, constraint: SizeConstraint, ctx: &mut LayoutContext) -> Vector2F;
|
||||||
fn after_layout(&mut self, _: &mut AfterLayoutContext) {}
|
fn after_layout(&mut self, _: &mut AfterLayoutContext) {}
|
||||||
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext);
|
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext);
|
||||||
fn dispatch_event(&mut self, event: &Event, ctx: &mut EventContext) -> bool;
|
fn dispatch_event(&mut self, event: &Event, ctx: &mut EventContext) -> bool;
|
||||||
|
fn debug(&self, ctx: &DebugContext) -> serde_json::Value;
|
||||||
|
|
||||||
fn size(&self) -> Vector2F;
|
fn size(&self) -> Vector2F;
|
||||||
fn metadata(&self) -> Option<&dyn Any>;
|
fn metadata(&self) -> Option<&dyn Any>;
|
||||||
|
@ -53,11 +55,32 @@ pub trait Element {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
layout: &Self::LayoutState,
|
||||||
|
paint: &Self::PaintState,
|
||||||
|
ctx: &DebugContext,
|
||||||
|
) -> serde_json::Value;
|
||||||
|
|
||||||
fn boxed(self) -> ElementBox
|
fn boxed(self) -> ElementBox
|
||||||
where
|
where
|
||||||
Self: 'static + Sized,
|
Self: 'static + Sized,
|
||||||
{
|
{
|
||||||
ElementBox(Box::new(Lifecycle::Init { element: self }))
|
ElementBox {
|
||||||
|
name: None,
|
||||||
|
element: Box::new(Lifecycle::Init { element: self }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn named(self, name: impl Into<Cow<'static, str>>) -> ElementBox
|
||||||
|
where
|
||||||
|
Self: 'static + Sized,
|
||||||
|
{
|
||||||
|
ElementBox {
|
||||||
|
name: Some(name.into()),
|
||||||
|
element: Box::new(Lifecycle::Init { element: self }),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +100,10 @@ pub enum Lifecycle<T: Element> {
|
||||||
paint: T::PaintState,
|
paint: T::PaintState,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
pub struct ElementBox(Box<dyn AnyElement>);
|
pub struct ElementBox {
|
||||||
|
name: Option<Cow<'static, str>>,
|
||||||
|
element: Box<dyn AnyElement>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Element> AnyElement for Lifecycle<T> {
|
impl<T: Element> AnyElement for Lifecycle<T> {
|
||||||
fn layout(&mut self, constraint: SizeConstraint, ctx: &mut LayoutContext) -> Vector2F {
|
fn layout(&mut self, constraint: SizeConstraint, ctx: &mut LayoutContext) -> Vector2F {
|
||||||
|
@ -165,30 +191,57 @@ impl<T: Element> AnyElement for Lifecycle<T> {
|
||||||
| Lifecycle::PostPaint { element, .. } => element.metadata(),
|
| Lifecycle::PostPaint { element, .. } => element.metadata(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(&self, ctx: &DebugContext) -> serde_json::Value {
|
||||||
|
match self {
|
||||||
|
Lifecycle::PostPaint {
|
||||||
|
element,
|
||||||
|
bounds,
|
||||||
|
layout,
|
||||||
|
paint,
|
||||||
|
} => element.debug(*bounds, layout, paint, ctx),
|
||||||
|
_ => panic!("invalid element lifecycle state"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElementBox {
|
impl ElementBox {
|
||||||
pub fn layout(&mut self, constraint: SizeConstraint, ctx: &mut LayoutContext) -> Vector2F {
|
pub fn layout(&mut self, constraint: SizeConstraint, ctx: &mut LayoutContext) -> Vector2F {
|
||||||
self.0.layout(constraint, ctx)
|
self.element.layout(constraint, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn after_layout(&mut self, ctx: &mut AfterLayoutContext) {
|
pub fn after_layout(&mut self, ctx: &mut AfterLayoutContext) {
|
||||||
self.0.after_layout(ctx);
|
self.element.after_layout(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext) {
|
pub fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext) {
|
||||||
self.0.paint(origin, ctx);
|
self.element.paint(origin, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch_event(&mut self, event: &Event, ctx: &mut EventContext) -> bool {
|
pub fn dispatch_event(&mut self, event: &Event, ctx: &mut EventContext) -> bool {
|
||||||
self.0.dispatch_event(event, ctx)
|
self.element.dispatch_event(event, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> Vector2F {
|
pub fn size(&self) -> Vector2F {
|
||||||
self.0.size()
|
self.element.size()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metadata(&self) -> Option<&dyn Any> {
|
pub fn metadata(&self) -> Option<&dyn Any> {
|
||||||
self.0.metadata()
|
self.element.metadata()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debug(&self, ctx: &DebugContext) -> json::Value {
|
||||||
|
let mut value = self.element.debug(ctx);
|
||||||
|
|
||||||
|
if let Some(name) = &self.name {
|
||||||
|
if let json::Value::Object(map) = &mut value {
|
||||||
|
let mut new_map: crate::json::Map<String, serde_json::Value> = Default::default();
|
||||||
|
new_map.insert("name".into(), json::Value::String(name.to_string()));
|
||||||
|
new_map.append(map);
|
||||||
|
return json::Value::Object(new_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
json::{self, json, ToJson},
|
||||||
SizeConstraint,
|
AfterLayoutContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext,
|
||||||
|
PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Stack {
|
pub struct Stack {
|
||||||
|
@ -71,6 +72,20 @@ impl Element for Stack {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &DebugContext,
|
||||||
|
) -> json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "Stack",
|
||||||
|
"bounds": bounds.to_json(),
|
||||||
|
"children": self.children.iter().map(|child| child.debug(ctx)).collect::<Vec<json::Value>>()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Extend<ElementBox> for Stack {
|
impl Extend<ElementBox> for Stack {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color::ColorU,
|
color::ColorU,
|
||||||
geometry::{
|
geometry::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
scene, AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext,
|
scene, AfterLayoutContext, DebugContext, Element, Event, EventContext, LayoutContext,
|
||||||
SizeConstraint,
|
PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Svg {
|
pub struct Svg {
|
||||||
|
@ -86,8 +88,25 @@ impl Element for Svg {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &DebugContext,
|
||||||
|
) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "Svg",
|
||||||
|
"bounds": bounds.to_json(),
|
||||||
|
"path": self.path,
|
||||||
|
"color": self.color.to_json(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::json::ToJson;
|
||||||
|
|
||||||
fn from_usvg_rect(rect: usvg::Rect) -> RectF {
|
fn from_usvg_rect(rect: usvg::Rect) -> RectF {
|
||||||
RectF::new(
|
RectF::new(
|
||||||
vec2f(rect.x() as f32, rect.y() as f32),
|
vec2f(rect.x() as f32, rect.y() as f32),
|
||||||
|
|
|
@ -7,8 +7,10 @@ use crate::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
|
json::{self, json},
|
||||||
ElementBox,
|
ElementBox,
|
||||||
};
|
};
|
||||||
|
use json::ToJson;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{cmp, ops::Range, sync::Arc};
|
use std::{cmp, ops::Range, sync::Arc};
|
||||||
|
|
||||||
|
@ -236,4 +238,21 @@ where
|
||||||
|
|
||||||
handled
|
handled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
layout: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &crate::DebugContext,
|
||||||
|
) -> json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "UniformList",
|
||||||
|
"bounds": bounds.to_json(),
|
||||||
|
"scroll_max": layout.scroll_max,
|
||||||
|
"item_height": layout.item_height,
|
||||||
|
"items": layout.items.iter().map(|item| item.debug(ctx)).collect::<Vec<json::Value>>()
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,15 @@ impl FontCache {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn family_name(&self, family_id: FamilyId) -> Result<String> {
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.families
|
||||||
|
.get(family_id.0)
|
||||||
|
.ok_or_else(|| anyhow!("invalid family id"))
|
||||||
|
.map(|family| family.name.clone())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_family(&self, names: &[&str]) -> Result<FamilyId> {
|
pub fn load_family(&self, names: &[&str]) -> Result<FamilyId> {
|
||||||
for name in names {
|
for name in names {
|
||||||
let state = self.0.upgradable_read();
|
let state = self.0.upgradable_read();
|
||||||
|
|
|
@ -1,7 +1,62 @@
|
||||||
|
use crate::json::json;
|
||||||
pub use font_kit::metrics::Metrics;
|
pub use font_kit::metrics::Metrics;
|
||||||
pub use font_kit::properties::{Properties, Weight};
|
pub use font_kit::properties::{Properties, Stretch, Style, Weight};
|
||||||
|
|
||||||
|
use crate::json::ToJson;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub struct FontId(pub usize);
|
pub struct FontId(pub usize);
|
||||||
|
|
||||||
pub type GlyphId = u32;
|
pub type GlyphId = u32;
|
||||||
|
|
||||||
|
impl ToJson for Properties {
|
||||||
|
fn to_json(&self) -> crate::json::Value {
|
||||||
|
json!({
|
||||||
|
"style": self.style.to_json(),
|
||||||
|
"weight": self.weight.to_json(),
|
||||||
|
"stretch": self.stretch.to_json(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToJson for Style {
|
||||||
|
fn to_json(&self) -> crate::json::Value {
|
||||||
|
match self {
|
||||||
|
Style::Normal => json!("normal"),
|
||||||
|
Style::Italic => json!("italic"),
|
||||||
|
Style::Oblique => json!("oblique"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToJson for Weight {
|
||||||
|
fn to_json(&self) -> crate::json::Value {
|
||||||
|
if self.0 == Weight::THIN.0 {
|
||||||
|
json!("thin")
|
||||||
|
} else if self.0 == Weight::EXTRA_LIGHT.0 {
|
||||||
|
json!("extra light")
|
||||||
|
} else if self.0 == Weight::LIGHT.0 {
|
||||||
|
json!("light")
|
||||||
|
} else if self.0 == Weight::NORMAL.0 {
|
||||||
|
json!("normal")
|
||||||
|
} else if self.0 == Weight::MEDIUM.0 {
|
||||||
|
json!("medium")
|
||||||
|
} else if self.0 == Weight::SEMIBOLD.0 {
|
||||||
|
json!("semibold")
|
||||||
|
} else if self.0 == Weight::BOLD.0 {
|
||||||
|
json!("bold")
|
||||||
|
} else if self.0 == Weight::EXTRA_BOLD.0 {
|
||||||
|
json!("extra bold")
|
||||||
|
} else if self.0 == Weight::BLACK.0 {
|
||||||
|
json!("black")
|
||||||
|
} else {
|
||||||
|
json!(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToJson for Stretch {
|
||||||
|
fn to_json(&self) -> serde_json::Value {
|
||||||
|
json!(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use super::scene::{Path, PathVertex};
|
use super::scene::{Path, PathVertex};
|
||||||
use crate::color::ColorU;
|
use crate::{color::ColorU, json::ToJson};
|
||||||
pub use pathfinder_geometry::*;
|
pub use pathfinder_geometry::*;
|
||||||
use rect::RectF;
|
use rect::RectF;
|
||||||
|
use serde_json::json;
|
||||||
use vector::{vec2f, Vector2F};
|
use vector::{vec2f, Vector2F};
|
||||||
|
|
||||||
pub struct PathBuilder {
|
pub struct PathBuilder {
|
||||||
|
@ -106,3 +107,15 @@ impl PathBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToJson for Vector2F {
|
||||||
|
fn to_json(&self) -> serde_json::Value {
|
||||||
|
json!([self.x(), self.y()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToJson for RectF {
|
||||||
|
fn to_json(&self) -> serde_json::Value {
|
||||||
|
json!({"origin": self.origin().to_json(), "size": self.size().to_json()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
15
gpui/src/json.rs
Normal file
15
gpui/src/json.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
pub use serde_json::*;
|
||||||
|
|
||||||
|
pub trait ToJson {
|
||||||
|
fn to_json(&self) -> Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ToJson> ToJson for Option<T> {
|
||||||
|
fn to_json(&self) -> Value {
|
||||||
|
if let Some(value) = self.as_ref() {
|
||||||
|
value.to_json()
|
||||||
|
} else {
|
||||||
|
json!(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,11 +18,12 @@ mod util;
|
||||||
pub use elements::{Element, ElementBox};
|
pub use elements::{Element, ElementBox};
|
||||||
pub mod executor;
|
pub mod executor;
|
||||||
pub use executor::Task;
|
pub use executor::Task;
|
||||||
|
pub mod color;
|
||||||
|
pub mod json;
|
||||||
pub mod keymap;
|
pub mod keymap;
|
||||||
pub mod platform;
|
pub mod platform;
|
||||||
pub use pathfinder_color as color;
|
|
||||||
pub use platform::Event;
|
pub use platform::Event;
|
||||||
pub use presenter::{
|
pub use presenter::{
|
||||||
AfterLayoutContext, Axis, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext,
|
||||||
Vector2FExt,
|
SizeConstraint, Vector2FExt,
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,12 +2,12 @@ use super::{BoolExt as _, Dispatcher, FontSystem, Window};
|
||||||
use crate::{executor, platform};
|
use crate::{executor, platform};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::{NSApplication, NSOpenPanel, NSModalResponse},
|
appkit::{NSApplication, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString},
|
||||||
base::nil,
|
base::nil,
|
||||||
foundation::{NSArray, NSString, NSURL},
|
foundation::{NSArray, NSData, NSString, NSURL},
|
||||||
};
|
};
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{msg_send, sel, sel_impl};
|
||||||
use std::{path::PathBuf, rc::Rc, sync::Arc};
|
use std::{ffi::c_void, path::PathBuf, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
dispatcher: Arc<Dispatcher>,
|
dispatcher: Arc<Dispatcher>,
|
||||||
|
@ -84,4 +84,17 @@ impl platform::App for App {
|
||||||
let _: () = msg_send![app, terminate: nil];
|
let _: () = msg_send![app, terminate: nil];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copy(&self, text: &str) {
|
||||||
|
unsafe {
|
||||||
|
let data = NSData::dataWithBytes_length_(
|
||||||
|
nil,
|
||||||
|
text.as_ptr() as *const c_void,
|
||||||
|
text.len() as u64,
|
||||||
|
);
|
||||||
|
let pasteboard = NSPasteboard::generalPasteboard(nil);
|
||||||
|
pasteboard.clearContents();
|
||||||
|
pasteboard.setData_forType(data, NSPasteboardTypeString);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ pub trait App {
|
||||||
fn prompt_for_paths(&self, options: PathPromptOptions) -> Option<Vec<PathBuf>>;
|
fn prompt_for_paths(&self, options: PathPromptOptions) -> Option<Vec<PathBuf>>;
|
||||||
fn fonts(&self) -> Arc<dyn FontSystem>;
|
fn fonts(&self) -> Arc<dyn FontSystem>;
|
||||||
fn quit(&self);
|
fn quit(&self);
|
||||||
|
fn copy(&self, text: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Dispatcher: Send + Sync {
|
pub trait Dispatcher: Send + Sync {
|
||||||
|
|
|
@ -52,6 +52,8 @@ impl super::App for App {
|
||||||
fn prompt_for_paths(&self, _: super::PathPromptOptions) -> Option<Vec<std::path::PathBuf>> {
|
fn prompt_for_paths(&self, _: super::PathPromptOptions) -> Option<Vec<std::path::PathBuf>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copy(&self, _: &str) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
|
|
@ -2,11 +2,13 @@ use crate::{
|
||||||
app::{AppContext, MutableAppContext, WindowInvalidation},
|
app::{AppContext, MutableAppContext, WindowInvalidation},
|
||||||
elements::Element,
|
elements::Element,
|
||||||
font_cache::FontCache,
|
font_cache::FontCache,
|
||||||
|
json::{self, ToJson},
|
||||||
platform::Event,
|
platform::Event,
|
||||||
text_layout::TextLayoutCache,
|
text_layout::TextLayoutCache,
|
||||||
AssetCache, ElementBox, Scene,
|
AssetCache, ElementBox, Scene,
|
||||||
};
|
};
|
||||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||||
|
use serde_json::json;
|
||||||
use std::{any::Any, collections::HashMap, sync::Arc};
|
use std::{any::Any, collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
pub struct Presenter {
|
pub struct Presenter {
|
||||||
|
@ -128,6 +130,18 @@ impl Presenter {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn debug_elements(&self, ctx: &AppContext) -> Option<json::Value> {
|
||||||
|
ctx.root_view_id(self.window_id)
|
||||||
|
.and_then(|root_view_id| self.rendered_views.get(&root_view_id))
|
||||||
|
.map(|root_element| {
|
||||||
|
root_element.debug(&DebugContext {
|
||||||
|
rendered_views: &self.rendered_views,
|
||||||
|
font_cache: &self.font_cache,
|
||||||
|
app: ctx,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ActionToDispatch {
|
pub struct ActionToDispatch {
|
||||||
|
@ -224,6 +238,12 @@ impl<'a> EventContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct DebugContext<'a> {
|
||||||
|
rendered_views: &'a HashMap<usize, ElementBox>,
|
||||||
|
pub font_cache: &'a FontCache,
|
||||||
|
pub app: &'a AppContext,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum Axis {
|
pub enum Axis {
|
||||||
Horizontal,
|
Horizontal,
|
||||||
|
@ -239,6 +259,15 @@ impl Axis {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToJson for Axis {
|
||||||
|
fn to_json(&self) -> serde_json::Value {
|
||||||
|
match self {
|
||||||
|
Axis::Horizontal => json!("horizontal"),
|
||||||
|
Axis::Vertical => json!("vertical"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Vector2FExt {
|
pub trait Vector2FExt {
|
||||||
fn along(self, axis: Axis) -> f32;
|
fn along(self, axis: Axis) -> f32;
|
||||||
}
|
}
|
||||||
|
@ -291,6 +320,15 @@ impl SizeConstraint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToJson for SizeConstraint {
|
||||||
|
fn to_json(&self) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"min": self.min.to_json(),
|
||||||
|
"max": self.max.to_json(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ChildView {
|
pub struct ChildView {
|
||||||
view_id: usize,
|
view_id: usize,
|
||||||
}
|
}
|
||||||
|
@ -342,6 +380,25 @@ impl Element for ChildView {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
ctx.dispatch_event(self.view_id, event)
|
ctx.dispatch_event(self.view_id, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: pathfinder_geometry::rect::RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
ctx: &DebugContext,
|
||||||
|
) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "ChildView",
|
||||||
|
"view_id": self.view_id,
|
||||||
|
"bounds": bounds.to_json(),
|
||||||
|
"child": if let Some(view) = ctx.rendered_views.get(&self.view_id) {
|
||||||
|
view.debug(ctx)
|
||||||
|
} else {
|
||||||
|
json!(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color::ColorU,
|
color::ColorU,
|
||||||
fonts::{FontId, GlyphId},
|
fonts::{FontId, GlyphId},
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
|
json::ToJson,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
|
@ -258,3 +261,22 @@ impl Border {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToJson for Border {
|
||||||
|
fn to_json(&self) -> serde_json::Value {
|
||||||
|
let mut value = json!({});
|
||||||
|
if self.top {
|
||||||
|
value["top"] = json!(self.width);
|
||||||
|
}
|
||||||
|
if self.right {
|
||||||
|
value["right"] = json!(self.width);
|
||||||
|
}
|
||||||
|
if self.bottom {
|
||||||
|
value["bottom"] = json!(self.width);
|
||||||
|
}
|
||||||
|
if self.left {
|
||||||
|
value["left"] = json!(self.width);
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@ arrayvec = "0.5.2"
|
||||||
crossbeam-channel = "0.5.0"
|
crossbeam-channel = "0.5.0"
|
||||||
dirs = "3.0"
|
dirs = "3.0"
|
||||||
easy-parallel = "3.1.0"
|
easy-parallel = "3.1.0"
|
||||||
|
futures-core = "0.3"
|
||||||
gpui = {path = "../gpui"}
|
gpui = {path = "../gpui"}
|
||||||
ignore = {git = "https://github.com/zed-industries/ripgrep", rev = "1d152118f35b3e3590216709b86277062d79b8a0"}
|
ignore = {git = "https://github.com/zed-industries/ripgrep", rev = "1d152118f35b3e3590216709b86277062d79b8a0"}
|
||||||
futures-core = "0.3"
|
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
@ -28,11 +28,12 @@ num_cpus = "1.13.0"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
rust-embed = "5.9.0"
|
rust-embed = "5.9.0"
|
||||||
|
seahash = "4.1"
|
||||||
simplelog = "0.9"
|
simplelog = "0.9"
|
||||||
smallvec = "1.6.1"
|
smallvec = "1.6.1"
|
||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1.0.64"
|
serde_json = {version = "1.0.64", features = ["preserve_order"]}
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
|
|
|
@ -5,6 +5,7 @@ mod text;
|
||||||
pub use anchor::*;
|
pub use anchor::*;
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
pub use point::*;
|
pub use point::*;
|
||||||
|
use seahash::SeaHasher;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -20,7 +21,7 @@ use lazy_static::lazy_static;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
collections::{HashMap, HashSet},
|
hash::BuildHasher,
|
||||||
iter::{self, Iterator},
|
iter::{self, Iterator},
|
||||||
mem,
|
mem,
|
||||||
ops::{AddAssign, Range},
|
ops::{AddAssign, Range},
|
||||||
|
@ -32,13 +33,38 @@ use std::{
|
||||||
pub type SelectionSetId = time::Lamport;
|
pub type SelectionSetId = time::Lamport;
|
||||||
pub type SelectionsVersion = usize;
|
pub type SelectionsVersion = usize;
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
struct DeterministicState;
|
||||||
|
|
||||||
|
impl BuildHasher for DeterministicState {
|
||||||
|
type Hasher = SeaHasher;
|
||||||
|
|
||||||
|
fn build_hasher(&self) -> Self::Hasher {
|
||||||
|
SeaHasher::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
type HashMap<K, V> = std::collections::HashMap<K, V, DeterministicState>;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
type HashSet<T> = std::collections::HashSet<T, DeterministicState>;
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
type HashMap<K, V> = std::collections::HashMap<K, V>;
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
type HashSet<T> = std::collections::HashSet<T>;
|
||||||
|
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
file: Option<FileHandle>,
|
file: Option<FileHandle>,
|
||||||
fragments: SumTree<Fragment>,
|
fragments: SumTree<Fragment>,
|
||||||
insertion_splits: HashMap<time::Local, SumTree<InsertionSplit>>,
|
insertion_splits: HashMap<time::Local, SumTree<InsertionSplit>>,
|
||||||
|
edit_ops: HashMap<time::Local, EditOperation>,
|
||||||
pub version: time::Global,
|
pub version: time::Global,
|
||||||
saved_version: time::Global,
|
saved_version: time::Global,
|
||||||
last_edit: time::Local,
|
last_edit: time::Local,
|
||||||
|
undo_map: UndoMap,
|
||||||
selections: HashMap<SelectionSetId, Vec<Selection>>,
|
selections: HashMap<SelectionSetId, Vec<Selection>>,
|
||||||
pub selections_last_update: SelectionsVersion,
|
pub selections_last_update: SelectionsVersion,
|
||||||
deferred_ops: OperationQueue<Operation>,
|
deferred_ops: OperationQueue<Operation>,
|
||||||
|
@ -64,6 +90,42 @@ pub struct Selection {
|
||||||
pub reversed: bool,
|
pub reversed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
|
struct UndoMap(HashMap<time::Local, Vec<UndoOperation>>);
|
||||||
|
|
||||||
|
impl UndoMap {
|
||||||
|
fn insert(&mut self, undo: UndoOperation) {
|
||||||
|
self.0.entry(undo.edit_id).or_default().push(undo);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_undone(&self, edit_id: time::Local) -> bool {
|
||||||
|
self.undo_count(edit_id) % 2 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn was_undone(&self, edit_id: time::Local, version: &time::Global) -> bool {
|
||||||
|
let undo_count = self
|
||||||
|
.0
|
||||||
|
.get(&edit_id)
|
||||||
|
.unwrap_or(&Vec::new())
|
||||||
|
.iter()
|
||||||
|
.filter(|undo| version.observed(undo.id))
|
||||||
|
.map(|undo| undo.count)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
undo_count % 2 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn undo_count(&self, edit_id: time::Local) -> u32 {
|
||||||
|
self.0
|
||||||
|
.get(&edit_id)
|
||||||
|
.unwrap_or(&Vec::new())
|
||||||
|
.iter()
|
||||||
|
.map(|undo| undo.count)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CharIter<'a> {
|
pub struct CharIter<'a> {
|
||||||
fragments_cursor: Cursor<'a, Fragment, usize, usize>,
|
fragments_cursor: Cursor<'a, Fragment, usize, usize>,
|
||||||
|
@ -78,6 +140,7 @@ pub struct FragmentIter<'a> {
|
||||||
|
|
||||||
struct Edits<'a, F: Fn(&FragmentSummary) -> bool> {
|
struct Edits<'a, F: Fn(&FragmentSummary) -> bool> {
|
||||||
cursor: FilterCursor<'a, F, Fragment, usize>,
|
cursor: FilterCursor<'a, F, Fragment, usize>,
|
||||||
|
undos: &'a UndoMap,
|
||||||
since: time::Global,
|
since: time::Global,
|
||||||
delta: isize,
|
delta: isize,
|
||||||
}
|
}
|
||||||
|
@ -114,6 +177,8 @@ struct Fragment {
|
||||||
insertion: Insertion,
|
insertion: Insertion,
|
||||||
text: Text,
|
text: Text,
|
||||||
deletions: HashSet<time::Local>,
|
deletions: HashSet<time::Local>,
|
||||||
|
max_undos: time::Global,
|
||||||
|
visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||||
|
@ -143,13 +208,11 @@ struct InsertionSplitSummary {
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Operation {
|
pub enum Operation {
|
||||||
Edit {
|
Edit {
|
||||||
start_id: time::Local,
|
edit: EditOperation,
|
||||||
start_offset: usize,
|
lamport_timestamp: time::Lamport,
|
||||||
end_id: time::Local,
|
},
|
||||||
end_offset: usize,
|
Undo {
|
||||||
version_in_range: time::Global,
|
undo: UndoOperation,
|
||||||
new_text: Option<Text>,
|
|
||||||
local_timestamp: time::Local,
|
|
||||||
lamport_timestamp: time::Lamport,
|
lamport_timestamp: time::Lamport,
|
||||||
},
|
},
|
||||||
UpdateSelections {
|
UpdateSelections {
|
||||||
|
@ -159,6 +222,24 @@ pub enum Operation {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct EditOperation {
|
||||||
|
id: time::Local,
|
||||||
|
start_id: time::Local,
|
||||||
|
start_offset: usize,
|
||||||
|
end_id: time::Local,
|
||||||
|
end_offset: usize,
|
||||||
|
version_in_range: time::Global,
|
||||||
|
new_text: Option<Text>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct UndoOperation {
|
||||||
|
id: time::Local,
|
||||||
|
edit_id: time::Local,
|
||||||
|
count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
impl Buffer {
|
impl Buffer {
|
||||||
pub fn new<T: Into<String>>(replica_id: ReplicaId, base_text: T) -> Self {
|
pub fn new<T: Into<String>>(replica_id: ReplicaId, base_text: T) -> Self {
|
||||||
Self::build(replica_id, None, base_text.into())
|
Self::build(replica_id, None, base_text.into())
|
||||||
|
@ -169,7 +250,7 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(replica_id: ReplicaId, file: Option<FileHandle>, base_text: String) -> Self {
|
fn build(replica_id: ReplicaId, file: Option<FileHandle>, base_text: String) -> Self {
|
||||||
let mut insertion_splits = HashMap::new();
|
let mut insertion_splits = HashMap::default();
|
||||||
let mut fragments = SumTree::new();
|
let mut fragments = SumTree::new();
|
||||||
|
|
||||||
let base_insertion = Insertion {
|
let base_insertion = Insertion {
|
||||||
|
@ -191,7 +272,9 @@ impl Buffer {
|
||||||
id: FragmentId::min_value().clone(),
|
id: FragmentId::min_value().clone(),
|
||||||
insertion: base_insertion.clone(),
|
insertion: base_insertion.clone(),
|
||||||
text: base_insertion.text.slice(0..0),
|
text: base_insertion.text.slice(0..0),
|
||||||
deletions: HashSet::new(),
|
deletions: Default::default(),
|
||||||
|
max_undos: Default::default(),
|
||||||
|
visible: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if base_insertion.text.len() > 0 {
|
if base_insertion.text.len() > 0 {
|
||||||
|
@ -209,7 +292,9 @@ impl Buffer {
|
||||||
id: base_fragment_id,
|
id: base_fragment_id,
|
||||||
text: base_insertion.text.clone(),
|
text: base_insertion.text.clone(),
|
||||||
insertion: base_insertion,
|
insertion: base_insertion,
|
||||||
deletions: HashSet::new(),
|
deletions: Default::default(),
|
||||||
|
max_undos: Default::default(),
|
||||||
|
visible: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,13 +302,15 @@ impl Buffer {
|
||||||
file,
|
file,
|
||||||
fragments,
|
fragments,
|
||||||
insertion_splits,
|
insertion_splits,
|
||||||
|
edit_ops: HashMap::default(),
|
||||||
version: time::Global::new(),
|
version: time::Global::new(),
|
||||||
saved_version: time::Global::new(),
|
saved_version: time::Global::new(),
|
||||||
last_edit: time::Local::default(),
|
last_edit: time::Local::default(),
|
||||||
|
undo_map: Default::default(),
|
||||||
selections: HashMap::default(),
|
selections: HashMap::default(),
|
||||||
selections_last_update: 0,
|
selections_last_update: 0,
|
||||||
deferred_ops: OperationQueue::new(),
|
deferred_ops: OperationQueue::new(),
|
||||||
deferred_replicas: HashSet::new(),
|
deferred_replicas: HashSet::default(),
|
||||||
replica_id,
|
replica_id,
|
||||||
local_clock: time::Local::new(replica_id),
|
local_clock: time::Local::new(replica_id),
|
||||||
lamport_clock: time::Lamport::new(replica_id),
|
lamport_clock: time::Lamport::new(replica_id),
|
||||||
|
@ -391,6 +478,7 @@ impl Buffer {
|
||||||
|
|
||||||
Edits {
|
Edits {
|
||||||
cursor,
|
cursor,
|
||||||
|
undos: &self.undo_map,
|
||||||
since,
|
since,
|
||||||
delta: 0,
|
delta: 0,
|
||||||
}
|
}
|
||||||
|
@ -432,6 +520,12 @@ impl Buffer {
|
||||||
new_text.clone(),
|
new_text.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for op in &ops {
|
||||||
|
if let Operation::Edit { edit, .. } = op {
|
||||||
|
self.edit_ops.insert(edit.id, edit.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(op) = ops.last() {
|
if let Some(op) = ops.last() {
|
||||||
if let Some(ctx) = ctx {
|
if let Some(ctx) = ctx {
|
||||||
ctx.notify();
|
ctx.notify();
|
||||||
|
@ -441,12 +535,9 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Operation::Edit {
|
if let Operation::Edit { edit, .. } = op {
|
||||||
local_timestamp, ..
|
self.last_edit = edit.id;
|
||||||
} = op
|
self.version.observe(edit.id);
|
||||||
{
|
|
||||||
self.last_edit = *local_timestamp;
|
|
||||||
self.version.observe(*local_timestamp);
|
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
@ -676,27 +767,33 @@ impl Buffer {
|
||||||
fn apply_op(&mut self, op: Operation) -> Result<()> {
|
fn apply_op(&mut self, op: Operation) -> Result<()> {
|
||||||
match op {
|
match op {
|
||||||
Operation::Edit {
|
Operation::Edit {
|
||||||
start_id,
|
edit,
|
||||||
start_offset,
|
|
||||||
end_id,
|
|
||||||
end_offset,
|
|
||||||
new_text,
|
|
||||||
version_in_range,
|
|
||||||
local_timestamp,
|
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
if !self.version.observed(local_timestamp) {
|
if !self.version.observed(edit.id) {
|
||||||
self.apply_edit(
|
self.apply_edit(
|
||||||
start_id,
|
edit.start_id,
|
||||||
start_offset,
|
edit.start_offset,
|
||||||
end_id,
|
edit.end_id,
|
||||||
end_offset,
|
edit.end_offset,
|
||||||
new_text.as_ref().cloned(),
|
edit.new_text.as_ref().cloned(),
|
||||||
&version_in_range,
|
&edit.version_in_range,
|
||||||
local_timestamp,
|
edit.id,
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
)?;
|
)?;
|
||||||
self.version.observe(local_timestamp);
|
self.version.observe(edit.id);
|
||||||
|
self.edit_ops.insert(edit.id, edit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Operation::Undo {
|
||||||
|
undo,
|
||||||
|
lamport_timestamp,
|
||||||
|
} => {
|
||||||
|
if !self.version.observed(undo.id) {
|
||||||
|
self.apply_undo(undo)?;
|
||||||
|
self.version.observe(undo.id);
|
||||||
|
self.lamport_clock.observe(lamport_timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Operation::UpdateSelections {
|
Operation::UpdateSelections {
|
||||||
|
@ -785,8 +882,9 @@ impl Buffer {
|
||||||
new_fragments.push(fragment);
|
new_fragments.push(fragment);
|
||||||
}
|
}
|
||||||
if let Some(mut fragment) = within_range {
|
if let Some(mut fragment) = within_range {
|
||||||
if version_in_range.observed(fragment.insertion.id) {
|
if fragment.was_visible(&version_in_range, &self.undo_map) {
|
||||||
fragment.deletions.insert(local_timestamp);
|
fragment.deletions.insert(local_timestamp);
|
||||||
|
fragment.visible = false;
|
||||||
}
|
}
|
||||||
new_fragments.push(fragment);
|
new_fragments.push(fragment);
|
||||||
}
|
}
|
||||||
|
@ -804,9 +902,11 @@ impl Buffer {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if fragment.id < end_fragment_id && version_in_range.observed(fragment.insertion.id)
|
if fragment.id < end_fragment_id
|
||||||
|
&& fragment.was_visible(&version_in_range, &self.undo_map)
|
||||||
{
|
{
|
||||||
fragment.deletions.insert(local_timestamp);
|
fragment.deletions.insert(local_timestamp);
|
||||||
|
fragment.visible = false;
|
||||||
}
|
}
|
||||||
new_fragments.push(fragment);
|
new_fragments.push(fragment);
|
||||||
}
|
}
|
||||||
|
@ -831,6 +931,76 @@ impl Buffer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn undo_or_redo(&mut self, edit_id: time::Local) -> Result<Operation> {
|
||||||
|
let undo = UndoOperation {
|
||||||
|
id: self.local_clock.tick(),
|
||||||
|
edit_id,
|
||||||
|
count: self.undo_map.undo_count(edit_id) + 1,
|
||||||
|
};
|
||||||
|
self.apply_undo(undo)?;
|
||||||
|
self.version.observe(undo.id);
|
||||||
|
|
||||||
|
Ok(Operation::Undo {
|
||||||
|
undo,
|
||||||
|
lamport_timestamp: self.lamport_clock.tick(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_undo(&mut self, undo: UndoOperation) -> Result<()> {
|
||||||
|
let mut new_fragments;
|
||||||
|
|
||||||
|
self.undo_map.insert(undo);
|
||||||
|
let edit = &self.edit_ops[&undo.edit_id];
|
||||||
|
let start_fragment_id = self.resolve_fragment_id(edit.start_id, edit.start_offset)?;
|
||||||
|
let end_fragment_id = self.resolve_fragment_id(edit.end_id, edit.end_offset)?;
|
||||||
|
let mut cursor = self.fragments.cursor::<FragmentIdRef, ()>();
|
||||||
|
|
||||||
|
if edit.start_id == edit.end_id && edit.start_offset == edit.end_offset {
|
||||||
|
let splits = &self.insertion_splits[&undo.edit_id];
|
||||||
|
let mut insertion_splits = splits.cursor::<(), ()>().map(|s| &s.fragment_id).peekable();
|
||||||
|
|
||||||
|
let first_split_id = insertion_splits.next().unwrap();
|
||||||
|
new_fragments = cursor.slice(&FragmentIdRef::new(first_split_id), SeekBias::Left);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut fragment = cursor.item().unwrap().clone();
|
||||||
|
fragment.visible = fragment.is_visible(&self.undo_map);
|
||||||
|
fragment.max_undos.observe(undo.id);
|
||||||
|
new_fragments.push(fragment);
|
||||||
|
cursor.next();
|
||||||
|
if let Some(split_id) = insertion_splits.next() {
|
||||||
|
new_fragments
|
||||||
|
.push_tree(cursor.slice(&FragmentIdRef::new(split_id), SeekBias::Left));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new_fragments = cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left);
|
||||||
|
while let Some(fragment) = cursor.item() {
|
||||||
|
if fragment.id > end_fragment_id {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
let mut fragment = fragment.clone();
|
||||||
|
if edit.version_in_range.observed(fragment.insertion.id)
|
||||||
|
|| fragment.insertion.id == undo.edit_id
|
||||||
|
{
|
||||||
|
fragment.visible = fragment.is_visible(&self.undo_map);
|
||||||
|
fragment.max_undos.observe(undo.id);
|
||||||
|
}
|
||||||
|
new_fragments.push(fragment);
|
||||||
|
cursor.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_fragments.push_tree(cursor.suffix());
|
||||||
|
drop(cursor);
|
||||||
|
self.fragments = new_fragments;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn flush_deferred_ops(&mut self) -> Result<()> {
|
fn flush_deferred_ops(&mut self) -> Result<()> {
|
||||||
self.deferred_replicas.clear();
|
self.deferred_replicas.clear();
|
||||||
let mut deferred_ops = Vec::new();
|
let mut deferred_ops = Vec::new();
|
||||||
|
@ -851,16 +1021,12 @@ impl Buffer {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
match op {
|
match op {
|
||||||
Operation::Edit {
|
Operation::Edit { edit, .. } => {
|
||||||
start_id,
|
self.version.observed(edit.start_id)
|
||||||
end_id,
|
&& self.version.observed(edit.end_id)
|
||||||
version_in_range,
|
&& edit.version_in_range <= self.version
|
||||||
..
|
|
||||||
} => {
|
|
||||||
self.version.observed(*start_id)
|
|
||||||
&& self.version.observed(*end_id)
|
|
||||||
&& *version_in_range <= self.version
|
|
||||||
}
|
}
|
||||||
|
Operation::Undo { undo, .. } => self.version.observed(undo.edit_id),
|
||||||
Operation::UpdateSelections { selections, .. } => {
|
Operation::UpdateSelections { selections, .. } => {
|
||||||
if let Some(selections) = selections {
|
if let Some(selections) = selections {
|
||||||
selections.iter().all(|selection| {
|
selections.iter().all(|selection| {
|
||||||
|
@ -927,6 +1093,7 @@ impl Buffer {
|
||||||
|
|
||||||
while cur_range.is_some() && cursor.item().is_some() {
|
while cur_range.is_some() && cursor.item().is_some() {
|
||||||
let mut fragment = cursor.item().unwrap().clone();
|
let mut fragment = cursor.item().unwrap().clone();
|
||||||
|
let fragment_summary = cursor.item_summary().unwrap();
|
||||||
let mut fragment_start = *cursor.start();
|
let mut fragment_start = *cursor.start();
|
||||||
let mut fragment_end = fragment_start + fragment.visible_len();
|
let mut fragment_end = fragment_start + fragment.visible_len();
|
||||||
|
|
||||||
|
@ -986,8 +1153,10 @@ impl Buffer {
|
||||||
prefix.set_end_offset(prefix.start_offset() + (range.end - fragment_start));
|
prefix.set_end_offset(prefix.start_offset() + (range.end - fragment_start));
|
||||||
prefix.id =
|
prefix.id =
|
||||||
FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id);
|
FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id);
|
||||||
if fragment.is_visible() {
|
version_in_range.observe_all(&fragment_summary.max_version);
|
||||||
|
if fragment.visible {
|
||||||
prefix.deletions.insert(local_timestamp);
|
prefix.deletions.insert(local_timestamp);
|
||||||
|
prefix.visible = false;
|
||||||
}
|
}
|
||||||
fragment.set_start_offset(prefix.end_offset());
|
fragment.set_start_offset(prefix.end_offset());
|
||||||
new_fragments.push(prefix.clone());
|
new_fragments.push(prefix.clone());
|
||||||
|
@ -998,12 +1167,12 @@ impl Buffer {
|
||||||
fragment_start = range.end;
|
fragment_start = range.end;
|
||||||
end_id = Some(fragment.insertion.id);
|
end_id = Some(fragment.insertion.id);
|
||||||
end_offset = Some(fragment.start_offset());
|
end_offset = Some(fragment.start_offset());
|
||||||
version_in_range.observe(fragment.insertion.id);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
version_in_range.observe(fragment.insertion.id);
|
version_in_range.observe_all(&fragment_summary.max_version);
|
||||||
if fragment.is_visible() {
|
if fragment.visible {
|
||||||
fragment.deletions.insert(local_timestamp);
|
fragment.deletions.insert(local_timestamp);
|
||||||
|
fragment.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1012,13 +1181,15 @@ impl Buffer {
|
||||||
// loop and find the first fragment that the splice does not contain fully.
|
// loop and find the first fragment that the splice does not contain fully.
|
||||||
if range.end <= fragment_end {
|
if range.end <= fragment_end {
|
||||||
ops.push(Operation::Edit {
|
ops.push(Operation::Edit {
|
||||||
start_id: start_id.unwrap(),
|
edit: EditOperation {
|
||||||
start_offset: start_offset.unwrap(),
|
id: local_timestamp,
|
||||||
end_id: end_id.unwrap(),
|
start_id: start_id.unwrap(),
|
||||||
end_offset: end_offset.unwrap(),
|
start_offset: start_offset.unwrap(),
|
||||||
version_in_range,
|
end_id: end_id.unwrap(),
|
||||||
new_text: new_text.clone(),
|
end_offset: end_offset.unwrap(),
|
||||||
local_timestamp,
|
version_in_range,
|
||||||
|
new_text: new_text.clone(),
|
||||||
|
},
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1051,14 +1222,16 @@ impl Buffer {
|
||||||
cursor.next();
|
cursor.next();
|
||||||
if let Some(range) = cur_range.clone() {
|
if let Some(range) = cur_range.clone() {
|
||||||
while let Some(fragment) = cursor.item() {
|
while let Some(fragment) = cursor.item() {
|
||||||
|
let fragment_summary = cursor.item_summary().unwrap();
|
||||||
fragment_start = *cursor.start();
|
fragment_start = *cursor.start();
|
||||||
fragment_end = fragment_start + fragment.visible_len();
|
fragment_end = fragment_start + fragment.visible_len();
|
||||||
if range.start < fragment_start && range.end >= fragment_end {
|
if range.start < fragment_start && range.end >= fragment_end {
|
||||||
let mut new_fragment = fragment.clone();
|
let mut new_fragment = fragment.clone();
|
||||||
if new_fragment.is_visible() {
|
version_in_range.observe_all(&fragment_summary.max_version);
|
||||||
|
if new_fragment.visible {
|
||||||
new_fragment.deletions.insert(local_timestamp);
|
new_fragment.deletions.insert(local_timestamp);
|
||||||
|
new_fragment.visible = false;
|
||||||
}
|
}
|
||||||
version_in_range.observe(new_fragment.insertion.id);
|
|
||||||
new_fragments.push(new_fragment);
|
new_fragments.push(new_fragment);
|
||||||
cursor.next();
|
cursor.next();
|
||||||
|
|
||||||
|
@ -1066,13 +1239,15 @@ impl Buffer {
|
||||||
end_id = Some(fragment.insertion.id);
|
end_id = Some(fragment.insertion.id);
|
||||||
end_offset = Some(fragment.end_offset());
|
end_offset = Some(fragment.end_offset());
|
||||||
ops.push(Operation::Edit {
|
ops.push(Operation::Edit {
|
||||||
start_id: start_id.unwrap(),
|
edit: EditOperation {
|
||||||
start_offset: start_offset.unwrap(),
|
id: local_timestamp,
|
||||||
end_id: end_id.unwrap(),
|
start_id: start_id.unwrap(),
|
||||||
end_offset: end_offset.unwrap(),
|
start_offset: start_offset.unwrap(),
|
||||||
version_in_range,
|
end_id: end_id.unwrap(),
|
||||||
new_text: new_text.clone(),
|
end_offset: end_offset.unwrap(),
|
||||||
local_timestamp,
|
version_in_range,
|
||||||
|
new_text: new_text.clone(),
|
||||||
|
},
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1111,13 +1286,15 @@ impl Buffer {
|
||||||
debug_assert_eq!(old_ranges.next(), None);
|
debug_assert_eq!(old_ranges.next(), None);
|
||||||
let last_fragment = new_fragments.last().unwrap();
|
let last_fragment = new_fragments.last().unwrap();
|
||||||
ops.push(Operation::Edit {
|
ops.push(Operation::Edit {
|
||||||
start_id: last_fragment.insertion.id,
|
edit: EditOperation {
|
||||||
start_offset: last_fragment.end_offset(),
|
id: local_timestamp,
|
||||||
end_id: last_fragment.insertion.id,
|
start_id: last_fragment.insertion.id,
|
||||||
end_offset: last_fragment.end_offset(),
|
start_offset: last_fragment.end_offset(),
|
||||||
version_in_range: time::Global::new(),
|
end_id: last_fragment.insertion.id,
|
||||||
new_text: new_text.clone(),
|
end_offset: last_fragment.end_offset(),
|
||||||
local_timestamp,
|
version_in_range: time::Global::new(),
|
||||||
|
new_text: new_text.clone(),
|
||||||
|
},
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1365,7 +1542,7 @@ impl Buffer {
|
||||||
.ok_or_else(|| anyhow!("fragment id does not exist"))?;
|
.ok_or_else(|| anyhow!("fragment id does not exist"))?;
|
||||||
|
|
||||||
let mut summary = fragments_cursor.start().clone();
|
let mut summary = fragments_cursor.start().clone();
|
||||||
if fragment.is_visible() {
|
if fragment.visible {
|
||||||
summary += fragment
|
summary += fragment
|
||||||
.text
|
.text
|
||||||
.slice(..offset - fragment.start_offset())
|
.slice(..offset - fragment.start_offset())
|
||||||
|
@ -1398,9 +1575,11 @@ impl Clone for Buffer {
|
||||||
file: self.file.clone(),
|
file: self.file.clone(),
|
||||||
fragments: self.fragments.clone(),
|
fragments: self.fragments.clone(),
|
||||||
insertion_splits: self.insertion_splits.clone(),
|
insertion_splits: self.insertion_splits.clone(),
|
||||||
|
edit_ops: self.edit_ops.clone(),
|
||||||
version: self.version.clone(),
|
version: self.version.clone(),
|
||||||
saved_version: self.saved_version.clone(),
|
saved_version: self.saved_version.clone(),
|
||||||
last_edit: self.last_edit.clone(),
|
last_edit: self.last_edit.clone(),
|
||||||
|
undo_map: self.undo_map.clone(),
|
||||||
selections: self.selections.clone(),
|
selections: self.selections.clone(),
|
||||||
selections_last_update: self.selections_last_update.clone(),
|
selections_last_update: self.selections_last_update.clone(),
|
||||||
deferred_ops: self.deferred_ops.clone(),
|
deferred_ops: self.deferred_ops.clone(),
|
||||||
|
@ -1464,7 +1643,7 @@ impl<'a> Iterator for CharIter<'a> {
|
||||||
loop {
|
loop {
|
||||||
self.fragments_cursor.next();
|
self.fragments_cursor.next();
|
||||||
if let Some(fragment) = self.fragments_cursor.item() {
|
if let Some(fragment) = self.fragments_cursor.item() {
|
||||||
if fragment.is_visible() {
|
if fragment.visible {
|
||||||
self.fragment_chars = fragment.text.as_str().chars();
|
self.fragment_chars = fragment.text.as_str().chars();
|
||||||
return self.fragment_chars.next();
|
return self.fragment_chars.next();
|
||||||
}
|
}
|
||||||
|
@ -1498,7 +1677,7 @@ impl<'a> Iterator for FragmentIter<'a> {
|
||||||
self.started = true;
|
self.started = true;
|
||||||
}
|
}
|
||||||
if let Some(fragment) = self.cursor.item() {
|
if let Some(fragment) = self.cursor.item() {
|
||||||
if fragment.is_visible() {
|
if fragment.visible {
|
||||||
return Some(fragment.text.as_str());
|
return Some(fragment.text.as_str());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1518,7 +1697,7 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> {
|
||||||
let new_offset = *self.cursor.start();
|
let new_offset = *self.cursor.start();
|
||||||
let old_offset = (new_offset as isize - self.delta) as usize;
|
let old_offset = (new_offset as isize - self.delta) as usize;
|
||||||
|
|
||||||
if !fragment.was_visible(&self.since) && fragment.is_visible() {
|
if !fragment.was_visible(&self.since, &self.undos) && fragment.visible {
|
||||||
if let Some(ref mut change) = change {
|
if let Some(ref mut change) = change {
|
||||||
if change.new_range.end == new_offset {
|
if change.new_range.end == new_offset {
|
||||||
change.new_range.end += fragment.len();
|
change.new_range.end += fragment.len();
|
||||||
|
@ -1533,7 +1712,7 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> {
|
||||||
});
|
});
|
||||||
self.delta += fragment.len() as isize;
|
self.delta += fragment.len() as isize;
|
||||||
}
|
}
|
||||||
} else if fragment.was_visible(&self.since) && !fragment.is_visible() {
|
} else if fragment.was_visible(&self.since, &self.undos) && !fragment.visible {
|
||||||
if let Some(ref mut change) = change {
|
if let Some(ref mut change) = change {
|
||||||
if change.new_range.end == new_offset {
|
if change.new_range.end == new_offset {
|
||||||
change.old_range.end += fragment.len();
|
change.old_range.end += fragment.len();
|
||||||
|
@ -1732,7 +1911,9 @@ impl Fragment {
|
||||||
id,
|
id,
|
||||||
text: insertion.text.clone(),
|
text: insertion.text.clone(),
|
||||||
insertion,
|
insertion,
|
||||||
deletions: HashSet::new(),
|
deletions: Default::default(),
|
||||||
|
max_undos: Default::default(),
|
||||||
|
visible: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1753,7 +1934,7 @@ impl Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visible_len(&self) -> usize {
|
fn visible_len(&self) -> usize {
|
||||||
if self.is_visible() {
|
if self.visible {
|
||||||
self.len()
|
self.len()
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
|
@ -1764,12 +1945,16 @@ impl Fragment {
|
||||||
self.text.len()
|
self.text.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_visible(&self) -> bool {
|
fn is_visible(&self, undos: &UndoMap) -> bool {
|
||||||
self.deletions.is_empty()
|
!undos.is_undone(self.insertion.id) && self.deletions.iter().all(|d| undos.is_undone(*d))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn was_visible(&self, version: &time::Global) -> bool {
|
fn was_visible(&self, version: &time::Global, undos: &UndoMap) -> bool {
|
||||||
version.observed(self.insertion.id) && self.deletions.iter().all(|d| !version.observed(*d))
|
(version.observed(self.insertion.id) && !undos.was_undone(self.insertion.id, version))
|
||||||
|
&& self
|
||||||
|
.deletions
|
||||||
|
.iter()
|
||||||
|
.all(|d| !version.observed(*d) || undos.was_undone(*d, version))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn point_for_offset(&self, offset: usize) -> Result<Point> {
|
fn point_for_offset(&self, offset: usize) -> Result<Point> {
|
||||||
|
@ -1790,8 +1975,9 @@ impl sum_tree::Item for Fragment {
|
||||||
for deletion in &self.deletions {
|
for deletion in &self.deletions {
|
||||||
max_version.observe(*deletion);
|
max_version.observe(*deletion);
|
||||||
}
|
}
|
||||||
|
max_version.observe_all(&self.max_undos);
|
||||||
|
|
||||||
if self.is_visible() {
|
if self.visible {
|
||||||
FragmentSummary {
|
FragmentSummary {
|
||||||
text_summary: self.text.summary(),
|
text_summary: self.text.summary(),
|
||||||
max_fragment_id: self.id.clone(),
|
max_fragment_id: self.id.clone(),
|
||||||
|
@ -1899,6 +2085,9 @@ impl Operation {
|
||||||
Operation::Edit {
|
Operation::Edit {
|
||||||
lamport_timestamp, ..
|
lamport_timestamp, ..
|
||||||
} => *lamport_timestamp,
|
} => *lamport_timestamp,
|
||||||
|
Operation::Undo {
|
||||||
|
lamport_timestamp, ..
|
||||||
|
} => *lamport_timestamp,
|
||||||
Operation::UpdateSelections {
|
Operation::UpdateSelections {
|
||||||
lamport_timestamp, ..
|
lamport_timestamp, ..
|
||||||
} => *lamport_timestamp,
|
} => *lamport_timestamp,
|
||||||
|
@ -2077,6 +2266,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
assert_eq!(buffer.text(), reference_string);
|
assert_eq!(buffer.text(), reference_string);
|
||||||
|
|
||||||
|
if rng.gen_bool(0.25) {
|
||||||
|
buffer.randomly_undo_redo(rng);
|
||||||
|
reference_string = buffer.text();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let line_lengths = line_lengths_in_range(&buffer, 0..buffer.len());
|
let line_lengths = line_lengths_in_range(&buffer, 0..buffer.len());
|
||||||
|
|
||||||
|
@ -2607,13 +2801,46 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_undo_redo() -> Result<()> {
|
||||||
|
let mut buffer = Buffer::new(0, "1234");
|
||||||
|
|
||||||
|
let edit1 = buffer.edit(vec![1..1], "abx", None)?;
|
||||||
|
let edit2 = buffer.edit(vec![3..4], "yzef", None)?;
|
||||||
|
let edit3 = buffer.edit(vec![3..5], "cd", None)?;
|
||||||
|
assert_eq!(buffer.text(), "1abcdef234");
|
||||||
|
|
||||||
|
buffer.undo_or_redo(edit1[0].edit_id().unwrap())?;
|
||||||
|
assert_eq!(buffer.text(), "1cdef234");
|
||||||
|
buffer.undo_or_redo(edit1[0].edit_id().unwrap())?;
|
||||||
|
assert_eq!(buffer.text(), "1abcdef234");
|
||||||
|
|
||||||
|
buffer.undo_or_redo(edit2[0].edit_id().unwrap())?;
|
||||||
|
assert_eq!(buffer.text(), "1abcdx234");
|
||||||
|
buffer.undo_or_redo(edit3[0].edit_id().unwrap())?;
|
||||||
|
assert_eq!(buffer.text(), "1abx234");
|
||||||
|
buffer.undo_or_redo(edit2[0].edit_id().unwrap())?;
|
||||||
|
assert_eq!(buffer.text(), "1abyzef234");
|
||||||
|
buffer.undo_or_redo(edit3[0].edit_id().unwrap())?;
|
||||||
|
assert_eq!(buffer.text(), "1abcdef234");
|
||||||
|
|
||||||
|
buffer.undo_or_redo(edit3[0].edit_id().unwrap())?;
|
||||||
|
assert_eq!(buffer.text(), "1abyzef234");
|
||||||
|
buffer.undo_or_redo(edit1[0].edit_id().unwrap())?;
|
||||||
|
assert_eq!(buffer.text(), "1yzef234");
|
||||||
|
buffer.undo_or_redo(edit2[0].edit_id().unwrap())?;
|
||||||
|
assert_eq!(buffer.text(), "1234");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_random_concurrent_edits() {
|
fn test_random_concurrent_edits() {
|
||||||
use crate::test::Network;
|
use crate::test::Network;
|
||||||
|
|
||||||
const PEERS: usize = 3;
|
const PEERS: usize = 5;
|
||||||
|
|
||||||
for seed in 0..50 {
|
for seed in 0..100 {
|
||||||
println!("{:?}", seed);
|
println!("{:?}", seed);
|
||||||
let mut rng = &mut StdRng::seed_from_u64(seed);
|
let mut rng = &mut StdRng::seed_from_u64(seed);
|
||||||
|
|
||||||
|
@ -2636,14 +2863,24 @@ mod tests {
|
||||||
let replica_index = rng.gen_range(0..PEERS);
|
let replica_index = rng.gen_range(0..PEERS);
|
||||||
let replica_id = replica_ids[replica_index];
|
let replica_id = replica_ids[replica_index];
|
||||||
let buffer = &mut buffers[replica_index];
|
let buffer = &mut buffers[replica_index];
|
||||||
if mutation_count > 0 && rng.gen() {
|
|
||||||
let (_, _, ops) = buffer.randomly_mutate(&mut rng, None);
|
match rng.gen_range(0..=100) {
|
||||||
network.broadcast(replica_id, ops, &mut rng);
|
0..=50 if mutation_count != 0 => {
|
||||||
mutation_count -= 1;
|
let (_, _, ops) = buffer.randomly_mutate(&mut rng, None);
|
||||||
} else if network.has_unreceived(replica_id) {
|
network.broadcast(replica_id, ops, &mut rng);
|
||||||
buffer
|
mutation_count -= 1;
|
||||||
.apply_ops(network.receive(replica_id, &mut rng), None)
|
}
|
||||||
.unwrap();
|
51..=70 if mutation_count != 0 => {
|
||||||
|
let ops = buffer.randomly_undo_redo(&mut rng);
|
||||||
|
network.broadcast(replica_id, ops, &mut rng);
|
||||||
|
mutation_count -= 1;
|
||||||
|
}
|
||||||
|
71..=100 if network.has_unreceived(replica_id) => {
|
||||||
|
buffer
|
||||||
|
.apply_ops(network.receive(replica_id, &mut rng), None)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mutation_count == 0 && network.is_idle() {
|
if mutation_count == 0 && network.is_idle() {
|
||||||
|
@ -2669,13 +2906,14 @@ mod tests {
|
||||||
pub fn randomly_mutate<T>(
|
pub fn randomly_mutate<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut T,
|
rng: &mut T,
|
||||||
ctx: Option<&mut ModelContext<Self>>,
|
mut ctx: Option<&mut ModelContext<Self>>,
|
||||||
) -> (Vec<Range<usize>>, String, Vec<Operation>)
|
) -> (Vec<Range<usize>>, String, Vec<Operation>)
|
||||||
where
|
where
|
||||||
T: Rng,
|
T: Rng,
|
||||||
{
|
{
|
||||||
// Randomly edit
|
// Randomly edit
|
||||||
let (old_ranges, new_text, mut operations) = self.randomly_edit(rng, 5, ctx);
|
let (old_ranges, new_text, mut operations) =
|
||||||
|
self.randomly_edit(rng, 5, ctx.as_deref_mut());
|
||||||
|
|
||||||
// Randomly add, remove or mutate selection sets.
|
// Randomly add, remove or mutate selection sets.
|
||||||
let replica_selection_sets = &self
|
let replica_selection_sets = &self
|
||||||
|
@ -2708,6 +2946,26 @@ mod tests {
|
||||||
|
|
||||||
(old_ranges, new_text, operations)
|
(old_ranges, new_text, operations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn randomly_undo_redo(&mut self, rng: &mut impl Rng) -> Vec<Operation> {
|
||||||
|
let mut ops = Vec::new();
|
||||||
|
for _ in 0..rng.gen_range(1..5) {
|
||||||
|
if let Some(edit_id) = self.edit_ops.keys().choose(rng).copied() {
|
||||||
|
ops.push(self.undo_or_redo(edit_id).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operation {
|
||||||
|
fn edit_id(&self) -> Option<time::Local> {
|
||||||
|
match self {
|
||||||
|
Operation::Edit { edit, .. } => Some(edit.id),
|
||||||
|
Operation::Undo { undo, .. } => Some(undo.edit_id),
|
||||||
|
Operation::UpdateSelections { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_lengths_in_range(buffer: &Buffer, range: Range<usize>) -> BTreeMap<u32, HashSet<u32>> {
|
fn line_lengths_in_range(buffer: &Buffer, range: Range<usize>) -> BTreeMap<u32, HashSet<u32>> {
|
||||||
|
@ -2715,11 +2973,11 @@ mod tests {
|
||||||
for (row, line) in buffer.text()[range].lines().enumerate() {
|
for (row, line) in buffer.text()[range].lines().enumerate() {
|
||||||
lengths
|
lengths
|
||||||
.entry(line.len() as u32)
|
.entry(line.len() as u32)
|
||||||
.or_insert(HashSet::new())
|
.or_insert(HashSet::default())
|
||||||
.insert(row as u32);
|
.insert(row as u32);
|
||||||
}
|
}
|
||||||
if lengths.is_empty() {
|
if lengths.is_empty() {
|
||||||
let mut rows = HashSet::new();
|
let mut rows = HashSet::default();
|
||||||
rows.insert(0);
|
rows.insert(0);
|
||||||
lengths.insert(0, rows);
|
lengths.insert(0, rows);
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,7 +162,7 @@ impl<'a> From<&'a str> for Text {
|
||||||
|
|
||||||
impl Debug for Text {
|
impl Debug for Text {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_tuple("Text").field(&self.text).finish()
|
f.debug_tuple("Text").field(&self.as_str()).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,12 @@ use gpui::{
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
PathBuilder,
|
PathBuilder,
|
||||||
},
|
},
|
||||||
|
json::{self, ToJson},
|
||||||
text_layout::{self, TextLayoutCache},
|
text_layout::{self, TextLayoutCache},
|
||||||
AfterLayoutContext, AppContext, Border, Element, Event, EventContext, FontCache, LayoutContext,
|
AfterLayoutContext, AppContext, Border, Element, Event, EventContext, FontCache, LayoutContext,
|
||||||
PaintContext, Quad, Scene, SizeConstraint, ViewHandle,
|
PaintContext, Quad, Scene, SizeConstraint, ViewHandle,
|
||||||
};
|
};
|
||||||
|
use json::json;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -477,6 +479,19 @@ impl Element for BufferElement {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
bounds: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &gpui::DebugContext,
|
||||||
|
) -> json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "BufferElement",
|
||||||
|
"bounds": bounds.to_json()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LayoutState {
|
pub struct LayoutState {
|
||||||
|
|
|
@ -78,7 +78,7 @@ impl View for FileFinder {
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.top_center()
|
.top_center()
|
||||||
.boxed()
|
.named("file finder")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_focus(&mut self, ctx: &mut ViewContext<Self>) {
|
fn on_focus(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
|
@ -105,7 +105,7 @@ impl FileFinder {
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_margin_top(6.0)
|
.with_margin_top(6.0)
|
||||||
.boxed();
|
.named("empty matches");
|
||||||
}
|
}
|
||||||
|
|
||||||
let handle = self.handle.clone();
|
let handle = self.handle.clone();
|
||||||
|
@ -127,7 +127,7 @@ impl FileFinder {
|
||||||
.with_background_color(ColorU::from_u32(0xf7f7f7ff))
|
.with_background_color(ColorU::from_u32(0xf7f7f7ff))
|
||||||
.with_border(Border::all(1.0, ColorU::from_u32(0xdbdbdcff)))
|
.with_border(Border::all(1.0, ColorU::from_u32(0xdbdbdcff)))
|
||||||
.with_margin_top(6.0)
|
.with_margin_top(6.0)
|
||||||
.boxed()
|
.named("matches")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_match(
|
fn render_match(
|
||||||
|
@ -226,7 +226,7 @@ impl FileFinder {
|
||||||
ctx.dispatch_action("file_finder:select", (tree_id, entry_id));
|
ctx.dispatch_action("file_finder:select", (tree_id, entry_id));
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
.boxed()
|
.named("match")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn item_summary(&self) -> Option<&'a T::Summary> {
|
pub fn item_summary(&self) -> Option<&'a T::Summary> {
|
||||||
assert!(self.did_seek, "Must seek before calling this method");
|
assert!(self.did_seek, "Must seek before calling this method");
|
||||||
if let Some(entry) = self.stack.last() {
|
if let Some(entry) = self.stack.last() {
|
||||||
match *entry.tree.0 {
|
match *entry.tree.0 {
|
||||||
|
|
|
@ -4,19 +4,20 @@ use std::mem;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
pub type ReplicaId = u16;
|
pub type ReplicaId = u16;
|
||||||
|
pub type Seq = u64;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd)]
|
||||||
pub struct Local {
|
pub struct Local {
|
||||||
pub replica_id: ReplicaId,
|
pub replica_id: ReplicaId,
|
||||||
pub value: u64,
|
pub value: Seq,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct Global(Arc<HashMap<ReplicaId, u64>>);
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
pub struct Lamport {
|
pub struct Lamport {
|
||||||
pub value: u64,
|
pub value: Seq,
|
||||||
pub replica_id: ReplicaId,
|
pub replica_id: ReplicaId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,12 +58,25 @@ impl<'a> AddAssign<&'a Local> for Local {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Global(Arc<HashMap<ReplicaId, u64>>);
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref DEFAULT_GLOBAL: Global = Global(Arc::new(HashMap::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Global {
|
||||||
|
fn default() -> Self {
|
||||||
|
DEFAULT_GLOBAL.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Global {
|
impl Global {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Global(Arc::new(HashMap::new()))
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, replica_id: ReplicaId) -> u64 {
|
pub fn get(&self, replica_id: ReplicaId) -> Seq {
|
||||||
*self.0.get(&replica_id).unwrap_or(&0)
|
*self.0.get(&replica_id).unwrap_or(&0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -244,7 +244,7 @@ impl Pane {
|
||||||
.with_max_width(264.0)
|
.with_max_width(264.0)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.boxed(),
|
.named("tab"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,10 +263,10 @@ impl Pane {
|
||||||
.with_border(Border::bottom(1.0, border_color))
|
.with_border(Border::bottom(1.0, border_color))
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.boxed(),
|
.named("filler"),
|
||||||
);
|
);
|
||||||
|
|
||||||
row.boxed()
|
row.named("tabs")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_modified_icon(is_modified: bool) -> ElementBox {
|
fn render_modified_icon(is_modified: bool) -> ElementBox {
|
||||||
|
@ -304,9 +304,9 @@ impl View for Pane {
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_child(self.render_tabs(app))
|
.with_child(self.render_tabs(app))
|
||||||
.with_child(Expanded::new(1.0, ChildView::new(active_item.id()).boxed()).boxed())
|
.with_child(Expanded::new(1.0, ChildView::new(active_item.id()).boxed()).boxed())
|
||||||
.boxed()
|
.named("pane")
|
||||||
} else {
|
} else {
|
||||||
Empty::new().boxed()
|
Empty::new().named("pane")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,19 @@ use super::{pane, Pane, PaneGroup, SplitDirection, Workspace};
|
||||||
use crate::{settings::Settings, watch};
|
use crate::{settings::Settings, watch};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::rgbu, elements::*, keymap::Binding, AnyViewHandle, App, AppContext, Entity, ModelHandle,
|
color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, App,
|
||||||
MutableAppContext, View, ViewContext, ViewHandle,
|
AppContext, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
|
||||||
};
|
};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use std::{collections::HashSet, path::PathBuf};
|
use std::{collections::HashSet, path::PathBuf};
|
||||||
|
|
||||||
pub fn init(app: &mut App) {
|
pub fn init(app: &mut App) {
|
||||||
app.add_action("workspace:save", WorkspaceView::save_active_item);
|
app.add_action("workspace:save", WorkspaceView::save_active_item);
|
||||||
app.add_bindings(vec![Binding::new("cmd-s", "workspace:save", None)]);
|
app.add_action("workspace:debug_elements", WorkspaceView::debug_elements);
|
||||||
|
app.add_bindings(vec![
|
||||||
|
Binding::new("cmd-s", "workspace:save", None),
|
||||||
|
Binding::new("cmd-alt-i", "workspace:debug_elements", None),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ItemView: View {
|
pub trait ItemView: View {
|
||||||
|
@ -251,6 +255,21 @@ impl WorkspaceView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn debug_elements(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||||
|
match to_string_pretty(&ctx.debug_elements()) {
|
||||||
|
Ok(json) => {
|
||||||
|
ctx.app_mut().copy(&json);
|
||||||
|
log::info!(
|
||||||
|
"copied {:.1} KiB of element debug JSON to the clipboard",
|
||||||
|
json.len() as f32 / 1024.
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
log::error!("error debugging elements: {}", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn workspace_updated(&mut self, _: ModelHandle<Workspace>, ctx: &mut ViewContext<Self>) {
|
fn workspace_updated(&mut self, _: ModelHandle<Workspace>, ctx: &mut ViewContext<Self>) {
|
||||||
ctx.notify();
|
ctx.notify();
|
||||||
}
|
}
|
||||||
|
@ -358,7 +377,7 @@ impl View for WorkspaceView {
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_background_color(rgbu(0xea, 0xea, 0xeb))
|
.with_background_color(rgbu(0xea, 0xea, 0xeb))
|
||||||
.boxed()
|
.named("workspace")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_focus(&mut self, ctx: &mut ViewContext<Self>) {
|
fn on_focus(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue