diff --git a/Cargo.lock b/Cargo.lock index 6a6ba400c7..8e310cf273 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6994,7 +6994,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.49.0" +version = "0.49.1" dependencies = [ "activity_indicator", "anyhow", diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 2da8c26342..5262daab5f 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -14,30 +14,30 @@ "k": "vim::Up", "l": "vim::Right", "0": "vim::StartOfLine", - "shift-$": "vim::EndOfLine", - "shift-G": "vim::EndOfDocument", + "$": "vim::EndOfLine", + "shift-g": "vim::EndOfDocument", "w": "vim::NextWordStart", - "shift-W": [ + "shift-w": [ "vim::NextWordStart", { "ignorePunctuation": true } ], "e": "vim::NextWordEnd", - "shift-E": [ + "shift-e": [ "vim::NextWordEnd", { "ignorePunctuation": true } ], "b": "vim::PreviousWordStart", - "shift-B": [ + "shift-b": [ "vim::PreviousWordStart", { "ignorePunctuation": true } ], - "shift-%": "vim::Matching", + "%": "vim::Matching", "escape": "editor::Cancel" } }, @@ -48,12 +48,12 @@ "vim::PushOperator", "Change" ], - "shift-C": "vim::ChangeToEndOfLine", + "shift-c": "vim::ChangeToEndOfLine", "d": [ "vim::PushOperator", "Delete" ], - "shift-D": "vim::DeleteToEndOfLine", + "shift-d": "vim::DeleteToEndOfLine", "y": [ "vim::PushOperator", "Yank" @@ -62,14 +62,14 @@ "vim::SwitchMode", "Insert" ], - "shift-I": "vim::InsertFirstNonWhitespace", + "shift-i": "vim::InsertFirstNonWhitespace", "a": "vim::InsertAfter", - "shift-A": "vim::InsertEndOfLine", + "shift-a": "vim::InsertEndOfLine", "x": "vim::DeleteRight", - "shift-X": "vim::DeleteLeft", - "shift-^": "vim::FirstNonWhitespace", + "shift-x": "vim::DeleteLeft", + "^": "vim::FirstNonWhitespace", "o": "vim::InsertLineBelow", - "shift-O": "vim::InsertLineAbove", + "shift-o": "vim::InsertLineAbove", "v": [ "vim::SwitchMode", { @@ -78,7 +78,7 @@ } } ], - "shift-V": [ + "shift-v": [ "vim::SwitchMode", { "Visual": { @@ -113,7 +113,7 @@ "context": "Editor && vim_operator == c", "bindings": { "w": "vim::ChangeWord", - "shift-W": [ + "shift-w": [ "vim::ChangeWord", { "ignorePunctuation": true diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index 22cb3ba5de..d32344b0db 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -41,6 +41,7 @@ pub struct Keystroke { pub alt: bool, pub shift: bool, pub cmd: bool, + pub function: bool, pub key: String, } @@ -277,6 +278,7 @@ impl Keystroke { let mut alt = false; let mut shift = false; let mut cmd = false; + let mut function = false; let mut key = None; let mut components = source.split("-").peekable(); @@ -286,6 +288,7 @@ impl Keystroke { "alt" => alt = true, "shift" => shift = true, "cmd" => cmd = true, + "fn" => function = true, _ => { if let Some(component) = components.peek() { if component.is_empty() && source.ends_with('-') { @@ -306,6 +309,7 @@ impl Keystroke { alt, shift, cmd, + function, key: key.unwrap(), }) } @@ -464,6 +468,7 @@ mod tests { alt: false, shift: false, cmd: false, + function: false, } ); @@ -475,6 +480,7 @@ mod tests { alt: true, shift: true, cmd: false, + function: false, } ); @@ -486,6 +492,7 @@ mod tests { alt: false, shift: true, cmd: true, + function: false, } ); diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index e0e178aa8c..5aedc63b2e 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -210,19 +210,24 @@ impl Event { unsafe fn parse_keystroke(native_event: id) -> Keystroke { use cocoa::appkit::*; - let modifiers = native_event.modifierFlags(); - let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask); - let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask); - let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask); - let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask); - let mut chars_ignoring_modifiers = CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char) .to_str() .unwrap(); + let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16); + let modifiers = native_event.modifierFlags(); + + let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask); + let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask); + let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask); + let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask); + let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask) + && first_char.map_or(true, |ch| { + ch < NSUpArrowFunctionKey || ch > NSModeSwitchFunctionKey + }); #[allow(non_upper_case_globals)] - let key = match chars_ignoring_modifiers.chars().next().map(|ch| ch as u16) { + let key = match first_char { Some(SPACE_KEY) => "space", Some(BACKSPACE_KEY) => "backspace", Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter", @@ -282,6 +287,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { alt, shift, cmd, + function, key: key.into(), } } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 5dc10c7b57..8895757539 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -184,6 +184,7 @@ impl MacForegroundPlatform { (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask), (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask), (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask), + (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask), ] { if *modifier { mask |= *flag; diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index de175812ba..05cc542b98 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -154,6 +154,10 @@ unsafe fn build_classes() { sel!(performKeyEquivalent:), handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL, ); + decl.add_method( + sel!(keyDown:), + handle_key_down as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(mouseDown:), handle_view_event as extern "C" fn(&Object, Sel, id), @@ -275,7 +279,8 @@ struct WindowState { should_close_callback: Option bool>>, close_callback: Option>, input_handler: Option>, - pending_key_down_event: Option, + pending_key_down: Option<(KeyDownEvent, Option)>, + performed_key_equivalent: bool, synthetic_drag_counter: usize, executor: Rc, scene_to_render: Option, @@ -287,6 +292,11 @@ struct WindowState { previous_modifiers_changed_event: Option, } +struct InsertText { + replacement_range: Option>, + text: String, +} + impl Window { pub fn open( id: usize, @@ -359,7 +369,8 @@ impl Window { close_callback: None, activate_callback: None, input_handler: None, - pending_key_down_event: None, + pending_key_down: None, + performed_key_equivalent: false, synthetic_drag_counter: 0, executor, scene_to_render: Default::default(), @@ -689,13 +700,28 @@ extern "C" fn dealloc_view(this: &Object, _: Sel) { } extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL { + handle_key_event(this, native_event, true) +} + +extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) { + handle_key_event(this, native_event, false); +} + +extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL { let window_state = unsafe { get_window_state(this) }; let mut window_state_borrow = window_state.as_ref().borrow_mut(); let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) }; if let Some(event) = event { - window_state_borrow.pending_key_down_event = match event { + if key_equivalent { + window_state_borrow.performed_key_equivalent = true; + } else if window_state_borrow.performed_key_equivalent { + return NO; + } + + let function_is_held; + window_state_borrow.pending_key_down = match event { Event::KeyDown(event) => { let keydown = event.keystroke.clone(); // Ignore events from held-down keys after some of the initially-pressed keys @@ -708,19 +734,23 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> window_state_borrow.last_fresh_keydown = Some(keydown); } - Some(event) + function_is_held = event.keystroke.function; + Some((event, None)) } _ => return NO, }; drop(window_state_borrow); - unsafe { - let input_context: id = msg_send![this, inputContext]; - let _: BOOL = msg_send![input_context, handleEvent: native_event]; + if !function_is_held { + unsafe { + let input_context: id = msg_send![this, inputContext]; + let _: BOOL = msg_send![input_context, handleEvent: native_event]; + } } + let mut handled = false; let mut window_state_borrow = window_state.borrow_mut(); - if let Some(event) = window_state_borrow.pending_key_down_event.take() { + if let Some((event, insert_text)) = window_state_borrow.pending_key_down.take() { if let Some(mut callback) = window_state_borrow.event_callback.take() { drop(window_state_borrow); @@ -729,14 +759,26 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> .flatten() .is_some(); if !is_composing { - callback(Event::KeyDown(event)); + handled = callback(Event::KeyDown(event)); + } + + if !handled { + if let Some(insert) = insert_text { + handled = true; + with_input_handler(this, |input_handler| { + input_handler + .replace_text_in_range(insert.replacement_range, &insert.text) + }); + } } window_state.borrow_mut().event_callback = Some(callback); } + } else { + handled = true; } - YES + handled as BOOL } else { NO } @@ -819,6 +861,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { ctrl: false, alt: false, shift: false, + function: false, key: ".".into(), }; let event = Event::KeyDown(KeyDownEvent { @@ -837,6 +880,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { extern "C" fn send_event(this: &Object, _: Sel, native_event: id) { unsafe { let () = msg_send![super(this, class!(NSWindow)), sendEvent: native_event]; + get_window_state(this).borrow_mut().performed_key_equivalent = false; } } @@ -1042,7 +1086,7 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS unsafe { let window_state = get_window_state(this); let mut window_state_borrow = window_state.borrow_mut(); - let pending_key_down_event = window_state_borrow.pending_key_down_event.take(); + let pending_key_down = window_state_borrow.pending_key_down.take(); drop(window_state_borrow); let is_attributed_string: BOOL = @@ -1062,24 +1106,17 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS .flatten() .is_some(); - if is_composing || text.chars().count() > 1 || pending_key_down_event.is_none() { + if is_composing || text.chars().count() > 1 || pending_key_down.is_none() { with_input_handler(this, |input_handler| { input_handler.replace_text_in_range(replacement_range, text) }); } else { - let mut handled = false; - - let event_callback = window_state.borrow_mut().event_callback.take(); - if let Some(mut event_callback) = event_callback { - handled = event_callback(Event::KeyDown(pending_key_down_event.unwrap())); - window_state.borrow_mut().event_callback = Some(event_callback); - } - - if !handled { - with_input_handler(this, |input_handler| { - input_handler.replace_text_in_range(replacement_range, text) - }); - } + let mut pending_key_down = pending_key_down.unwrap(); + pending_key_down.1 = Some(InsertText { + replacement_range, + text: text.to_string(), + }); + window_state.borrow_mut().pending_key_down = Some(pending_key_down); } } } @@ -1092,10 +1129,7 @@ extern "C" fn set_marked_text( replacement_range: NSRange, ) { unsafe { - get_window_state(this) - .borrow_mut() - .pending_key_down_event - .take(); + get_window_state(this).borrow_mut().pending_key_down.take(); let is_attributed_string: BOOL = msg_send![text, isKindOfClass: [class!(NSAttributedString)]]; diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 187b018249..7693b39d81 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -173,4 +173,12 @@ impl View for ConnectedView { self.terminal .update(cx, |terminal, _| terminal.write_to_pty(text.into())); } + + fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context { + let mut context = Self::default_keymap_context(); + if self.modal { + context.set.insert("ModalTerminal".into()); + } + context + } } diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index f88bfa927a..3ed46de459 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -311,6 +311,7 @@ mod test { alt: false, shift: false, cmd: false, + function: false, key: "🖖🏻".to_string(), //2 char string }; assert_eq!(to_esc_str(&ks, &TermMode::NONE), None); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 27339d2d00..028a8f23b0 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -171,14 +171,6 @@ impl View for TerminalView { cx.focus(view.content.handle()); }); } - - fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context { - let mut context = Self::default_keymap_context(); - if self.modal { - context.set.insert("ModalTerminal".into()); - } - context - } } impl View for ErrorView { diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 8ec0fea9a9..13055a9df7 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -427,7 +427,7 @@ mod test { #[gpui::test] async fn test_jump_to_line_boundaries(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["shift-$"]); + let mut cx = cx.binding(["$"]); cx.assert("T|est test", "Test tes|t"); cx.assert("Test tes|t", "Test tes|t"); cx.assert( @@ -471,7 +471,7 @@ mod test { #[gpui::test] async fn test_jump_to_end(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["shift-G"]); + let mut cx = cx.binding(["shift-g"]); cx.assert( indoc! {" @@ -561,7 +561,7 @@ mod test { ); for cursor_offset in cursor_offsets { - cx.simulate_keystroke("shift-W"); + cx.simulate_keystroke("shift-w"); cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]); } } @@ -607,7 +607,7 @@ mod test { Mode::Normal, ); for cursor_offset in cursor_offsets { - cx.simulate_keystroke("shift-E"); + cx.simulate_keystroke("shift-e"); cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]); } } @@ -653,7 +653,7 @@ mod test { Mode::Normal, ); for cursor_offset in cursor_offsets.into_iter().rev() { - cx.simulate_keystroke("shift-B"); + cx.simulate_keystroke("shift-b"); cx.assert_editor_selections(vec![Selection::from_offset(cursor_offset)]); } } @@ -740,7 +740,7 @@ mod test { #[gpui::test] async fn test_insert_end_of_line(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["shift-A"]).mode_after(Mode::Insert); + let mut cx = cx.binding(["shift-a"]).mode_after(Mode::Insert); cx.assert("The q|uick", "The quick|"); cx.assert("The q|uick ", "The quick |"); cx.assert("|", "|"); @@ -765,7 +765,7 @@ mod test { #[gpui::test] async fn test_jump_to_first_non_whitespace(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["shift-^"]); + let mut cx = cx.binding(["^"]); cx.assert("The q|uick", "|The quick"); cx.assert(" The q|uick", " |The quick"); cx.assert("|", "|"); @@ -792,7 +792,7 @@ mod test { #[gpui::test] async fn test_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["shift-I"]).mode_after(Mode::Insert); + let mut cx = cx.binding(["shift-i"]).mode_after(Mode::Insert); cx.assert("The q|uick", "|The quick"); cx.assert(" The q|uick", " |The quick"); cx.assert("|", "|"); @@ -817,7 +817,7 @@ mod test { #[gpui::test] async fn test_delete_to_end_of_line(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["shift-D"]); + let mut cx = cx.binding(["shift-d"]); cx.assert( indoc! {" The q|uick @@ -858,7 +858,7 @@ mod test { #[gpui::test] async fn test_delete_left(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["shift-X"]); + let mut cx = cx.binding(["shift-x"]); cx.assert("Te|st", "T|st"); cx.assert("T|est", "|est"); cx.assert("|Test", "|Test"); @@ -956,7 +956,7 @@ mod test { #[gpui::test] async fn test_insert_line_above(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["shift-O"]).mode_after(Mode::Insert); + let mut cx = cx.binding(["shift-o"]).mode_after(Mode::Insert); cx.assert( "|", diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index 55445c930e..03f213b584 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -139,7 +139,7 @@ mod test { test"}, ); - let mut cx = cx.binding(["c", "shift-W"]); + let mut cx = cx.binding(["c", "shift-w"]); cx.assert("Test te|st-test test", "Test te| test"); } @@ -174,7 +174,7 @@ mod test { test"}, ); - let mut cx = cx.binding(["c", "shift-E"]); + let mut cx = cx.binding(["c", "shift-e"]); cx.assert("Test te|st-test test", "Test te| test"); } @@ -204,14 +204,14 @@ mod test { test"}, ); - let mut cx = cx.binding(["c", "shift-B"]); + let mut cx = cx.binding(["c", "shift-b"]); cx.assert("Test test-test |test", "Test |test"); } #[gpui::test] async fn test_change_end_of_line(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "shift-$"]).mode_after(Mode::Insert); + let mut cx = cx.binding(["c", "$"]).mode_after(Mode::Insert); cx.assert( indoc! {" The q|uick @@ -347,7 +347,7 @@ mod test { #[gpui::test] async fn test_change_end_of_document(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["c", "shift-G"]).mode_after(Mode::Insert); + let mut cx = cx.binding(["c", "shift-g"]).mode_after(Mode::Insert); cx.assert( indoc! {" The quick diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index c5c823c79e..ca2c27cf70 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -109,7 +109,7 @@ mod test { test"}, ); - let mut cx = cx.binding(["d", "shift-W"]); + let mut cx = cx.binding(["d", "shift-w"]); cx.assert("Test te|st-test test", "Test te|test"); } @@ -144,7 +144,7 @@ mod test { test"}, ); - let mut cx = cx.binding(["d", "shift-E"]); + let mut cx = cx.binding(["d", "shift-e"]); cx.assert("Test te|st-test test", "Test te| test"); } @@ -176,14 +176,14 @@ mod test { test"}, ); - let mut cx = cx.binding(["d", "shift-B"]); + let mut cx = cx.binding(["d", "shift-b"]); cx.assert("Test test-test |test", "Test |test"); } #[gpui::test] async fn test_delete_end_of_line(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "shift-$"]); + let mut cx = cx.binding(["d", "$"]); cx.assert( indoc! {" The q|uick @@ -304,7 +304,7 @@ mod test { #[gpui::test] async fn test_delete_end_of_document(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["d", "shift-G"]); + let mut cx = cx.binding(["d", "shift-g"]); cx.assert( indoc! {" The quick diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index a81ac1c455..d9772c031b 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -422,7 +422,7 @@ mod test { #[gpui::test] async fn test_visual_line_delete(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["shift-V", "x"]); + let mut cx = cx.binding(["shift-v", "x"]); cx.assert( indoc! {" The qu|ick brown @@ -457,7 +457,7 @@ mod test { The quick brown fox ju|mps over"}, ); - let mut cx = cx.binding(["shift-V", "j", "x"]); + let mut cx = cx.binding(["shift-v", "j", "x"]); cx.assert( indoc! {" The qu|ick brown @@ -558,7 +558,7 @@ mod test { #[gpui::test] async fn test_visual_line_change(cx: &mut gpui::TestAppContext) { let cx = VimTestContext::new(cx, true).await; - let mut cx = cx.binding(["shift-V", "c"]).mode_after(Mode::Insert); + let mut cx = cx.binding(["shift-v", "c"]).mode_after(Mode::Insert); cx.assert( indoc! {" The qu|ick brown @@ -597,7 +597,7 @@ mod test { fox jumps over |"}, ); - let mut cx = cx.binding(["shift-V", "j", "c"]).mode_after(Mode::Insert); + let mut cx = cx.binding(["shift-v", "j", "c"]).mode_after(Mode::Insert); cx.assert( indoc! {" The qu|ick brown diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e8ff1932d4..ac3d1c3696 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -949,11 +949,11 @@ impl Workspace { &mut self, cx: &mut ViewContext, app_state: Arc, - mut callback: F, + callback: F, ) -> T where T: 'static, - F: FnMut(&mut Workspace, &mut ViewContext) -> T, + F: FnOnce(&mut Workspace, &mut ViewContext) -> T, { if self.project.read(cx).is_local() { callback(self, cx) diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d56305cee9..be20be2fd7 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.49.0" +version = "0.49.1" [lib] name = "zed" diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 337e3afc2d..2ca1403b6c 100644 --- a/crates/zed/src/main.rs +++ b/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) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 867913fc7b..6ed9b40a18 100644 --- a/crates/zed/src/zed.rs +++ b/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, cx: &mut gpui::MutableAppContext) { }); } }); + cx.add_action({ + let app_state = app_state.clone(); + move |workspace: &mut Workspace, _: &OpenLog, cx: &mut ViewContext| { + open_log_file(workspace, app_state.clone(), cx); + } + }); cx.add_action({ let app_state = app_state.clone(); move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext| { @@ -407,6 +417,60 @@ fn open_config_file( .detach_and_log_err(cx) } +fn open_log_file( + workspace: &mut Workspace, + app_state: Arc, + cx: &mut ViewContext, +) { + 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::(); + + 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)], None, 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,