Merge pull request #1402 from zed-industries/open-log

Antonio Scandurra created

Introduce `zed: open log` command

Change summary

crates/workspace/src/workspace.rs |  4 
crates/zed/src/main.rs            |  8 +++
crates/zed/src/zed.rs             | 70 +++++++++++++++++++++++++++++++-
3 files changed, 77 insertions(+), 5 deletions(-)

Detailed changes

crates/workspace/src/workspace.rs 🔗

@@ -949,11 +949,11 @@ impl Workspace {
         &mut self,
         cx: &mut ViewContext<Self>,
         app_state: Arc<AppState>,
-        mut callback: F,
+        callback: F,
     ) -> T
     where
         T: 'static,
-        F: FnMut(&mut Workspace, &mut ViewContext<Workspace>) -> T,
+        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
     {
         if self.project.read(cx).is_local() {
             callback(self, cx)

crates/zed/src/main.rs 🔗

@@ -57,6 +57,7 @@ fn main() {
     fs::create_dir_all(&logs_dir_path).expect("could not create logs path");
     init_logger(&logs_dir_path);
 
+    log::info!("========== starting zed ==========");
     let mut app = gpui::App::new(Assets).unwrap();
     let app_version = ZED_APP_VERSION
         .or_else(|| app.platform().app_version().ok())
@@ -210,6 +211,13 @@ fn init_logger(logs_dir_path: &Path) {
     } else {
         let level = LevelFilter::Info;
         let log_file_path = logs_dir_path.join("Zed.log");
+
+        // Prevent log file from becoming too large.
+        const MAX_LOG_BYTES: u64 = 1 * 1024 * 1024;
+        if fs::metadata(&log_file_path).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES) {
+            let _ = fs::rename(&log_file_path, logs_dir_path.join("Zed.log.old"));
+        }
+
         let log_file = OpenOptions::new()
             .create(true)
             .append(true)

crates/zed/src/zed.rs 🔗

@@ -9,6 +9,7 @@ use anyhow::{anyhow, Context, Result};
 use assets::Assets;
 use breadcrumbs::Breadcrumbs;
 pub use client;
+use collections::VecDeque;
 pub use contacts_panel;
 use contacts_panel::ContactsPanel;
 pub use editor;
@@ -52,6 +53,7 @@ actions!(
         Quit,
         DebugElements,
         OpenSettings,
+        OpenLog,
         OpenKeymap,
         OpenDefaultSettings,
         OpenDefaultKeymap,
@@ -65,9 +67,11 @@ actions!(
 const MIN_FONT_SIZE: f32 = 6.0;
 
 lazy_static! {
-    pub static ref ROOT_PATH: PathBuf = dirs::home_dir()
-        .expect("failed to determine home directory")
-        .join(".zed");
+    pub static ref HOME_PATH: PathBuf =
+        dirs::home_dir().expect("failed to determine home directory");
+    pub static ref LOG_PATH: PathBuf = HOME_PATH.join("Library/Logs/Zed/Zed.log");
+    pub static ref OLD_LOG_PATH: PathBuf = HOME_PATH.join("Library/Logs/Zed/Zed.log.old");
+    pub static ref ROOT_PATH: PathBuf = HOME_PATH.join(".zed");
     pub static ref SETTINGS_PATH: PathBuf = ROOT_PATH.join("settings.json");
     pub static ref KEYMAP_PATH: PathBuf = ROOT_PATH.join("keymap.json");
 }
@@ -120,6 +124,12 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
             });
         }
     });
+    cx.add_action({
+        let app_state = app_state.clone();
+        move |workspace: &mut Workspace, _: &OpenLog, cx: &mut ViewContext<Workspace>| {
+            open_log_file(workspace, app_state.clone(), cx);
+        }
+    });
     cx.add_action({
         let app_state = app_state.clone();
         move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
@@ -407,6 +417,60 @@ fn open_config_file(
     .detach_and_log_err(cx)
 }
 
+fn open_log_file(
+    workspace: &mut Workspace,
+    app_state: Arc<AppState>,
+    cx: &mut ViewContext<Workspace>,
+) {
+    const MAX_LINES: usize = 1000;
+
+    workspace.with_local_workspace(cx, app_state.clone(), |_, cx| {
+        cx.spawn_weak(|workspace, mut cx| async move {
+            let (old_log, new_log) = futures::join!(
+                app_state.fs.load(&OLD_LOG_PATH),
+                app_state.fs.load(&LOG_PATH)
+            );
+
+            if let Some(workspace) = workspace.upgrade(&cx) {
+                let mut lines = VecDeque::with_capacity(MAX_LINES);
+                for line in old_log
+                    .iter()
+                    .flat_map(|log| log.lines())
+                    .chain(new_log.iter().flat_map(|log| log.lines()))
+                {
+                    if lines.len() == MAX_LINES {
+                        lines.pop_front();
+                    }
+                    lines.push_back(line);
+                }
+                let log = lines
+                    .into_iter()
+                    .flat_map(|line| [line, "\n"])
+                    .collect::<String>();
+
+                workspace.update(&mut cx, |workspace, cx| {
+                    let project = workspace.project().clone();
+                    let buffer = project
+                        .update(cx, |project, cx| project.create_buffer("", None, cx))
+                        .expect("creating buffers on a local workspace always succeeds");
+                    buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], cx));
+
+                    let buffer = cx.add_model(|cx| {
+                        MultiBuffer::singleton(buffer, cx).with_title("Log".into())
+                    });
+                    workspace.add_item(
+                        Box::new(
+                            cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx)),
+                        ),
+                        cx,
+                    );
+                });
+            }
+        })
+        .detach();
+    });
+}
+
 fn open_bundled_config_file(
     workspace: &mut Workspace,
     app_state: Arc<AppState>,