Add keymap editor UI telemetry events (#34571)
- Search queries - Keybinding update or removed - Copy action name - Copy context name cc @katie-z-geer Release Notes: - N/A Co-authored-by: Ben Kunkle <ben@zed.dev>
This commit is contained in:
parent
6f60939d30
commit
0bde929d54
4 changed files with 132 additions and 3 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -14720,6 +14720,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
|
"telemetry",
|
||||||
"theme",
|
"theme",
|
||||||
"tree-sitter-json",
|
"tree-sitter-json",
|
||||||
"tree-sitter-rust",
|
"tree-sitter-rust",
|
||||||
|
|
|
@ -847,6 +847,7 @@ impl KeymapFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum KeybindUpdateOperation<'a> {
|
pub enum KeybindUpdateOperation<'a> {
|
||||||
Replace {
|
Replace {
|
||||||
/// Describes the keybind to create
|
/// Describes the keybind to create
|
||||||
|
@ -865,6 +866,47 @@ pub enum KeybindUpdateOperation<'a> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl KeybindUpdateOperation<'_> {
|
||||||
|
pub fn generate_telemetry(
|
||||||
|
&self,
|
||||||
|
) -> (
|
||||||
|
// The keybind that is created
|
||||||
|
String,
|
||||||
|
// The keybinding that was removed
|
||||||
|
String,
|
||||||
|
// The source of the keybinding
|
||||||
|
String,
|
||||||
|
) {
|
||||||
|
let (new_binding, removed_binding, source) = match &self {
|
||||||
|
KeybindUpdateOperation::Replace {
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
target_keybind_source,
|
||||||
|
} => (Some(source), Some(target), Some(*target_keybind_source)),
|
||||||
|
KeybindUpdateOperation::Add { source, .. } => (Some(source), None, None),
|
||||||
|
KeybindUpdateOperation::Remove {
|
||||||
|
target,
|
||||||
|
target_keybind_source,
|
||||||
|
} => (None, Some(target), Some(*target_keybind_source)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_binding = new_binding
|
||||||
|
.map(KeybindUpdateTarget::telemetry_string)
|
||||||
|
.unwrap_or("null".to_owned());
|
||||||
|
let removed_binding = removed_binding
|
||||||
|
.map(KeybindUpdateTarget::telemetry_string)
|
||||||
|
.unwrap_or("null".to_owned());
|
||||||
|
|
||||||
|
let source = source
|
||||||
|
.as_ref()
|
||||||
|
.map(KeybindSource::name)
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.unwrap_or("null".to_owned());
|
||||||
|
|
||||||
|
(new_binding, removed_binding, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> KeybindUpdateOperation<'a> {
|
impl<'a> KeybindUpdateOperation<'a> {
|
||||||
pub fn add(source: KeybindUpdateTarget<'a>) -> Self {
|
pub fn add(source: KeybindUpdateTarget<'a>) -> Self {
|
||||||
Self::Add { source, from: None }
|
Self::Add { source, from: None }
|
||||||
|
@ -905,6 +947,16 @@ impl<'a> KeybindUpdateTarget<'a> {
|
||||||
keystrokes.pop();
|
keystrokes.pop();
|
||||||
keystrokes
|
keystrokes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn telemetry_string(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"action_name: {}, context: {}, action_arguments: {}, keystrokes: {}",
|
||||||
|
self.action_name,
|
||||||
|
self.context.unwrap_or("global"),
|
||||||
|
self.action_arguments.unwrap_or("none"),
|
||||||
|
self.keystrokes_unparsed()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
|
@ -34,6 +34,7 @@ search.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
telemetry.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
tree-sitter-json.workspace = true
|
tree-sitter-json.workspace = true
|
||||||
tree-sitter-rust.workspace = true
|
tree-sitter-rust.workspace = true
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
ops::{Not as _, Range},
|
ops::{Not as _, Range},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context as _, anyhow};
|
use anyhow::{Context as _, anyhow};
|
||||||
|
@ -12,7 +13,7 @@ use gpui::{
|
||||||
Action, Animation, AnimationExt, AppContext as _, AsyncApp, Axis, ClickEvent, Context,
|
Action, Animation, AnimationExt, AppContext as _, AsyncApp, Axis, ClickEvent, Context,
|
||||||
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, IsZero,
|
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, IsZero,
|
||||||
KeyContext, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Point, ScrollStrategy,
|
KeyContext, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Point, ScrollStrategy,
|
||||||
ScrollWheelEvent, StyledText, Subscription, WeakEntity, actions, anchored, deferred, div,
|
ScrollWheelEvent, StyledText, Subscription, Task, WeakEntity, actions, anchored, deferred, div,
|
||||||
};
|
};
|
||||||
use language::{Language, LanguageConfig, ToOffset as _};
|
use language::{Language, LanguageConfig, ToOffset as _};
|
||||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||||
|
@ -151,6 +152,13 @@ impl SearchMode {
|
||||||
SearchMode::KeyStroke { .. } => SearchMode::Normal,
|
SearchMode::KeyStroke { .. } => SearchMode::Normal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn exact_match(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
SearchMode::Normal => false,
|
||||||
|
SearchMode::KeyStroke { exact_match } => *exact_match,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Copy, Clone)]
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
|
@ -249,6 +257,7 @@ struct KeymapEditor {
|
||||||
keybinding_conflict_state: ConflictState,
|
keybinding_conflict_state: ConflictState,
|
||||||
filter_state: FilterState,
|
filter_state: FilterState,
|
||||||
search_mode: SearchMode,
|
search_mode: SearchMode,
|
||||||
|
search_query_debounce: Option<Task<()>>,
|
||||||
// corresponds 1 to 1 with keybindings
|
// corresponds 1 to 1 with keybindings
|
||||||
string_match_candidates: Arc<Vec<StringMatchCandidate>>,
|
string_match_candidates: Arc<Vec<StringMatchCandidate>>,
|
||||||
matches: Vec<StringMatch>,
|
matches: Vec<StringMatch>,
|
||||||
|
@ -347,6 +356,7 @@ impl KeymapEditor {
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
previous_edit: None,
|
previous_edit: None,
|
||||||
humanized_action_names,
|
humanized_action_names,
|
||||||
|
search_query_debounce: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.on_keymap_changed(cx);
|
this.on_keymap_changed(cx);
|
||||||
|
@ -371,10 +381,32 @@ impl KeymapEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_query_changed(&self, cx: &mut Context<Self>) {
|
fn on_query_changed(&mut self, cx: &mut Context<Self>) {
|
||||||
let action_query = self.current_action_query(cx);
|
let action_query = self.current_action_query(cx);
|
||||||
let keystroke_query = self.current_keystroke_query(cx);
|
let keystroke_query = self.current_keystroke_query(cx);
|
||||||
|
let exact_match = self.search_mode.exact_match();
|
||||||
|
|
||||||
|
let timer = cx.background_executor().timer(Duration::from_secs(1));
|
||||||
|
self.search_query_debounce = Some(cx.background_spawn({
|
||||||
|
let action_query = action_query.clone();
|
||||||
|
let keystroke_query = keystroke_query.clone();
|
||||||
|
async move {
|
||||||
|
timer.await;
|
||||||
|
|
||||||
|
let keystroke_query = keystroke_query
|
||||||
|
.into_iter()
|
||||||
|
.map(|keystroke| keystroke.unparse())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
telemetry::event!(
|
||||||
|
"Keystroke Search Completed",
|
||||||
|
action_query = action_query,
|
||||||
|
keystroke_query = keystroke_query,
|
||||||
|
keystroke_exact_match = exact_match
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}));
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
Self::update_matches(this.clone(), action_query, keystroke_query, cx).await?;
|
Self::update_matches(this.clone(), action_query, keystroke_query, cx).await?;
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
@ -474,6 +506,7 @@ impl KeymapEditor {
|
||||||
}
|
}
|
||||||
this.selected_index.take();
|
this.selected_index.take();
|
||||||
this.matches = matches;
|
this.matches = matches;
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -864,6 +897,26 @@ impl KeymapEditor {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let keymap_editor = cx.entity();
|
let keymap_editor = cx.entity();
|
||||||
|
|
||||||
|
let arguments = keybind
|
||||||
|
.action_arguments
|
||||||
|
.as_ref()
|
||||||
|
.map(|arguments| arguments.text.clone());
|
||||||
|
let context = keybind
|
||||||
|
.context
|
||||||
|
.as_ref()
|
||||||
|
.map(|context| context.local_str().unwrap_or("global"));
|
||||||
|
let source = keybind.source.as_ref().map(|source| source.1.clone());
|
||||||
|
|
||||||
|
telemetry::event!(
|
||||||
|
"Edit Keybinding Modal Opened",
|
||||||
|
keystroke = keybind.keystroke_text,
|
||||||
|
action = keybind.action_name,
|
||||||
|
source = source,
|
||||||
|
context = context,
|
||||||
|
arguments = arguments,
|
||||||
|
);
|
||||||
|
|
||||||
self.workspace
|
self.workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
let fs = workspace.app_state().fs.clone();
|
let fs = workspace.app_state().fs.clone();
|
||||||
|
@ -899,7 +952,7 @@ impl KeymapEditor {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(fs) = self
|
let std::result::Result::Ok(fs) = self
|
||||||
.workspace
|
.workspace
|
||||||
.read_with(cx, |workspace, _| workspace.app_state().fs.clone())
|
.read_with(cx, |workspace, _| workspace.app_state().fs.clone())
|
||||||
else {
|
else {
|
||||||
|
@ -929,6 +982,8 @@ impl KeymapEditor {
|
||||||
let Some(context) = context else {
|
let Some(context) = context else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
telemetry::event!("Keybinding Context Copied", context = context.clone());
|
||||||
cx.write_to_clipboard(gpui::ClipboardItem::new_string(context.clone()));
|
cx.write_to_clipboard(gpui::ClipboardItem::new_string(context.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -944,6 +999,8 @@ impl KeymapEditor {
|
||||||
let Some(action) = action else {
|
let Some(action) = action else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
telemetry::event!("Keybinding Action Copied", action = action.clone());
|
||||||
cx.write_to_clipboard(gpui::ClipboardItem::new_string(action.clone()));
|
cx.write_to_clipboard(gpui::ClipboardItem::new_string(action.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2222,6 +2279,9 @@ async fn save_keybinding_update(
|
||||||
from: Some(target),
|
from: Some(target),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
|
||||||
|
|
||||||
let updated_keymap_contents =
|
let updated_keymap_contents =
|
||||||
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
|
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
|
||||||
.context("Failed to update keybinding")?;
|
.context("Failed to update keybinding")?;
|
||||||
|
@ -2231,6 +2291,13 @@ async fn save_keybinding_update(
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to write keymap file")?;
|
.context("Failed to write keymap file")?;
|
||||||
|
|
||||||
|
telemetry::event!(
|
||||||
|
"Keybinding Updated",
|
||||||
|
new_keybinding = new_keybinding,
|
||||||
|
removed_keybinding = removed_keybinding,
|
||||||
|
source = source
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2266,6 +2333,7 @@ async fn remove_keybinding(
|
||||||
.unwrap_or(KeybindSource::User),
|
.unwrap_or(KeybindSource::User),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
|
||||||
let updated_keymap_contents =
|
let updated_keymap_contents =
|
||||||
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
|
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
|
||||||
.context("Failed to update keybinding")?;
|
.context("Failed to update keybinding")?;
|
||||||
|
@ -2275,6 +2343,13 @@ async fn remove_keybinding(
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to write keymap file")?;
|
.context("Failed to write keymap file")?;
|
||||||
|
|
||||||
|
telemetry::event!(
|
||||||
|
"Keybinding Removed",
|
||||||
|
new_keybinding = new_keybinding,
|
||||||
|
removed_keybinding = removed_keybinding,
|
||||||
|
source = source
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue