From 48e3f842f07e06ff40178ff2553b4817e1cb1a82 Mon Sep 17 00:00:00 2001 From: Dongdong Zhou Date: Thu, 17 Sep 2020 22:33:59 +0100 Subject: [PATCH] editor load buffer --- Cargo.lock | 417 ++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 5 +- src/buffer.rs | 47 ++++++ src/command.rs | 3 +- src/container.rs | 49 +++++- src/editor.rs | 190 ++++++++++++++++++++- src/font.rs | 15 ++ src/main.rs | 27 +-- src/palette.rs | 101 +++--------- src/scroll.rs | 22 ++- src/split.rs | 12 +- src/state.rs | 23 ++- src/theme.rs | 3 +- 13 files changed, 790 insertions(+), 124 deletions(-) create mode 100644 src/buffer.rs create mode 100644 src/font.rs diff --git a/Cargo.lock b/Cargo.lock index 0a5205d3..fe5fd615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,12 @@ version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" +[[package]] +name = "anymap" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" + [[package]] name = "arrayvec" version = "0.4.12" @@ -161,12 +167,45 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + [[package]] name = "bumpalo" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "bytecount" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0017894339f586ccb943b01b9555de56770c11cda818e7e3d8bd93f4ed7f46e" + [[package]] name = "byteorder" version = "1.3.4" @@ -220,6 +259,16 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "chashmap" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff41a3c2c1e39921b9003de14bf0439c7b63a9039637c291e1a64925d8ddfa45" +dependencies = [ + "owning_ref 0.3.3", + "parking_lot 0.4.8", +] + [[package]] name = "clang-sys" version = "1.0.0" @@ -397,6 +446,7 @@ dependencies = [ name = "crane" version = "0.0.1" dependencies = [ + "anyhow", "druid", "fzyr", "lazy_static 1.4.0", @@ -407,6 +457,8 @@ dependencies = [ "strum_macros 0.19.2", "syntect", "uuid", + "xi-core-lib", + "xi-rope", ] [[package]] @@ -424,7 +476,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7408247b1b87f480890f28b670c5f8d9a8a4274833433fe74dc0dfd46d33650" dependencies = [ - "crossbeam-channel", + "crossbeam-channel 0.2.6", "crossbeam-deque", "crossbeam-epoch 0.5.2", "crossbeam-utils 0.5.0", @@ -443,6 +495,15 @@ dependencies = [ "smallvec 0.6.13", ] +[[package]] +name = "crossbeam-channel" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" +dependencies = [ + "crossbeam-utils 0.6.6", +] + [[package]] name = "crossbeam-deque" version = "0.5.2" @@ -497,6 +558,26 @@ dependencies = [ "lazy_static 1.4.0", ] +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg 1.0.1", + "cfg-if", + "lazy_static 1.4.0", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + [[package]] name = "discard" version = "1.0.4" @@ -520,7 +601,7 @@ dependencies = [ "simple_logger", "unic-langid", "unicode-segmentation", - "xi-unicode", + "xi-unicode 0.2.1", ] [[package]] @@ -560,7 +641,7 @@ dependencies = [ "log", "objc", "piet-common", - "time", + "time 0.2.18", "wasm-bindgen", "web-sys", "winapi 0.3.9", @@ -598,6 +679,24 @@ dependencies = [ "termcolor", ] +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "filetime" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + [[package]] name = "flate2" version = "1.0.17" @@ -661,12 +760,47 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fsevent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +dependencies = [ + "bitflags", + "fsevent-sys", +] + +[[package]] +name = "fsevent-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" +dependencies = [ + "libc", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + [[package]] name = "futures" version = "0.3.5" @@ -850,6 +984,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + [[package]] name = "gio" version = "0.9.1" @@ -1029,6 +1172,26 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "inotify" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.6" @@ -1060,6 +1223,15 @@ dependencies = [ "unic-langid", ] +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + [[package]] name = "itertools" version = "0.7.11" @@ -1176,7 +1348,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" dependencies = [ - "owning_ref", + "owning_ref 0.4.1", "scopeguard 0.3.3", ] @@ -1262,6 +1434,49 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log", + "mio", + "slab", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + [[package]] name = "ndarray" version = "0.11.2" @@ -1274,6 +1489,17 @@ dependencies = [ "num-traits 0.1.43", ] +[[package]] +name = "net2" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.9", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -1290,6 +1516,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "notify" +version = "5.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d742ae493f34bd2e20ec2f3c1276fc1981343a8efd7ef12bca4368d0303bed50" +dependencies = [ + "anymap", + "bitflags", + "chashmap", + "crossbeam-channel 0.3.9", + "filetime", + "fsevent", + "fsevent-sys", + "inotify", + "kernel32-sys", + "libc", + "mio", + "mio-extras", + "walkdir", + "winapi 0.3.9", +] + [[package]] name = "num-complex" version = "0.1.43" @@ -1355,6 +1603,21 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "owning_ref" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "owning_ref" version = "0.4.1" @@ -1391,6 +1654,16 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking_lot" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" +dependencies = [ + "owning_ref 0.3.3", + "parking_lot_core 0.2.14", +] + [[package]] name = "parking_lot" version = "0.6.4" @@ -1412,6 +1685,18 @@ dependencies = [ "parking_lot_core 0.8.0", ] +[[package]] +name = "parking_lot_core" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4db1a8ccf734a7bce794cc19b3df06ed87ab2f3907036b693c68f56b4d4537fa" +dependencies = [ + "libc", + "rand 0.4.6", + "smallvec 0.6.13", + "winapi 0.3.9", +] + [[package]] name = "parking_lot_core" version = "0.3.1" @@ -1470,7 +1755,7 @@ dependencies = [ "cairo-rs", "piet", "unicode-segmentation", - "xi-unicode", + "xi-unicode 0.2.1", ] [[package]] @@ -1531,7 +1816,7 @@ dependencies = [ "unicode-segmentation", "wasm-bindgen", "web-sys", - "xi-unicode", + "xi-unicode 0.2.1", ] [[package]] @@ -1649,6 +1934,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", +] + [[package]] name = "rand" version = "0.5.6" @@ -1939,6 +2237,18 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + [[package]] name = "shlex" version = "0.1.1" @@ -2187,6 +2497,17 @@ dependencies = [ "lazy_static 1.4.0", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi 0.3.9", +] + [[package]] name = "time" version = "0.2.18" @@ -2255,6 +2576,12 @@ dependencies = [ "fxhash", ] +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + [[package]] name = "unic-bidi" version = "0.9.0" @@ -2416,6 +2743,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasm-bindgen" version = "0.2.68" @@ -2541,12 +2874,84 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "xi-core-lib" +version = "0.3.0" +dependencies = [ + "crossbeam-channel 0.3.9", + "log", + "memchr", + "notify", + "regex", + "serde", + "serde_derive", + "serde_json", + "sha2", + "syntect", + "time 0.1.44", + "toml", + "xi-rope", + "xi-rpc", + "xi-trace", + "xi-unicode 0.3.0", +] + +[[package]] +name = "xi-rope" +version = "0.3.0" +dependencies = [ + "bytecount", + "memchr", + "regex", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "xi-rpc" +version = "0.3.0" +dependencies = [ + "crossbeam-utils 0.7.2", + "log", + "serde", + "serde_derive", + "serde_json", + "xi-trace", +] + +[[package]] +name = "xi-trace" +version = "0.2.0" +dependencies = [ + "lazy_static 1.4.0", + "libc", + "log", + "serde", + "serde_derive", + "serde_json", + "time 0.1.44", +] + [[package]] name = "xi-unicode" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e71b85d8b1b8bfaf4b5c834187554d201a8cd621c2bbfa33efd41a3ecabd48b2" +[[package]] +name = "xi-unicode" +version = "0.3.0" + [[package]] name = "xml-rs" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index 6722bd77..00755893 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Dongdong Zhou "] edition = "2018" [dependencies] +anyhow = "1.0.32" strum = "0.19" strum_macros = "0.19" lazy_static = "1.4.0" @@ -12,8 +13,8 @@ lazy_static = "1.4.0" serde = "1.0" serde_json = "1.0" syntect = "3.2" -# xi-core-lib = { path = "../xi-editor/rust/core-lib/" } -# xi-rpc = { path = "../xi-editor/rust/rpc/" } +xi-core-lib = { path = "../xi-editor/rust/core-lib/" } +xi-rope = { path = "../xi-editor/rust/rope/" } fzyr = "0.1.2" uuid = { version = "0.7.4", features = ["v4"] } # crane_ui = {path = "./ui"} diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 00000000..68c3fc30 --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,47 @@ +use anyhow::Result; +use std::fs::File; +use std::io::{self, Read, Write}; +use xi_rope::{rope::Rope, LinesMetric}; + +#[derive(Eq, PartialEq, Hash, Clone)] +pub struct BufferId(pub usize); + +pub struct Buffer { + pub rope: Rope, + pub num_lines: usize, + pub max_line_len: usize, +} + +impl Buffer { + pub fn new(buffer_id: BufferId, path: &str) -> Buffer { + let rope = if let Ok(rope) = load_file(path) { + rope + } else { + Rope::from("") + }; + let num_lines = rope.line_of_offset(rope.len()) + 1; + + let mut pre_offset = 0; + let mut max_line_len = 0; + for i in 0..num_lines { + let offset = rope.offset_of_line(i); + let line_len = offset - pre_offset; + pre_offset = offset; + if line_len > max_line_len { + max_line_len = line_len; + } + } + Buffer { + rope, + num_lines, + max_line_len, + } + } +} + +fn load_file(path: &str) -> Result { + let mut f = File::open(path)?; + let mut bytes = Vec::new(); + f.read_to_end(&mut bytes)?; + Ok(Rope::from(std::str::from_utf8(&bytes)?)) +} diff --git a/src/command.rs b/src/command.rs index 083f7650..45b2eb02 100644 --- a/src/command.rs +++ b/src/command.rs @@ -32,9 +32,8 @@ pub enum CraneCommand { #[derive(Debug)] pub enum CraneUICommand { - Show, - Hide, RequestLayout, RequestPaint, EnsureVisible((Rect, (f64, f64))), + ScrollTo((f64, f64)), } diff --git a/src/container.rs b/src/container.rs index 6617fb83..59e2f880 100644 --- a/src/container.rs +++ b/src/container.rs @@ -1,6 +1,11 @@ -use crate::command::{CraneCommand, CRANE_COMMAND}; -use crate::state::CRANE_STATE; +use crate::{ + command::{CraneCommand, CRANE_COMMAND}, + editor::Editor, + editor::EditorState, + palette::PaletteWrapper, +}; use crate::{palette::Palette, split::CraneSplit}; +use crate::{scroll::CraneScroll, state::CRANE_STATE}; use druid::{ kurbo::{Line, Rect}, widget::IdentityWrapper, @@ -26,19 +31,40 @@ pub struct CraneContainer { } impl CraneContainer { - pub fn new( - palette: impl Widget + 'static, - editor_split: impl Widget + 'static, - ) -> Self { + pub fn new() -> Self { + let palette = PaletteWrapper::new(); let palette_id = WidgetId::next(); let palette = WidgetPod::new(IdentityWrapper::wrap(palette, palette_id)).boxed(); - let editor_split = WidgetPod::new(editor_split).boxed(); CRANE_STATE .palette .lock() .unwrap() .set_widget_id(palette_id); + + let editor_id = WidgetId::next(); + let editor = Editor::new(editor_id); + let scroll_id = WidgetId::next(); + let editor_state = EditorState::new(editor_id, scroll_id); + CRANE_STATE + .editor_split + .lock() + .unwrap() + .editors + .insert(editor_id, editor_state); + CRANE_STATE + .editor_split + .lock() + .unwrap() + .set_active(editor_id); + let editor_split = WidgetPod::new(CraneSplit::new(true).with_child( + IdentityWrapper::wrap( + CraneScroll::new(IdentityWrapper::wrap(editor, editor_id)), + scroll_id, + ), + )) + .boxed(); + CraneContainer { palette_max_size: Size::new(600.0, 400.0), palette_rect: Rect::ZERO @@ -60,7 +86,10 @@ fn event( ) { ctx.request_focus(); match event { - Event::Internal(_) => self.palette.event(ctx, event, data, env), + Event::Internal(_) => { + self.palette.event(ctx, event, data, env); + self.editor_split.event(ctx, event, data, env); + } Event::KeyDown(key_event) => CRANE_STATE.key_down(key_event), Event::Command(cmd) => { match cmd { @@ -84,7 +113,9 @@ fn event( | Event::MouseUp(mouse) | Event::MouseMove(mouse) | Event::Wheel(mouse) => { - if self.palette_rect.contains(mouse.pos) { + if !CRANE_STATE.palette.lock().unwrap().hidden + && self.palette_rect.contains(mouse.pos) + { self.palette.event(ctx, event, data, env); return; } else { diff --git a/src/editor.rs b/src/editor.rs index abb315e9..5e39612b 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,28 +1,127 @@ +use crate::{ + buffer::{Buffer, BufferId}, + command::CraneUICommand, + command::CRANE_UI_COMMAND, + container::CraneContainer, + state::CRANE_STATE, + theme::CraneTheme, +}; use druid::{ theme, BoxConstraints, Cursor, Data, Env, Event, EventCtx, ExtEventSink, Key, KeyEvent, LayoutCtx, LifeCycle, LifeCycleCtx, Modifiers, PaintCtx, Point, RenderContext, Selector, Size, Target, TextLayout, UpdateCtx, - Widget, WidgetPod, + Widget, WidgetId, WidgetPod, }; use lazy_static::lazy_static; use std::time::Duration; use std::{any::Any, thread}; use std::{collections::HashMap, sync::Arc, sync::Mutex}; -use crate::container::CraneContainer; - pub struct CraneUI { container: CraneContainer, } +#[derive(Debug, Default)] +pub struct Counter(usize); + +impl Counter { + pub fn next(&mut self) -> usize { + let n = self.0; + self.0 = n + 1; + n + 1 + } +} + +pub struct EditorState { + id: WidgetId, + scroll_id: WidgetId, + buffer_id: Option, +} + +impl EditorState { + pub fn new(id: WidgetId, scroll_id: WidgetId) -> EditorState { + EditorState { + id, + scroll_id, + buffer_id: None, + } + } +} + +pub struct EditorSplitState { + active: WidgetId, + pub editors: HashMap, + buffers: HashMap, + open_files: HashMap, + id_counter: Counter, +} + +impl EditorSplitState { + pub fn new() -> EditorSplitState { + EditorSplitState { + active: WidgetId::next(), + editors: HashMap::new(), + id_counter: Counter::default(), + buffers: HashMap::new(), + open_files: HashMap::new(), + } + } + + pub fn set_active(&mut self, widget_id: WidgetId) { + self.active = widget_id; + } + + pub fn open_file(&mut self, path: &str) { + let buffer_id = if let Some(buffer_id) = self.open_files.get(path) { + buffer_id.clone() + } else { + let buffer_id = self.next_buffer_id(); + let buffer = Buffer::new(buffer_id.clone(), path); + self.buffers.insert(buffer_id.clone(), buffer); + buffer_id + }; + if let Some(active_editor) = self.editors.get_mut(&self.active) { + if let Some(active_buffer) = active_editor.buffer_id.as_mut() { + if active_buffer == &buffer_id { + return; + } + } + active_editor.buffer_id = Some(buffer_id); + println!("submit ui scroll request layout"); + CRANE_STATE.submit_ui_command( + CraneUICommand::RequestLayout, + active_editor.scroll_id, + ); + } + } + + fn next_buffer_id(&mut self) -> BufferId { + BufferId(self.id_counter.next()) + } + + pub fn get_buffer_id( + &self, + editor_widget_id: &WidgetId, + ) -> Option { + self.editors + .get(editor_widget_id) + .map(|e| e.buffer_id.clone()) + .unwrap() + } +} + pub struct Editor { text_layout: TextLayout, + widget_id: WidgetId, } impl Editor { - pub fn new() -> Self { + pub fn new(widget_id: WidgetId) -> Self { let text_layout = TextLayout::new(""); - Editor { text_layout } + Editor { + text_layout, + widget_id, + } } } @@ -34,6 +133,28 @@ fn event( data: &mut T, env: &Env, ) { + match event { + Event::Command(cmd) => match cmd { + _ if cmd.is(CRANE_UI_COMMAND) => { + let command = cmd.get_unchecked(CRANE_UI_COMMAND); + match command { + CraneUICommand::RequestLayout => { + println!("editor request layout"); + ctx.request_layout(); + } + CraneUICommand::RequestPaint => { + ctx.request_paint(); + } + _ => println!( + "editor unprocessed ui command {:?}", + command + ), + } + } + _ => (), + }, + _ => (), + } } fn lifecycle( @@ -61,12 +182,63 @@ fn layout( data: &T, env: &Env, ) -> Size { - Size::new(500.0, 1000.0) + let buffer_id = { + CRANE_STATE + .editor_split + .lock() + .unwrap() + .get_buffer_id(&self.widget_id) + }; + if let Some(buffer_id) = buffer_id { + let buffers = &CRANE_STATE.editor_split.lock().unwrap().buffers; + let buffer = buffers.get(&buffer_id).unwrap(); + let width = 7.6171875; + Size::new( + width * buffer.max_line_len as f64, + 25.0 * buffer.num_lines as f64, + ) + } else { + Size::new(0.0, 0.0) + } } fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) { - let mut layout = TextLayout::new("abc sldkjfdslkjf"); - layout.rebuild_if_needed(&mut ctx.text(), env); - layout.draw(ctx, Point::new(10.0, 10.0)); + let line_height = env.get(CraneTheme::EDITOR_LINE_HEIGHT); + let buffer_id = { + CRANE_STATE + .editor_split + .lock() + .unwrap() + .get_buffer_id(&self.widget_id) + }; + if let Some(buffer_id) = buffer_id { + let buffers = &CRANE_STATE.editor_split.lock().unwrap().buffers; + let buffer = buffers.get(&buffer_id).unwrap(); + let width = 7.6171875; + let rects = ctx.region().rects().to_vec(); + for rect in rects { + let start_line = (rect.y0 / line_height).floor() as usize; + let num_lines = (rect.height() / line_height).floor() as usize; + for (i, line) in buffer + .rope + .lines_raw( + buffer.rope.offset_of_line(start_line) + ..buffer.rope.offset_of_line( + (start_line + num_lines + 1) + .min(buffer.num_lines), + ), + ) + .enumerate() + { + let mut layout = TextLayout::new(line); + layout.set_font(CraneTheme::EDITOR_FONT); + layout.rebuild_if_needed(&mut ctx.text(), env); + layout.draw( + ctx, + Point::new(0.0, line_height * (i + start_line) as f64), + ); + } + } + } } } diff --git a/src/font.rs b/src/font.rs new file mode 100644 index 00000000..3aa38fcf --- /dev/null +++ b/src/font.rs @@ -0,0 +1,15 @@ +use druid::{FontDescriptor, FontFamily, TextLayout}; + +pub struct CraneFont { + font: FontDescriptor, +} + +impl CraneFont { + pub fn new(font_name: &str, size: f64) { + let font = + FontDescriptor::new(FontFamily::new_unchecked("Cascadia Code")) + .with_size(size); + let mut text_layout = TextLayout::new("W"); + text_layout.set_font(font.clone()); + } +} diff --git a/src/main.rs b/src/main.rs index 010eb655..476f3a99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ +mod buffer; mod command; mod container; mod editor; +mod font; mod palette; mod scroll; mod split; @@ -15,7 +17,7 @@ use crate::split::CraneSplit; use crate::state::CRANE_STATE; -use druid::{piet::Color, Size}; +use druid::{piet::Color, FontDescriptor, FontFamily, Size}; use druid::{ widget::{Align, Container, Flex, Label, Padding, Scroll, Split}, Point, @@ -24,23 +26,7 @@ use palette::PaletteWrapper; fn build_app() -> impl Widget { - let editor = Editor::new(); - let mut split = CraneSplit::new(true) - .with_child(Scroll::new(Padding::new( - (100.0, 100.0, 100.0, 100.0), - Container::new(editor), - ))) - .with_child(Scroll::new(Padding::new( - (100.0, 100.0, 100.0, 100.0), - Container::new(Editor::new()) - .border(Color::rgb(122.0, 0.0, 0.0), 2.0), - ))); - // .env_scope(|env: &mut druid::Env, data: &u32| { - // env.set(theme::SCROLLBAR_RADIUS, 0.0); - // env.set(theme::SCROLLBAR_WIDTH, 15.0); - // env.set(theme::SCROLLBAR_EDGE_WIDTH, 0.0); - // }); - let container = CraneContainer::new(PaletteWrapper::new(0), split); + let container = CraneContainer::new(); container.env_scope(|env: &mut druid::Env, data: &u32| { env.set(theme::CraneTheme::EDITOR_LINE_HEIGHT, 25.0); env.set( @@ -59,6 +45,11 @@ fn build_app() -> impl Widget { theme::CraneTheme::PALETTE_INPUT_BORDER, Color::rgb8(0, 0, 0), ); + env.set( + theme::CraneTheme::EDITOR_FONT, + FontDescriptor::new(FontFamily::new_unchecked("Cascadia Code")) + .with_size(13.0), + ); }) } diff --git a/src/palette.rs b/src/palette.rs index f515db44..02824b44 100644 --- a/src/palette.rs +++ b/src/palette.rs @@ -50,6 +50,7 @@ pub struct PaletteState { items: Vec, filtered_items: Vec, index: usize, + pub hidden: bool, } impl PaletteState { @@ -64,6 +65,7 @@ pub fn new() -> PaletteState { input: "".to_string(), cursor: 0, index: 0, + hidden: true, } } } @@ -87,62 +89,30 @@ pub fn set_width(&mut self, width: f64) { pub fn run(&mut self) { self.items = self.get_files(); - CRANE_STATE - .ui_sink - .lock() - .unwrap() - .as_ref() - .unwrap() - .submit_command( - CRANE_UI_COMMAND, - CraneUICommand::Show, - Target::Widget(self.widget_id.unwrap().clone()), - ); + self.hidden = false; + self.request_layout(); } pub fn cancel(&mut self) { self.input = "".to_string(); self.cursor = 0; self.index = 0; - CRANE_STATE - .ui_sink - .lock() - .unwrap() - .as_ref() - .unwrap() - .submit_command( - CRANE_UI_COMMAND, - CraneUICommand::Hide, - Target::Widget(self.widget_id.unwrap().clone()), - ); + self.hidden = true; + self.request_paint(); } fn request_layout(&self) { - CRANE_STATE - .ui_sink - .lock() - .unwrap() - .as_ref() - .unwrap() - .submit_command( - CRANE_UI_COMMAND, - CraneUICommand::RequestLayout, - Target::Widget(self.widget_id.unwrap().clone()), - ); + CRANE_STATE.submit_ui_command( + CraneUICommand::RequestLayout, + self.widget_id.unwrap(), + ); } fn request_paint(&self) { - CRANE_STATE - .ui_sink - .lock() - .unwrap() - .as_ref() - .unwrap() - .submit_command( - CRANE_UI_COMMAND, - CraneUICommand::RequestPaint, - Target::Widget(self.widget_id.unwrap().clone()), - ); + CRANE_STATE.submit_ui_command( + CraneUICommand::RequestPaint, + self.widget_id.unwrap(), + ); } fn ensure_visible(&self) { @@ -151,17 +121,10 @@ fn ensure_visible(&self) { .with_size(Size::new(self.width, self.line_height)); let margin = (0.0, 0.0); - CRANE_STATE - .ui_sink - .lock() - .unwrap() - .as_ref() - .unwrap() - .submit_command( - CRANE_UI_COMMAND, - CraneUICommand::EnsureVisible((rect, margin)), - Target::Widget(self.scroll_widget_id.unwrap().clone()), - ); + CRANE_STATE.submit_ui_command( + CraneUICommand::EnsureVisible((rect, margin)), + self.widget_id.unwrap(), + ); } pub fn insert(&mut self, content: &str) { @@ -272,8 +235,8 @@ pub fn select(&mut self) { if items.is_empty() { return; } - let file = &items[self.index]; - println!("open file {}", file.text); + CRANE_STATE.open_file(&items[self.index].text); + self.cancel(); } pub fn change_index(&mut self, n: i64) { @@ -304,7 +267,6 @@ pub struct Palette { pub struct PaletteWrapper { palette: WidgetPod>>, - hidden: bool, } pub struct PaletteInput {} @@ -756,17 +718,14 @@ pub fn new() -> PaletteInput { } impl PaletteWrapper { - pub fn new(data: T) -> PaletteWrapper { + pub fn new() -> PaletteWrapper { let palette = WidgetPod::new( Palette::new() .border(theme::BORDER_LIGHT, 1.0) .background(CraneTheme::PALETTE_BACKGROUND), ) .boxed(); - PaletteWrapper { - palette, - hidden: true, - } + PaletteWrapper { palette } } } @@ -1024,28 +983,22 @@ fn event( data: &mut T, env: &Env, ) { - println!("event {:?}", event); match event { Event::Internal(_) => self.palette.event(ctx, event, data, env), Event::Command(cmd) => match cmd { _ if cmd.is(CRANE_UI_COMMAND) => { let command = cmd.get_unchecked(CRANE_UI_COMMAND); match command { - CraneUICommand::Show => { - self.hidden = false; - ctx.request_layout(); - } - CraneUICommand::Hide => { - self.hidden = true; - ctx.request_paint(); - } CraneUICommand::RequestLayout => { ctx.request_layout(); } CraneUICommand::RequestPaint => { ctx.request_paint(); } - _ => println!("unprocessed ui command {:?}", command), + _ => println!( + "palette unprocessed ui command {:?}", + command + ), } } _ => (), @@ -1092,7 +1045,7 @@ fn layout( } fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) { - if self.hidden { + if CRANE_STATE.palette.lock().unwrap().hidden { return; } self.palette.paint(ctx, data, env); diff --git a/src/scroll.rs b/src/scroll.rs index 44cb27f0..f28807f0 100644 --- a/src/scroll.rs +++ b/src/scroll.rs @@ -88,6 +88,12 @@ pub fn scroll(&mut self, delta: Vec2, layout_size: Size) -> bool { scrolled } + fn scroll_to(&mut self, x: f64, y: f64) { + let offset = Vec2::new(x, y); + self.scroll_component.scroll_offset = offset; + self.child.set_viewport_offset(offset); + } + fn ensure_visible( &mut self, scroll_size: Size, @@ -135,11 +141,25 @@ fn event( _ if cmd.is(CRANE_UI_COMMAND) => { let command = cmd.get_unchecked(CRANE_UI_COMMAND); match command { + CraneUICommand::RequestLayout => { + println!("scroll request layout"); + ctx.request_layout(); + } + CraneUICommand::RequestPaint => { + ctx.request_paint(); + } CraneUICommand::EnsureVisible((rect, margin)) => { self.ensure_visible(ctx.size(), rect, margin); return; } - _ => println!("unprocessed ui command {:?}", command), + CraneUICommand::ScrollTo((x, y)) => { + self.scroll_to(*x, *y); + return; + } + _ => println!( + "scroll unprocessed ui command {:?}", + command + ), } } _ => (), diff --git a/src/split.rs b/src/split.rs index 5810c4f9..86e10cd4 100644 --- a/src/split.rs +++ b/src/split.rs @@ -102,6 +102,14 @@ fn event( data: &mut T, env: &Env, ) { + match event { + Event::Internal(_) => { + for child in self.children.as_mut_slice() { + child.event(ctx, event, data, env); + } + } + _ => (), + } for child in self.children.as_mut_slice() { if child.is_active() { child.event(ctx, event, data, env); @@ -200,7 +208,9 @@ fn layout( let width = if i < children_sizes.len() { children_sizes[i] * my_size.width } else { - my_size.width * (1.0 - children_sizes[i - 1]) + my_size.width + * (1.0 + - if i > 0 { children_sizes[i - 1] } else { 0.0 }) }; let child_bc = BoxConstraints::new( diff --git a/src/state.rs b/src/state.rs index 99714ea7..8e489655 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,4 +1,5 @@ use std::{ + cell::Cell, collections::HashMap, str::FromStr, sync::{Arc, Mutex}, @@ -6,11 +7,16 @@ time::Duration, }; -use druid::{ExtEventSink, KeyEvent, Modifiers, Target}; +use druid::{ExtEventSink, KeyEvent, Modifiers, Target, WidgetId}; use lazy_static::lazy_static; use crate::{ + buffer::Buffer, + buffer::BufferId, + command::CraneUICommand, + command::CRANE_UI_COMMAND, command::{CraneCommand, CRANE_COMMAND}, + editor::EditorSplitState, palette::PaletteState, }; @@ -33,6 +39,7 @@ pub struct CraneState { pub last_focus: Arc>, pub focus: Arc>, pub ui_sink: Arc>>, + pub editor_split: Arc>, } impl CraneState { @@ -104,6 +111,7 @@ pub fn new() -> CraneState { focus: Arc::new(Mutex::new(CraneWidget::Editor)), last_focus: Arc::new(Mutex::new(CraneWidget::Editor)), palette: Arc::new(Mutex::new(PaletteState::new())), + editor_split: Arc::new(Mutex::new(EditorSplitState::new())), } } @@ -245,4 +253,17 @@ pub fn key_down(&self, key_event: &KeyEvent) { pub fn set_ui_sink(&self, ui_sink: ExtEventSink) { *self.ui_sink.lock().unwrap() = Some(ui_sink); } + + pub fn open_file(&self, path: &str) { + self.editor_split.lock().unwrap().open_file(path); + } + + pub fn submit_ui_command(&self, cmd: CraneUICommand, widget_id: WidgetId) { + self.ui_sink + .lock() + .unwrap() + .as_ref() + .unwrap() + .submit_command(CRANE_UI_COMMAND, cmd, Target::Widget(widget_id)); + } } diff --git a/src/theme.rs b/src/theme.rs index 8b51bdc3..1b3751a3 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,4 +1,4 @@ -use druid::{Color, Key}; +use druid::{Color, FontDescriptor, Key}; pub struct CraneTheme {} @@ -13,4 +13,5 @@ impl CraneTheme { Key::new("crane.palette_input_foreground"); pub const PALETTE_INPUT_BORDER: Key = Key::new("crane.palette_input_border"); + pub const EDITOR_FONT: Key = Key::new("crane.eidtor_font"); }