1524 lines · 54.5 KB · Rust
RawDownload
use gpui::{    Animation, AnimationExt, Context, EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext,    KeybindingKeystroke, Keystroke, Modifiers, ModifiersChangedEvent, Subscription, Task, actions,};use ui::{    ActiveTheme as _, Color, IconButton, IconButtonShape, IconName, IconSize, Label, LabelSize,    ParentElement as _, Render, Styled as _, Tooltip, Window, prelude::*,}; actions!(    keystroke_input,    [        /// Starts recording keystrokes        StartRecording,        /// Stops recording keystrokes        StopRecording,        /// Clears the recorded keystrokes        ClearKeystrokes,    ]); const KEY_CONTEXT_VALUE: &str = "KeystrokeInput"; const CLOSE_KEYSTROKE_CAPTURE_END_TIMEOUT: std::time::Duration =    std::time::Duration::from_millis(300); enum CloseKeystrokeResult {    Partial,    Close,    None,} impl PartialEq for CloseKeystrokeResult {    fn eq(&self, other: &Self) -> bool {        matches!(            (self, other),            (CloseKeystrokeResult::Partial, CloseKeystrokeResult::Partial)                | (CloseKeystrokeResult::Close, CloseKeystrokeResult::Close)                | (CloseKeystrokeResult::None, CloseKeystrokeResult::None)        )    }} pub struct KeystrokeInput {    keystrokes: Vec<KeybindingKeystroke>,    placeholder_keystrokes: Option<Vec<KeybindingKeystroke>>,    outer_focus_handle: FocusHandle,    inner_focus_handle: FocusHandle,    intercept_subscription: Option<Subscription>,    _focus_subscriptions: [Subscription; 2],    search: bool,    /// The sequence of close keystrokes being typed    close_keystrokes: Option<Vec<Keystroke>>,    close_keystrokes_start: Option<usize>,    previous_modifiers: Modifiers,    /// In order to support inputting keystrokes that end with a prefix of the    /// close keybind keystrokes, we clear the close keystroke capture info    /// on a timeout after a close keystroke is pressed    ///    /// e.g. if close binding is `esc esc esc` and user wants to search for    /// `ctrl-g esc`, after entering the `ctrl-g esc`, hitting `esc` twice would    /// stop recording because of the sequence of three escapes making it    /// impossible to search for anything ending in `esc`    clear_close_keystrokes_timer: Option<Task<()>>,    #[cfg(test)]    recording: bool,    pub actions_slot: Option<AnyElement>,} impl KeystrokeInput {    const KEYSTROKE_COUNT_MAX: usize = 3;     pub fn new(        placeholder_keystrokes: Option<Vec<KeybindingKeystroke>>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let outer_focus_handle = cx.focus_handle();        let inner_focus_handle = cx.focus_handle();        let _focus_subscriptions = [            cx.on_focus_in(&inner_focus_handle, window, Self::on_inner_focus_in),            cx.on_focus_out(&inner_focus_handle, window, Self::on_inner_focus_out),        ];        Self {            keystrokes: Vec::new(),            placeholder_keystrokes,            inner_focus_handle,            outer_focus_handle,            intercept_subscription: None,            _focus_subscriptions,            search: false,            close_keystrokes: None,            close_keystrokes_start: None,            previous_modifiers: Modifiers::default(),            clear_close_keystrokes_timer: None,            #[cfg(test)]            recording: false,            actions_slot: None,        }    }     pub fn set_keystrokes(&mut self, keystrokes: Vec<KeybindingKeystroke>, cx: &mut Context<Self>) {        self.keystrokes = keystrokes;        self.keystrokes_changed(cx);    }     pub fn set_search(&mut self, search: bool) {        self.search = search;    }     pub fn keystrokes(&self) -> &[KeybindingKeystroke] {        if let Some(placeholders) = self.placeholder_keystrokes.as_ref()            && self.keystrokes.is_empty()        {            return placeholders;        }        if !self.search            && self                .keystrokes                .last()                .is_some_and(|last| last.key().is_empty())        {            return &self.keystrokes[..self.keystrokes.len() - 1];        }        &self.keystrokes    }     fn dummy(modifiers: Modifiers) -> KeybindingKeystroke {        KeybindingKeystroke::from_keystroke(Keystroke {            modifiers,            key: "".to_string(),            key_char: None,        })    }     fn keystrokes_changed(&self, cx: &mut Context<Self>) {        cx.emit(());        cx.notify();    }     fn key_context() -> KeyContext {        let mut key_context = KeyContext::default();        key_context.add(KEY_CONTEXT_VALUE);        key_context    }     fn determine_stop_recording_binding(window: &mut Window) -> Option<gpui::KeyBinding> {        if cfg!(test) {            Some(gpui::KeyBinding::new(                "escape escape escape",                StopRecording,                Some(KEY_CONTEXT_VALUE),            ))        } else {            window.highest_precedence_binding_for_action_in_context(                &StopRecording,                Self::key_context(),            )        }    }     fn upsert_close_keystrokes_start(&mut self, start: usize, cx: &mut Context<Self>) {        if self.close_keystrokes_start.is_some() {            return;        }        self.close_keystrokes_start = Some(start);        self.update_clear_close_keystrokes_timer(cx);    }     fn update_clear_close_keystrokes_timer(&mut self, cx: &mut Context<Self>) {        self.clear_close_keystrokes_timer = Some(cx.spawn(async |this, cx| {            cx.background_executor()                .timer(CLOSE_KEYSTROKE_CAPTURE_END_TIMEOUT)                .await;            this.update(cx, |this, _cx| {                this.end_close_keystrokes_capture();            })            .ok();        }));    }     /// Interrupt the capture of close keystrokes, but do not clear the close keystrokes    /// from the input    fn end_close_keystrokes_capture(&mut self) -> Option<usize> {        self.close_keystrokes.take();        self.clear_close_keystrokes_timer.take();        self.close_keystrokes_start.take()    }     fn handle_possible_close_keystroke(        &mut self,        keystroke: &Keystroke,        window: &mut Window,        cx: &mut Context<Self>,    ) -> CloseKeystrokeResult {        let Some(keybind_for_close_action) = Self::determine_stop_recording_binding(window) else {            log::trace!("No keybinding to stop recording keystrokes in keystroke input");            self.end_close_keystrokes_capture();            return CloseKeystrokeResult::None;        };        let action_keystrokes = keybind_for_close_action.keystrokes();         if let Some(mut close_keystrokes) = self.close_keystrokes.take() {            let mut index = 0;             while index < action_keystrokes.len() && index < close_keystrokes.len() {                if !close_keystrokes[index].should_match(&action_keystrokes[index]) {                    break;                }                index += 1;            }            if index == close_keystrokes.len() {                if index >= action_keystrokes.len() {                    self.end_close_keystrokes_capture();                    return CloseKeystrokeResult::None;                }                if keystroke.should_match(&action_keystrokes[index]) {                    close_keystrokes.push(keystroke.clone());                    if close_keystrokes.len() == action_keystrokes.len() {                        return CloseKeystrokeResult::Close;                    } else {                        self.close_keystrokes = Some(close_keystrokes);                        self.update_clear_close_keystrokes_timer(cx);                        return CloseKeystrokeResult::Partial;                    }                } else {                    self.end_close_keystrokes_capture();                    return CloseKeystrokeResult::None;                }            }        } else if let Some(first_action_keystroke) = action_keystrokes.first()            && keystroke.should_match(first_action_keystroke)        {            self.close_keystrokes = Some(vec![keystroke.clone()]);            return CloseKeystrokeResult::Partial;        }        self.end_close_keystrokes_capture();        CloseKeystrokeResult::None    }     fn on_modifiers_changed(        &mut self,        event: &ModifiersChangedEvent,        window: &mut Window,        cx: &mut Context<Self>,    ) {        cx.stop_propagation();        let keystrokes_len = self.keystrokes.len();         if self.previous_modifiers.modified()            && event.modifiers.is_subset_of(&self.previous_modifiers)        {            self.previous_modifiers &= event.modifiers;            return;        }        self.keystrokes_changed(cx);         if let Some(last) = self.keystrokes.last_mut()            && last.key().is_empty()            && keystrokes_len <= Self::KEYSTROKE_COUNT_MAX        {            if !self.search && !event.modifiers.modified() {                self.keystrokes.pop();                return;            }            if self.search {                if self.previous_modifiers.modified() {                    let modifiers = *last.modifiers() | event.modifiers;                    last.set_modifiers(modifiers);                } else {                    self.keystrokes.push(Self::dummy(event.modifiers));                }                self.previous_modifiers |= event.modifiers;            } else {                last.set_modifiers(event.modifiers);                return;            }        } else if keystrokes_len < Self::KEYSTROKE_COUNT_MAX {            self.keystrokes.push(Self::dummy(event.modifiers));            if self.search {                self.previous_modifiers |= event.modifiers;            }        }        if keystrokes_len >= Self::KEYSTROKE_COUNT_MAX {            self.clear_keystrokes(&ClearKeystrokes, window, cx);        }    }     fn handle_keystroke(        &mut self,        keystroke: &Keystroke,        window: &mut Window,        cx: &mut Context<Self>,    ) {        cx.stop_propagation();         let close_keystroke_result = self.handle_possible_close_keystroke(keystroke, window, cx);        if close_keystroke_result == CloseKeystrokeResult::Close {            self.stop_recording(&StopRecording, window, cx);            return;        }         let keystroke = KeybindingKeystroke::new_with_mapper(            keystroke.clone(),            false,            cx.keyboard_mapper().as_ref(),        );        if let Some(last) = self.keystrokes.last()            && last.key().is_empty()            && (!self.search || self.previous_modifiers.modified())        {            self.keystrokes.pop();        }         if close_keystroke_result == CloseKeystrokeResult::Partial {            self.upsert_close_keystrokes_start(self.keystrokes.len(), cx);            if self.keystrokes.len() >= Self::KEYSTROKE_COUNT_MAX {                return;            }        }         if self.keystrokes.len() >= Self::KEYSTROKE_COUNT_MAX {            self.clear_keystrokes(&ClearKeystrokes, window, cx);            return;        }         self.keystrokes.push(keystroke);        self.keystrokes_changed(cx);         // The reason we use the real modifiers from the window instead of the keystroke's modifiers        // is that for keystrokes like `ctrl-$` the modifiers reported by keystroke is `ctrl` which        // is wrong, it should be `ctrl-shift`. The window's modifiers are always correct.        let real_modifiers = window.modifiers();        if self.search {            self.previous_modifiers = real_modifiers;            return;        }        if self.keystrokes.len() < Self::KEYSTROKE_COUNT_MAX && real_modifiers.modified() {            self.keystrokes.push(Self::dummy(real_modifiers));        }    }     fn on_inner_focus_in(&mut self, _window: &mut Window, cx: &mut Context<Self>) {        if self.intercept_subscription.is_none() {            let listener = cx.listener(|this, event: &gpui::KeystrokeEvent, window, cx| {                this.handle_keystroke(&event.keystroke, window, cx);            });            self.intercept_subscription = Some(cx.intercept_keystrokes(listener))        }    }     fn on_inner_focus_out(        &mut self,        _event: gpui::FocusOutEvent,        _window: &mut Window,        cx: &mut Context<Self>,    ) {        self.intercept_subscription.take();        cx.notify();    }     fn render_keystrokes(&self, is_recording: bool) -> impl Iterator<Item = Div> {        let keystrokes = if let Some(placeholders) = self.placeholder_keystrokes.as_ref()            && self.keystrokes.is_empty()        {            if is_recording {                &[]            } else {                placeholders.as_slice()            }        } else {            &self.keystrokes        };        keystrokes.iter().map(move |keystroke| {            h_flex().children(ui::render_keybinding_keystroke(                keystroke,                Some(Color::Default),                Some(rems(0.875).into()),                ui::PlatformStyle::platform(),                false,            ))        })    }     pub fn start_recording(        &mut self,        _: &StartRecording,        window: &mut Window,        cx: &mut Context<Self>,    ) {        window.focus(&self.inner_focus_handle, cx);        self.clear_keystrokes(&ClearKeystrokes, window, cx);        self.previous_modifiers = window.modifiers();        #[cfg(test)]        {            self.recording = true;        }        cx.stop_propagation();    }     pub fn stop_recording(        &mut self,        _: &StopRecording,        window: &mut Window,        cx: &mut Context<Self>,    ) {        if !self.is_recording(window) {            return;        }        window.focus(&self.outer_focus_handle, cx);        if let Some(close_keystrokes_start) = self.close_keystrokes_start.take()            && close_keystrokes_start < self.keystrokes.len()        {            self.keystrokes.drain(close_keystrokes_start..);            self.keystrokes_changed(cx);        }        self.end_close_keystrokes_capture();        #[cfg(test)]        {            self.recording = false;        }        cx.notify();    }     pub fn clear_keystrokes(        &mut self,        _: &ClearKeystrokes,        _window: &mut Window,        cx: &mut Context<Self>,    ) {        self.keystrokes.clear();        self.keystrokes_changed(cx);        self.end_close_keystrokes_capture();    }     fn is_recording(&self, window: &Window) -> bool {        #[cfg(test)]        {            if true {                // in tests, we just need a simple bool that is toggled on start and stop recording                return self.recording;            }        }        // however, in the real world, checking if the inner focus handle is focused        // is a much more reliable check, as the intercept keystroke handlers are installed        // on focus of the inner focus handle, thereby ensuring our recording state does        // not get de-synced        self.inner_focus_handle.is_focused(window)    }     pub fn actions_slot(mut self, action: impl IntoElement) -> Self {        self.actions_slot = Some(action.into_any_element());        self    }} impl EventEmitter<()> for KeystrokeInput {} impl Focusable for KeystrokeInput {    fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {        self.outer_focus_handle.clone()    }} impl Render for KeystrokeInput {    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        let colors = cx.theme().colors();        let is_focused = self.outer_focus_handle.contains_focused(window, cx);        let is_recording = self.is_recording(window);         let width = rems_from_px(64.);         let recording_bg_color = colors            .editor_background            .blend(colors.text_accent.opacity(0.1));         let recording_pulse = |color: Color| {            Icon::new(IconName::Circle)                .size(IconSize::Small)                .color(Color::Error)                .with_animation(                    "recording-pulse",                    Animation::new(std::time::Duration::from_secs(2))                        .repeat()                        .with_easing(gpui::pulsating_between(0.4, 0.8)),                    {                        let color = color.color(cx);                        move |this, delta| this.color(Color::Custom(color.opacity(delta)))                    },                )        };         let recording_indicator = h_flex()            .h_4()            .pr_1()            .gap_0p5()            .border_1()            .border_color(colors.border)            .bg(colors                .editor_background

Lines 1–500 of 1524.