Display keymap errors on initial load (#23394)
Also fixes issue introduced in #23113 where changes to keyboard layout would not cause reload of keymap configuration. Closes #20531 Release Notes: - N/A
This commit is contained in:
parent
04c04e8406
commit
cc1af7d96b
3 changed files with 147 additions and 82 deletions
|
@ -119,9 +119,6 @@ pub enum KeymapFileLoadResult {
|
||||||
key_bindings: Vec<KeyBinding>,
|
key_bindings: Vec<KeyBinding>,
|
||||||
error_message: MarkdownString,
|
error_message: MarkdownString,
|
||||||
},
|
},
|
||||||
AllFailedToLoad {
|
|
||||||
error_message: MarkdownString,
|
|
||||||
},
|
|
||||||
JsonParseFailure {
|
JsonParseFailure {
|
||||||
error: anyhow::Error,
|
error: anyhow::Error,
|
||||||
},
|
},
|
||||||
|
@ -135,8 +132,7 @@ impl KeymapFile {
|
||||||
pub fn load_asset(asset_path: &str, cx: &AppContext) -> anyhow::Result<Vec<KeyBinding>> {
|
pub fn load_asset(asset_path: &str, cx: &AppContext) -> anyhow::Result<Vec<KeyBinding>> {
|
||||||
match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
|
match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
|
||||||
KeymapFileLoadResult::Success { key_bindings, .. } => Ok(key_bindings),
|
KeymapFileLoadResult::Success { key_bindings, .. } => Ok(key_bindings),
|
||||||
KeymapFileLoadResult::SomeFailedToLoad { error_message, .. }
|
KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => Err(anyhow!(
|
||||||
| KeymapFileLoadResult::AllFailedToLoad { error_message } => Err(anyhow!(
|
|
||||||
"Error loading built-in keymap \"{asset_path}\": {error_message}"
|
"Error loading built-in keymap \"{asset_path}\": {error_message}"
|
||||||
)),
|
)),
|
||||||
KeymapFileLoadResult::JsonParseFailure { error } => Err(anyhow!(
|
KeymapFileLoadResult::JsonParseFailure { error } => Err(anyhow!(
|
||||||
|
@ -151,11 +147,14 @@ impl KeymapFile {
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> anyhow::Result<Vec<KeyBinding>> {
|
) -> anyhow::Result<Vec<KeyBinding>> {
|
||||||
match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
|
match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
|
||||||
KeymapFileLoadResult::Success { key_bindings, .. }
|
KeymapFileLoadResult::SomeFailedToLoad {
|
||||||
| KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings),
|
key_bindings,
|
||||||
KeymapFileLoadResult::AllFailedToLoad { error_message } => Err(anyhow!(
|
error_message,
|
||||||
|
} if key_bindings.is_empty() => Err(anyhow!(
|
||||||
"Error loading built-in keymap \"{asset_path}\": {error_message}"
|
"Error loading built-in keymap \"{asset_path}\": {error_message}"
|
||||||
)),
|
)),
|
||||||
|
KeymapFileLoadResult::Success { key_bindings, .. }
|
||||||
|
| KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings),
|
||||||
KeymapFileLoadResult::JsonParseFailure { error } => Err(anyhow!(
|
KeymapFileLoadResult::JsonParseFailure { error } => Err(anyhow!(
|
||||||
"JSON parse error in built-in keymap \"{asset_path}\": {error}"
|
"JSON parse error in built-in keymap \"{asset_path}\": {error}"
|
||||||
)),
|
)),
|
||||||
|
@ -166,8 +165,7 @@ impl KeymapFile {
|
||||||
pub fn load_panic_on_failure(content: &str, cx: &AppContext) -> Vec<KeyBinding> {
|
pub fn load_panic_on_failure(content: &str, cx: &AppContext) -> Vec<KeyBinding> {
|
||||||
match Self::load(content, cx) {
|
match Self::load(content, cx) {
|
||||||
KeymapFileLoadResult::Success { key_bindings } => key_bindings,
|
KeymapFileLoadResult::Success { key_bindings } => key_bindings,
|
||||||
KeymapFileLoadResult::SomeFailedToLoad { error_message, .. }
|
KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
|
||||||
| KeymapFileLoadResult::AllFailedToLoad { error_message, .. } => {
|
|
||||||
panic!("{error_message}");
|
panic!("{error_message}");
|
||||||
}
|
}
|
||||||
KeymapFileLoadResult::JsonParseFailure { error } => {
|
KeymapFileLoadResult::JsonParseFailure { error } => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
/// Markdown text.
|
/// Markdown text.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MarkdownString(pub String);
|
pub struct MarkdownString(pub String);
|
||||||
|
|
||||||
impl Display for MarkdownString {
|
impl Display for MarkdownString {
|
||||||
|
|
|
@ -22,10 +22,10 @@ use feature_flags::FeatureFlagAppExt;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use futures::{channel::mpsc, select_biased, StreamExt};
|
use futures::{channel::mpsc, select_biased, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, px, Action, AppContext, AsyncAppContext, Context, DismissEvent, Element,
|
actions, point, px, Action, AnyWindowHandle, AppContext, AsyncAppContext, Context,
|
||||||
FocusableView, KeyBinding, MenuItem, ParentElement, PathPromptOptions, PromptLevel, ReadGlobal,
|
DismissEvent, Element, FocusableView, KeyBinding, MenuItem, ParentElement, PathPromptOptions,
|
||||||
SharedString, Styled, Task, TitlebarOptions, View, ViewContext, VisualContext, WindowKind,
|
PromptLevel, ReadGlobal, SharedString, Styled, Task, TitlebarOptions, View, ViewContext,
|
||||||
WindowOptions,
|
VisualContext, WindowKind, WindowOptions,
|
||||||
};
|
};
|
||||||
pub use open_listener::*;
|
pub use open_listener::*;
|
||||||
use outline_panel::OutlinePanel;
|
use outline_panel::OutlinePanel;
|
||||||
|
@ -1017,6 +1017,16 @@ pub fn handle_keymap_file_changes(
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
// Need to notify about keymap load errors when new workspaces are created, so that initial
|
||||||
|
// keymap load errors are shown to the user.
|
||||||
|
let (new_workspace_window_tx, mut new_workspace_window_rx) = mpsc::unbounded();
|
||||||
|
cx.observe_new_views(move |_: &mut Workspace, cx| {
|
||||||
|
new_workspace_window_tx
|
||||||
|
.unbounded_send(cx.window_handle())
|
||||||
|
.unwrap();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
let mut current_mapping = settings::get_key_equivalents(cx.keyboard_layout());
|
let mut current_mapping = settings::get_key_equivalents(cx.keyboard_layout());
|
||||||
cx.on_keyboard_layout_change(move |cx| {
|
cx.on_keyboard_layout_change(move |cx| {
|
||||||
let next_mapping = settings::get_key_equivalents(cx.keyboard_layout());
|
let next_mapping = settings::get_key_equivalents(cx.keyboard_layout());
|
||||||
|
@ -1033,68 +1043,102 @@ pub fn handle_keymap_file_changes(
|
||||||
let notification_id = NotificationId::unique::<KeymapParseErrorNotification>();
|
let notification_id = NotificationId::unique::<KeymapParseErrorNotification>();
|
||||||
|
|
||||||
cx.spawn(move |cx| async move {
|
cx.spawn(move |cx| async move {
|
||||||
let mut user_key_bindings = Vec::new();
|
let mut user_keymap_content = String::new();
|
||||||
|
enum LastError {
|
||||||
|
None,
|
||||||
|
JsonError(anyhow::Error),
|
||||||
|
LoadError(MarkdownString),
|
||||||
|
}
|
||||||
|
let mut last_load_error = LastError::None;
|
||||||
loop {
|
loop {
|
||||||
select_biased! {
|
let new_workspace_window = select_biased! {
|
||||||
_ = base_keymap_rx.next() => {}
|
_ = base_keymap_rx.next() => None,
|
||||||
_ = keyboard_layout_rx.next() => {}
|
_ = keyboard_layout_rx.next() => None,
|
||||||
user_keymap_content = user_keymap_file_rx.next() => {
|
workspace = new_workspace_window_rx.next() => workspace,
|
||||||
if let Some(user_keymap_content) = user_keymap_content {
|
content = user_keymap_file_rx.next() => {
|
||||||
cx.update(|cx| {
|
if let Some(content) = content {
|
||||||
let load_result = KeymapFile::load(&user_keymap_content, cx);
|
user_keymap_content = content;
|
||||||
match load_result {
|
}
|
||||||
KeymapFileLoadResult::Success { key_bindings } => {
|
None
|
||||||
user_key_bindings = key_bindings;
|
}
|
||||||
dismiss_app_notification(¬ification_id, cx);
|
};
|
||||||
}
|
cx.update(|cx| {
|
||||||
KeymapFileLoadResult::SomeFailedToLoad {
|
// No need to reload keymaps when a new workspace is added, just need to send the notification to it.
|
||||||
key_bindings,
|
if new_workspace_window.is_none() {
|
||||||
error_message
|
let load_result = KeymapFile::load(&user_keymap_content, cx);
|
||||||
} => {
|
match load_result {
|
||||||
user_key_bindings = key_bindings;
|
KeymapFileLoadResult::Success { key_bindings } => {
|
||||||
show_keymap_file_load_error(notification_id.clone(), error_message, cx);
|
reload_keymaps(cx, key_bindings);
|
||||||
}
|
dismiss_app_notification(¬ification_id, cx);
|
||||||
KeymapFileLoadResult::AllFailedToLoad {
|
last_load_error = LastError::None;
|
||||||
error_message
|
}
|
||||||
} => {
|
KeymapFileLoadResult::SomeFailedToLoad {
|
||||||
show_keymap_file_load_error(notification_id.clone(), error_message, cx);
|
key_bindings,
|
||||||
}
|
error_message,
|
||||||
KeymapFileLoadResult::JsonParseFailure { error } => {
|
} => {
|
||||||
show_keymap_file_json_error(notification_id.clone(), error, cx);
|
if !key_bindings.is_empty() {
|
||||||
}
|
reload_keymaps(cx, key_bindings);
|
||||||
};
|
}
|
||||||
}).ok();
|
last_load_error = LastError::LoadError(error_message);
|
||||||
|
}
|
||||||
|
KeymapFileLoadResult::JsonParseFailure { error } => {
|
||||||
|
last_load_error = LastError::JsonError(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
match &last_load_error {
|
||||||
cx.update(|cx| reload_keymaps(cx, user_key_bindings.clone()))
|
LastError::None => {}
|
||||||
.ok();
|
LastError::JsonError(err) => {
|
||||||
|
show_keymap_file_json_error(
|
||||||
|
new_workspace_window,
|
||||||
|
notification_id.clone(),
|
||||||
|
err,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
LastError::LoadError(message) => {
|
||||||
|
show_keymap_file_load_error(
|
||||||
|
new_workspace_window,
|
||||||
|
notification_id.clone(),
|
||||||
|
message.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_keymap_file_json_error(
|
fn show_keymap_file_json_error(
|
||||||
|
new_workspace_window: Option<AnyWindowHandle>,
|
||||||
notification_id: NotificationId,
|
notification_id: NotificationId,
|
||||||
error: anyhow::Error,
|
error: &anyhow::Error,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
let message: SharedString =
|
let message: SharedString =
|
||||||
format!("JSON parse error in keymap file. Bindings not reloaded.\n\n{error}").into();
|
format!("JSON parse error in keymap file. Bindings not reloaded.\n\n{error}").into();
|
||||||
show_app_notification(notification_id, cx, move |cx| {
|
show_notification_to_specific_workspace_or_all_workspaces(
|
||||||
cx.new_view(|_cx| {
|
new_workspace_window,
|
||||||
MessageNotification::new(message.clone())
|
notification_id,
|
||||||
.with_click_message("Open keymap file")
|
cx,
|
||||||
.on_click(|cx| {
|
move |cx| {
|
||||||
cx.dispatch_action(zed_actions::OpenKeymap.boxed_clone());
|
cx.new_view(|_cx| {
|
||||||
cx.emit(DismissEvent);
|
MessageNotification::new(message.clone())
|
||||||
})
|
.with_click_message("Open keymap file")
|
||||||
})
|
.on_click(|cx| {
|
||||||
})
|
cx.dispatch_action(zed_actions::OpenKeymap.boxed_clone());
|
||||||
.log_err();
|
cx.emit(DismissEvent);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_keymap_file_load_error(
|
fn show_keymap_file_load_error(
|
||||||
|
new_workspace_window: Option<AnyWindowHandle>,
|
||||||
notification_id: NotificationId,
|
notification_id: NotificationId,
|
||||||
markdown_error_message: MarkdownString,
|
markdown_error_message: MarkdownString,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
|
@ -1113,34 +1157,57 @@ fn show_keymap_file_load_error(
|
||||||
cx.spawn(move |cx| async move {
|
cx.spawn(move |cx| async move {
|
||||||
let parsed_markdown = Rc::new(parsed_markdown.await);
|
let parsed_markdown = Rc::new(parsed_markdown.await);
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
show_app_notification(notification_id, cx, move |cx| {
|
show_notification_to_specific_workspace_or_all_workspaces(
|
||||||
let workspace_handle = cx.view().downgrade();
|
new_workspace_window,
|
||||||
let parsed_markdown = parsed_markdown.clone();
|
notification_id,
|
||||||
cx.new_view(move |_cx| {
|
cx,
|
||||||
MessageNotification::new_from_builder(move |cx| {
|
move |cx| {
|
||||||
gpui::div()
|
let workspace_handle = cx.view().downgrade();
|
||||||
.text_xs()
|
let parsed_markdown = parsed_markdown.clone();
|
||||||
.child(markdown_preview::markdown_renderer::render_parsed_markdown(
|
cx.new_view(move |_cx| {
|
||||||
&parsed_markdown.clone(),
|
MessageNotification::new_from_builder(move |cx| {
|
||||||
Some(workspace_handle.clone()),
|
gpui::div()
|
||||||
cx,
|
.text_xs()
|
||||||
))
|
.child(markdown_preview::markdown_renderer::render_parsed_markdown(
|
||||||
.into_any()
|
&parsed_markdown.clone(),
|
||||||
|
Some(workspace_handle.clone()),
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.into_any()
|
||||||
|
})
|
||||||
|
.with_click_message("Open keymap file")
|
||||||
|
.on_click(|cx| {
|
||||||
|
cx.dispatch_action(zed_actions::OpenKeymap.boxed_clone());
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.with_click_message("Open keymap file")
|
},
|
||||||
.on_click(|cx| {
|
)
|
||||||
cx.dispatch_action(zed_actions::OpenKeymap.boxed_clone());
|
|
||||||
cx.emit(DismissEvent);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
})
|
})
|
||||||
.log_err();
|
.ok();
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_notification_to_specific_workspace_or_all_workspaces<V>(
|
||||||
|
new_workspace_window: Option<AnyWindowHandle>,
|
||||||
|
notification_id: NotificationId,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
build_notification: impl Fn(&mut ViewContext<Workspace>) -> View<V>,
|
||||||
|
) where
|
||||||
|
V: workspace::notifications::Notification,
|
||||||
|
{
|
||||||
|
if let Some(workspace_window) = new_workspace_window.and_then(|w| w.downcast::<Workspace>()) {
|
||||||
|
workspace_window
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
workspace.show_notification(notification_id, cx, build_notification);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
} else {
|
||||||
|
show_app_notification(notification_id, cx, build_notification).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn reload_keymaps(cx: &mut AppContext, user_key_bindings: Vec<KeyBinding>) {
|
fn reload_keymaps(cx: &mut AppContext, user_key_bindings: Vec<KeyBinding>) {
|
||||||
cx.clear_key_bindings();
|
cx.clear_key_bindings();
|
||||||
load_default_keymap(cx);
|
load_default_keymap(cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue