Port journal to GPUI2 (#3178)

Joseph T. Lyons created

Release Notes:

- N/A

Change summary

Cargo.lock                      |  19 +++
Cargo.toml                      |   1 
crates/journal2/Cargo.toml      |  27 +++++
crates/journal2/src/journal2.rs | 176 +++++++++++++++++++++++++++++++++++
crates/zed-actions/src/lib.rs   |  26 ++--
crates/zed2/Cargo.toml          |   2 
crates/zed2/src/main.rs         |   2 
7 files changed, 238 insertions(+), 15 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4171,6 +4171,24 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "journal2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "chrono",
+ "dirs 4.0.0",
+ "editor",
+ "gpui2",
+ "log",
+ "schemars",
+ "serde",
+ "settings2",
+ "shellexpand",
+ "util",
+ "workspace",
+]
+
 [[package]]
 name = "jpeg-decoder"
 version = "0.1.22"
@@ -10903,6 +10921,7 @@ dependencies = [
  "indexmap 1.9.3",
  "install_cli",
  "isahc",
+ "journal2",
  "language2",
  "language_tools",
  "lazy_static",

Cargo.toml 🔗

@@ -48,6 +48,7 @@ members = [
     "crates/install_cli",
     "crates/install_cli2",
     "crates/journal",
+    "crates/journal2",
     "crates/language",
     "crates/language2",
     "crates/language_selector",

crates/journal2/Cargo.toml 🔗

@@ -0,0 +1,27 @@
+[package]
+name = "journal2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/journal2.rs"
+doctest = false
+
+[dependencies]
+editor = { path = "../editor" }
+gpui2 = { path = "../gpui2" }
+util = { path = "../util" }
+workspace = { path = "../workspace" }
+settings2 = { path = "../settings2" }
+
+anyhow.workspace = true
+chrono = "0.4"
+dirs = "4.0"
+serde.workspace = true
+schemars.workspace = true
+log.workspace = true
+shellexpand = "2.1.0"
+
+[dev-dependencies]
+editor = { path = "../editor", features = ["test-support"] }

crates/journal2/src/journal2.rs 🔗

@@ -0,0 +1,176 @@
+use anyhow::Result;
+use chrono::{Datelike, Local, NaiveTime, Timelike};
+use gpui2::AppContext;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use settings2::Settings;
+use std::{
+    fs::OpenOptions,
+    path::{Path, PathBuf},
+    sync::Arc,
+};
+use workspace::AppState;
+// use zed::AppState;
+
+// todo!();
+// actions!(journal, [NewJournalEntry]);
+
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
+pub struct JournalSettings {
+    pub path: Option<String>,
+    pub hour_format: Option<HourFormat>,
+}
+
+impl Default for JournalSettings {
+    fn default() -> Self {
+        Self {
+            path: Some("~".into()),
+            hour_format: Some(Default::default()),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum HourFormat {
+    #[default]
+    Hour12,
+    Hour24,
+}
+
+impl settings2::Settings for JournalSettings {
+    const KEY: Option<&'static str> = Some("journal");
+
+    type FileContent = Self;
+
+    fn load(
+        defaults: &Self::FileContent,
+        user_values: &[&Self::FileContent],
+        _: &mut AppContext,
+    ) -> Result<Self> {
+        Self::load_via_json_merge(defaults, user_values)
+    }
+}
+
+pub fn init(_: Arc<AppState>, cx: &mut AppContext) {
+    JournalSettings::register(cx);
+
+    // todo!()
+    // cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx));
+}
+
+pub fn new_journal_entry(_: Arc<AppState>, cx: &mut AppContext) {
+    let settings = JournalSettings::get_global(cx);
+    let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) {
+        Some(journal_dir) => journal_dir,
+        None => {
+            log::error!("Can't determine journal directory");
+            return;
+        }
+    };
+
+    let now = Local::now();
+    let month_dir = journal_dir
+        .join(format!("{:02}", now.year()))
+        .join(format!("{:02}", now.month()));
+    let entry_path = month_dir.join(format!("{:02}.md", now.day()));
+    let now = now.time();
+    let _entry_heading = heading_entry(now, &settings.hour_format);
+
+    let _create_entry = cx.executor().spawn(async move {
+        std::fs::create_dir_all(month_dir)?;
+        OpenOptions::new()
+            .create(true)
+            .write(true)
+            .open(&entry_path)?;
+        Ok::<_, std::io::Error>((journal_dir, entry_path))
+    });
+
+    // todo!("workspace")
+    // cx.spawn(|cx| async move {
+    //     let (journal_dir, entry_path) = create_entry.await?;
+    //     let (workspace, _) =
+    //         cx.update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?;
+
+    //     let opened = workspace
+    //         .update(&mut cx, |workspace, cx| {
+    //             workspace.open_paths(vec![entry_path], true, cx)
+    //         })?
+    //         .await;
+
+    //     if let Some(Some(Ok(item))) = opened.first() {
+    //         if let Some(editor) = item.downcast::<Editor>().map(|editor| editor.downgrade()) {
+    //             editor.update(&mut cx, |editor, cx| {
+    //                 let len = editor.buffer().read(cx).len(cx);
+    //                 editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+    //                     s.select_ranges([len..len])
+    //                 });
+    //                 if len > 0 {
+    //                     editor.insert("\n\n", cx);
+    //                 }
+    //                 editor.insert(&entry_heading, cx);
+    //                 editor.insert("\n\n", cx);
+    //             })?;
+    //         }
+    //     }
+
+    //     anyhow::Ok(())
+    // })
+    // .detach_and_log_err(cx);
+}
+
+fn journal_dir(path: &str) -> Option<PathBuf> {
+    let expanded_journal_dir = shellexpand::full(path) //TODO handle this better
+        .ok()
+        .map(|dir| Path::new(&dir.to_string()).to_path_buf().join("journal"));
+
+    return expanded_journal_dir;
+}
+
+fn heading_entry(now: NaiveTime, hour_format: &Option<HourFormat>) -> String {
+    match hour_format {
+        Some(HourFormat::Hour24) => {
+            let hour = now.hour();
+            format!("# {}:{:02}", hour, now.minute())
+        }
+        _ => {
+            let (pm, hour) = now.hour12();
+            let am_or_pm = if pm { "PM" } else { "AM" };
+            format!("# {}:{:02} {}", hour, now.minute(), am_or_pm)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    mod heading_entry_tests {
+        use super::super::*;
+
+        #[test]
+        fn test_heading_entry_defaults_to_hour_12() {
+            let naive_time = NaiveTime::from_hms_milli_opt(15, 0, 0, 0).unwrap();
+            let actual_heading_entry = heading_entry(naive_time, &None);
+            let expected_heading_entry = "# 3:00 PM";
+
+            assert_eq!(actual_heading_entry, expected_heading_entry);
+        }
+
+        #[test]
+        fn test_heading_entry_is_hour_12() {
+            let naive_time = NaiveTime::from_hms_milli_opt(15, 0, 0, 0).unwrap();
+            let actual_heading_entry = heading_entry(naive_time, &Some(HourFormat::Hour12));
+            let expected_heading_entry = "# 3:00 PM";
+
+            assert_eq!(actual_heading_entry, expected_heading_entry);
+        }
+
+        #[test]
+        fn test_heading_entry_is_hour_24() {
+            let naive_time = NaiveTime::from_hms_milli_opt(15, 0, 0, 0).unwrap();
+            let actual_heading_entry = heading_entry(naive_time, &Some(HourFormat::Hour24));
+            let expected_heading_entry = "# 15:00";
+
+            assert_eq!(actual_heading_entry, expected_heading_entry);
+        }
+    }
+}

crates/zed-actions/src/lib.rs 🔗

@@ -7,26 +7,26 @@ actions!(
     zed,
     [
         About,
+        DebugElements,
+        DecreaseBufferFontSize,
         Hide,
         HideOthers,
-        ShowAll,
+        IncreaseBufferFontSize,
         Minimize,
-        Zoom,
-        ToggleFullScreen,
-        Quit,
-        DebugElements,
-        OpenLog,
-        OpenLicenses,
-        OpenTelemetryLog,
+        OpenDefaultKeymap,
+        OpenDefaultSettings,
         OpenKeymap,
-        OpenSettings,
+        OpenLicenses,
         OpenLocalSettings,
-        OpenDefaultSettings,
-        OpenDefaultKeymap,
-        IncreaseBufferFontSize,
-        DecreaseBufferFontSize,
+        OpenLog,
+        OpenSettings,
+        OpenTelemetryLog,
+        Quit,
         ResetBufferFontSize,
         ResetDatabase,
+        ShowAll,
+        ToggleFullScreen,
+        Zoom,
     ]
 );
 

crates/zed2/Cargo.toml 🔗

@@ -43,7 +43,7 @@ fuzzy = { path = "../fuzzy" }
 # go_to_line = { path = "../go_to_line" }
 gpui2 = { path = "../gpui2" }
 install_cli = { path = "../install_cli" }
-# journal = { path = "../journal" }
+journal2 = { path = "../journal2" }
 language2 = { path = "../language2" }
 # language_selector = { path = "../language_selector" }
 lsp = { path = "../lsp" }

crates/zed2/src/main.rs 🔗

@@ -188,7 +188,7 @@ fn main() {
         // workspace::init(app_state.clone(), cx);
         // recent_projects::init(cx);
 
-        // journal::init(app_state.clone(), cx);
+        // journal2::init(app_state.clone(), cx);
         // language_selector::init(cx);
         // theme_selector::init(cx);
         // activity_indicator::init(cx);