diff --git a/Cargo.lock b/Cargo.lock index 094fe79b..3cb61403 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -498,7 +498,7 @@ dependencies = [ "clicolors-control", "lazy_static 1.4.0", "libc", - "parking_lot 0.6.4", + "parking_lot 0.11.2", "regex", "termios", "unicode-width", @@ -1326,6 +1326,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "fsevent" version = "0.4.0" @@ -1960,6 +1970,7 @@ dependencies = [ "regex", "serde 1.0.130", "serde_json", + "sled", "strum", "strum_macros", "tinyfiledialogs", @@ -3668,6 +3679,22 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch 0.9.5", + "crossbeam-utils 0.8.5", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + [[package]] name = "slotmap" version = "1.0.6" diff --git a/core/Cargo.toml b/core/Cargo.toml index 96259f4e..6212bf5e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Dongdong Zhou "] edition = "2021" [dependencies] +sled = "0.34.7" base64 = "0.13.0" alacritty_terminal = "0.15.0" config = "0.11" diff --git a/core/src/data.rs b/core/src/data.rs index 0a344cb2..8328ebd9 100644 --- a/core/src/data.rs +++ b/core/src/data.rs @@ -32,7 +32,7 @@ Location, Position, TextEdit, WorkspaceClientCapabilities, }; use parking_lot::Mutex; -use serde::{Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use tree_sitter::{Node, Parser}; use tree_sitter_highlight::{ @@ -60,6 +60,7 @@ }, completion::{CompletionData, CompletionStatus, Snippet}, config::{Config, LapceTheme}, + db::LapceDb, editor::{EditorLocationNew, LapceEditorBufferData, LapceEditorViewContent}, find::Find, keypress::{KeyPressData, KeyPressFocus}, @@ -121,6 +122,7 @@ pub struct LapceWindowData { pub keypress: Arc, pub theme: Arc>, pub config: Arc, + pub db: Arc, } impl Data for LapceWindowData { @@ -134,9 +136,16 @@ pub fn new( keypress: Arc, theme: Arc>, ) -> Self { + let db = Arc::new(LapceDb::new().unwrap()); let mut tabs = im::HashMap::new(); let tab_id = WidgetId::next(); - let tab = LapceTabData::new(tab_id, keypress.clone(), theme.clone(), None); + let tab = LapceTabData::new( + tab_id, + db.clone(), + keypress.clone(), + theme.clone(), + None, + ); tabs.insert(tab_id, tab); let config = Arc::new(Config::load(None).unwrap_or_default()); Self { @@ -146,6 +155,7 @@ pub fn new( keypress, theme, config, + db, } } } @@ -245,6 +255,7 @@ pub struct LapceTabData { pub config: Arc, pub focus: WidgetId, pub focus_area: FocusArea, + pub db: Arc, } impl Data for LapceTabData { @@ -269,6 +280,7 @@ fn same(&self, other: &Self) -> bool { impl LapceTabData { pub fn new( tab_id: WidgetId, + db: Arc, keypress: Arc, theme: Arc>, event_sink: Option, @@ -345,6 +357,7 @@ pub fn new( panel_active: PanelPosition::LeftTop, config, focus_area: FocusArea::Editor, + db, }; if let Some(event_sink) = event_sink { tab.start_update_process(event_sink); @@ -1471,7 +1484,7 @@ pub enum LapceEditorContainerKind { DiffSplit(WidgetId, WidgetId), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub enum EditorContent { Buffer(PathBuf), None, diff --git a/core/src/db.rs b/core/src/db.rs new file mode 100644 index 00000000..4460efe1 --- /dev/null +++ b/core/src/db.rs @@ -0,0 +1,64 @@ +use anyhow::{anyhow, Result}; +use directories::ProjectDirs; +use druid::Vec2; +use serde::{Deserialize, Serialize}; + +use crate::{ + data::{EditorContent, EditorType, LapceTabData}, + movement::Cursor, + state::LapceWorkspace, +}; + +pub struct LapceDb { + db: sled::Db, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct WorkspaceInfo { + pub editors: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct EditorInfo { + pub content: EditorContent, + pub scroll_offset: (f64, f64), + pub cursor: Cursor, +} + +impl LapceDb { + pub fn new() -> Result { + let proj_dirs = ProjectDirs::from("", "", "Lapce") + .ok_or(anyhow!("can't find project dirs"))?; + let path = proj_dirs.config_dir().join("lapce.db"); + let db = sled::Config::default() + .path(path) + .flush_every_ms(None) + .open()?; + Ok(Self { db }) + } + + pub fn save_workspace(&self, data: &LapceTabData) -> Result<()> { + let workspace = data.workspace.as_ref().ok_or(anyhow!("no workspace"))?; + let editors = data + .main_split + .editors + .iter() + .filter_map(|(_, editor)| match &editor.editor_type { + EditorType::Normal => Some(EditorInfo { + content: editor.content.clone(), + scroll_offset: (editor.scroll_offset.x, editor.scroll_offset.y), + cursor: editor.cursor.clone(), + }), + _ => None, + }) + .collect(); + let workspace_info = WorkspaceInfo { editors }; + let workspace_info = serde_json::to_string(&workspace_info)?; + let workspace = workspace.to_string(); + self.db + .insert(workspace.as_bytes(), workspace_info.as_bytes())?; + self.db.flush()?; + println!("db save workspace done {} {}", workspace, workspace_info); + Ok(()) + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 50c3d8ec..714dd5e7 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -6,6 +6,7 @@ pub mod config; pub mod container; mod data; +pub mod db; pub mod editor; pub mod explorer; pub mod find; diff --git a/core/src/movement.rs b/core/src/movement.rs index 152630cb..a098a15a 100644 --- a/core/src/movement.rs +++ b/core/src/movement.rs @@ -1,4 +1,5 @@ use druid::{piet::PietText, Env, Point, Rect, Size}; +use serde::{Deserialize, Serialize}; use xi_core_lib::selection::InsertDrift; use xi_rope::{RopeDelta, Transformer}; @@ -11,13 +12,13 @@ }; use std::cmp::{max, min}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Cursor { pub mode: CursorMode, pub horiz: Option, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum CursorMode { Normal(usize), Visual { @@ -241,7 +242,7 @@ pub fn apply_delta(&mut self, delta: &RopeDelta) { } } -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] pub enum ColPosition { FirstNonBlank, Start, @@ -249,7 +250,7 @@ pub enum ColPosition { Col(usize), } -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] pub struct SelRegion { start: usize, end: usize, @@ -313,7 +314,7 @@ fn merge_with(self, other: SelRegion) -> SelRegion { } } -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct Selection { regions: Vec, } diff --git a/core/src/state.rs b/core/src/state.rs index 75ad4c9f..3f2d1665 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use serde_json::json; use serde_json::Value; +use std::fmt::Display; use std::process::Child; use std::process::Command; use std::process::Stdio; @@ -41,7 +42,7 @@ pub enum LapceFocus { SourceControl, } -#[derive(Clone, PartialEq, Eq, Hash, Debug, Copy)] +#[derive(Clone, PartialEq, Eq, Hash, Debug, Copy, Deserialize, Serialize)] pub enum VisualMode { Normal, Linewise, @@ -76,13 +77,24 @@ pub struct KeyMap { pub command: String, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum LapceWorkspaceType { Local, RemoteSSH(String, String), } -#[derive(Clone, Debug, PartialEq)] +impl Display for LapceWorkspaceType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LapceWorkspaceType::Local => f.write_str("Local"), + LapceWorkspaceType::RemoteSSH(user, host) => { + write!(f, "ssh://{}@{}", user, host) + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct LapceWorkspace { pub kind: LapceWorkspaceType, pub path: PathBuf, @@ -105,6 +117,12 @@ fn default() -> Self { } } +impl Display for LapceWorkspace { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.kind, self.path.to_str().unwrap()) + } +} + pub struct Counter(AtomicU64); impl Counter { diff --git a/core/src/window.rs b/core/src/window.rs index d670335c..dca4398f 100644 --- a/core/src/window.rs +++ b/core/src/window.rs @@ -65,6 +65,7 @@ pub fn new_tab( let tab_id = WidgetId::next(); let mut tab_data = LapceTabData::new( tab_id, + data.db.clone(), data.keypress.clone(), data.theme.clone(), Some(ctx.get_external_handle()), @@ -77,6 +78,7 @@ pub fn new_tab( self.tabs[data.active] = WidgetPod::new(tab.boxed()); self.tab_headers[data.active] = WidgetPod::new(tab_header); if let Some(tab) = data.tabs.remove(&data.active_id) { + tab.db.save_workspace(&tab); tab.proxy.stop(); } data.active_id = tab_id; @@ -113,6 +115,7 @@ pub fn close_index_tab( self.tabs.remove(index); self.tab_headers.remove(index); if let Some(tab) = data.tabs.remove(&id) { + tab.db.save_workspace(&tab); tab.proxy.stop(); }