feat: redesign status chrome and floating prompts
This commit is contained in:
+289
-17
@@ -1351,11 +1351,14 @@ pub struct Editor {
|
||||
owned_system_clipboard: Option<(String, Vec<String>, bool)>,
|
||||
last_edit: Option<EditRecipe>,
|
||||
message: String,
|
||||
notification_visible: bool,
|
||||
top_bar: ChromeLine,
|
||||
bottom_bar: ChromeLine,
|
||||
labels: BTreeMap<String, String>,
|
||||
battery_charge: Option<u8>,
|
||||
battery_label: String,
|
||||
lsp_names: Vec<String>,
|
||||
lsp_label: String,
|
||||
quit: bool,
|
||||
capabilities: Vec<String>,
|
||||
features: HashMap<String, Feature>,
|
||||
@@ -1441,11 +1444,14 @@ impl Editor {
|
||||
owned_system_clipboard: None,
|
||||
last_edit: None,
|
||||
message: String::new(),
|
||||
notification_visible: true,
|
||||
top_bar: ChromeLine::top(),
|
||||
bottom_bar: ChromeLine::bottom(),
|
||||
labels: BTreeMap::new(),
|
||||
battery_charge: None,
|
||||
battery_label: String::new(),
|
||||
lsp_names: Vec::new(),
|
||||
lsp_label: String::new(),
|
||||
quit: false,
|
||||
capabilities,
|
||||
features: HashMap::new(),
|
||||
@@ -1773,6 +1779,7 @@ impl Editor {
|
||||
self.line_pending.take(),
|
||||
self.seek_pending.take(),
|
||||
std::mem::take(&mut self.message),
|
||||
std::mem::replace(&mut self.notification_visible, true),
|
||||
)
|
||||
});
|
||||
self.select_tab(target_tab);
|
||||
@@ -1794,6 +1801,7 @@ impl Editor {
|
||||
self.line_pending,
|
||||
self.seek_pending,
|
||||
self.message,
|
||||
self.notification_visible,
|
||||
) = interaction;
|
||||
}
|
||||
match result {
|
||||
@@ -3199,7 +3207,6 @@ impl Editor {
|
||||
popup: Completion { items, selected: 1 },
|
||||
original,
|
||||
});
|
||||
self.message.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3835,6 +3842,9 @@ impl Editor {
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
pub fn notification_visible(&self) -> bool {
|
||||
self.notification_visible
|
||||
}
|
||||
pub fn top_bar_config(&self) -> &ChromeLine {
|
||||
&self.top_bar
|
||||
}
|
||||
@@ -3847,6 +3857,12 @@ impl Editor {
|
||||
pub fn battery_label(&self) -> &str {
|
||||
&self.battery_label
|
||||
}
|
||||
pub fn lsp_names(&self) -> &[String] {
|
||||
&self.lsp_names
|
||||
}
|
||||
pub fn lsp_label(&self) -> &str {
|
||||
&self.lsp_label
|
||||
}
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
self.active.path.as_deref()
|
||||
}
|
||||
@@ -3884,6 +3900,7 @@ impl Editor {
|
||||
} else {
|
||||
message
|
||||
};
|
||||
self.notification_visible = true;
|
||||
}
|
||||
|
||||
pub fn set_chrome_config(
|
||||
@@ -3928,6 +3945,20 @@ impl Editor {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) fn set_lsp_names(&mut self, mut names: Vec<String>) -> bool {
|
||||
for name in &mut names {
|
||||
if name.chars().any(char::is_control) {
|
||||
*name = sanitize_label(std::mem::take(name));
|
||||
}
|
||||
}
|
||||
if self.lsp_names == names {
|
||||
return false;
|
||||
}
|
||||
self.lsp_label = names.join(" + ");
|
||||
self.lsp_names = names;
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn set_battery_charge(&mut self, charge: Option<u8>) -> bool {
|
||||
let charge = charge.filter(|charge| *charge <= 100);
|
||||
if self.battery_charge == charge {
|
||||
@@ -4775,6 +4806,14 @@ impl Editor {
|
||||
if input == Input::Escape && self.count.take().is_some() {
|
||||
return None;
|
||||
}
|
||||
if input == Input::Escape
|
||||
&& self.active.view.mode == Mode::Normal
|
||||
&& self.notification_visible
|
||||
&& (!self.message.is_empty() || self.diagnostic_at_cursor().is_some())
|
||||
{
|
||||
self.notification_visible = false;
|
||||
return None;
|
||||
}
|
||||
if matches!(self.active.view.mode, Mode::Normal | Mode::Visual)
|
||||
&& let Input::Char(ch @ '0'..='9') = input
|
||||
{
|
||||
@@ -5124,7 +5163,6 @@ impl Editor {
|
||||
self.active.view.linewise = linewise;
|
||||
self.active.view.mode = Mode::Visual;
|
||||
self.normalize_targets();
|
||||
self.message.clear();
|
||||
}
|
||||
}
|
||||
Action::SelectLines => self.split_rows(false, false),
|
||||
@@ -5255,7 +5293,6 @@ impl Editor {
|
||||
}
|
||||
self.active.view.visual_motions = None;
|
||||
self.normalize_targets();
|
||||
self.message.clear();
|
||||
self.record_jump(source);
|
||||
}
|
||||
|
||||
@@ -5360,21 +5397,12 @@ impl Editor {
|
||||
self.parked.insert(previous.id, previous);
|
||||
self.load_view();
|
||||
self.reveal_caret();
|
||||
self.show_buffer();
|
||||
}
|
||||
|
||||
fn show_buffer(&mut self) {
|
||||
let path = self
|
||||
.path()
|
||||
.map_or_else(|| Cow::Borrowed("[No Name]"), Path::to_string_lossy);
|
||||
self.set_message(format!("Buffer {}: {path}", self.active.id));
|
||||
}
|
||||
|
||||
fn switch_buffer(&mut self, id: usize) {
|
||||
if id == self.active.id {
|
||||
self.reveal_caret();
|
||||
self.park_source();
|
||||
self.show_buffer();
|
||||
} else if let Some(buffer) = self.parked.remove(&id) {
|
||||
self.activate(buffer);
|
||||
} else {
|
||||
@@ -6424,7 +6452,6 @@ impl Editor {
|
||||
}
|
||||
self.active.view.visual_motions = None;
|
||||
self.normalize_targets();
|
||||
self.message.clear();
|
||||
}
|
||||
|
||||
fn line_selections(&mut self, count: usize) -> Vec<Selection> {
|
||||
@@ -6489,7 +6516,6 @@ impl Editor {
|
||||
},
|
||||
);
|
||||
}
|
||||
self.message.clear();
|
||||
self.reveal_caret();
|
||||
}
|
||||
|
||||
@@ -6518,7 +6544,6 @@ impl Editor {
|
||||
if changing {
|
||||
self.start_insert(before.unwrap(), recipe.unwrap().start);
|
||||
self.edit_ranges(action, &ranges);
|
||||
self.message.clear();
|
||||
return;
|
||||
}
|
||||
self.edit_ranges(action, &ranges);
|
||||
@@ -6770,7 +6795,6 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
self.record(before);
|
||||
self.message.clear();
|
||||
}
|
||||
if !system {
|
||||
self.clipboard = fragments;
|
||||
@@ -7079,7 +7103,6 @@ impl Editor {
|
||||
}
|
||||
self.active.view.visual_motions = None;
|
||||
self.record(before);
|
||||
self.message.clear();
|
||||
}
|
||||
Err(error) => {
|
||||
if !restored {
|
||||
@@ -8612,6 +8635,206 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notification_escape_preserves_carets_then_ordinary_escape_collapses_them() {
|
||||
let mut editor = editor();
|
||||
editor
|
||||
.apply(vec![Effect::Replace("abcd\nabcd".into())])
|
||||
.unwrap();
|
||||
set_carets(&mut editor, &[(0, 1), (1, 1)], 1);
|
||||
assert!(editor.notification_visible());
|
||||
editor.set_message("Written");
|
||||
editor.handle(Input::Right);
|
||||
let targets = editor.active.view.targets.clone();
|
||||
let revision = editor.text_revision();
|
||||
let history = (editor.active.undo.len(), editor.active.redo.len());
|
||||
editor.handle(Input::Escape);
|
||||
assert!(!editor.notification_visible());
|
||||
assert_eq!(editor.message(), "Written");
|
||||
assert_eq!(editor.active.view.targets, targets);
|
||||
assert_eq!(editor.active.view.primary, 1);
|
||||
assert_eq!(editor.text_revision(), revision);
|
||||
assert_eq!(
|
||||
(editor.active.undo.len(), editor.active.redo.len()),
|
||||
history
|
||||
);
|
||||
editor.handle(Input::Escape);
|
||||
assert_eq!(editor.selection_count(), 1);
|
||||
editor.set_message("Written");
|
||||
assert!(editor.notification_visible());
|
||||
type_text(&mut editor, "iX");
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.notification_visible());
|
||||
assert_eq!(editor.message(), "Written");
|
||||
editor.handle(Input::Char('v'));
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.notification_visible());
|
||||
for prompt in [':', '/'] {
|
||||
editor.handle(Input::Char(prompt));
|
||||
editor.handle(Input::Char('x'));
|
||||
editor.handle(Input::Escape);
|
||||
assert_eq!(editor.mode(), Mode::Normal);
|
||||
assert!(editor.notification_visible());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notification_escape_waits_for_prefixes_and_focused_overlays() {
|
||||
let mut editor = editor();
|
||||
editor
|
||||
.apply(vec![Effect::Replace("abcd\nabcd".into())])
|
||||
.unwrap();
|
||||
set_carets(&mut editor, &[(0, 1), (1, 1)], 1);
|
||||
editor.set_message("Written");
|
||||
let targets = editor.active.view.targets.clone();
|
||||
for prefix in ["f", "3g", "2d", "4", " b"] {
|
||||
type_text(&mut editor, prefix);
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.notification_visible(), "{prefix}");
|
||||
assert_eq!(editor.active.view.targets, targets, "{prefix}");
|
||||
assert_eq!(editor.count(), None);
|
||||
}
|
||||
editor.show_startup_diagnostic("startup".into());
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.startup_diagnostic().is_none());
|
||||
assert!(editor.notification_visible());
|
||||
editor.show_hover("hover".into()).unwrap();
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.hover().is_none());
|
||||
assert!(editor.notification_visible());
|
||||
editor.handle(Input::Ctrl('p'));
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.palette().is_none());
|
||||
assert!(editor.notification_visible());
|
||||
editor.open_file_picker(FilePresentation::Docked);
|
||||
editor.handle(Input::Char('i'));
|
||||
assert!(editor.file_picker().unwrap().searching);
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.file_picker().unwrap().focused);
|
||||
assert!(editor.notification_visible());
|
||||
editor.handle(Input::Escape);
|
||||
assert!(!editor.file_picker().unwrap().focused);
|
||||
assert!(editor.notification_visible());
|
||||
assert_eq!(editor.active.view.targets, targets);
|
||||
editor.handle(Input::Escape);
|
||||
assert!(!editor.notification_visible());
|
||||
assert_eq!(editor.active.view.targets, targets);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_notification_does_not_swallow_escape_but_cursor_diagnostic_does() {
|
||||
let mut editor = editor();
|
||||
editor
|
||||
.apply(vec![Effect::Replace("abcd\nabcd".into())])
|
||||
.unwrap();
|
||||
set_carets(&mut editor, &[(0, 1), (1, 1)], 1);
|
||||
editor.handle(Input::Escape);
|
||||
assert_eq!(editor.selection_count(), 1);
|
||||
assert!(editor.notification_visible());
|
||||
editor
|
||||
.replace_diagnostics(
|
||||
"fixture",
|
||||
editor.buffer_id(),
|
||||
editor.text_revision(),
|
||||
vec![annotation((1, 0), (1, 3), "diagnostic")],
|
||||
)
|
||||
.unwrap();
|
||||
set_carets(&mut editor, &[(0, 1), (1, 1)], 1);
|
||||
editor.handle(Input::Escape);
|
||||
assert!(!editor.notification_visible());
|
||||
assert_eq!(editor.selection_count(), 2);
|
||||
assert_eq!(editor.diagnostic_at_cursor().unwrap().message, "diagnostic");
|
||||
editor.handle(Input::Escape);
|
||||
assert_eq!(editor.selection_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hidden_pane_interactions_cannot_reopen_or_dismiss_focused_notifications() {
|
||||
let mut editor = editor();
|
||||
let hidden = editor.pane_id();
|
||||
editor.execute("pane.split_vertical");
|
||||
editor.set_message("focused");
|
||||
editor.handle(Input::Escape);
|
||||
assert!(!editor.notification_visible());
|
||||
editor.with_pane(hidden, |pane| pane.set_message("background"));
|
||||
assert_eq!(editor.message(), "focused");
|
||||
assert!(!editor.notification_visible());
|
||||
editor.with_render_pane(hidden, |pane| {
|
||||
assert!(pane.message().is_empty());
|
||||
});
|
||||
assert!(!editor.notification_visible());
|
||||
editor.set_message("focused");
|
||||
editor.with_pane(hidden, |pane| {
|
||||
pane.set_message("background");
|
||||
pane.handle(Input::Escape);
|
||||
});
|
||||
assert!(editor.notification_visible());
|
||||
assert_eq!(editor.message(), "focused");
|
||||
editor.execute("tab.new");
|
||||
editor.set_message("new tab");
|
||||
editor.handle(Input::Escape);
|
||||
editor.with_pane(hidden, |pane| pane.set_message("hidden tab"));
|
||||
assert_eq!(editor.message(), "new tab");
|
||||
assert!(!editor.notification_visible());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buffer_activation_preserves_last_operation_and_dismissal() {
|
||||
let directory = Directory::new();
|
||||
let path = directory.path("opened.txt");
|
||||
fs::write(&path, "text").unwrap();
|
||||
let mut editor = editor();
|
||||
editor.set_message("Written");
|
||||
editor.execute(&format!("edit {}", path.display()));
|
||||
assert_eq!(editor.message(), "Written");
|
||||
editor.handle(Input::Escape);
|
||||
for command in ["buffer 1", "bnext", "bprev", "buffer 1"] {
|
||||
editor.execute(command);
|
||||
assert_eq!(editor.message(), "Written");
|
||||
assert!(!editor.notification_visible());
|
||||
}
|
||||
editor.execute("buffer 999");
|
||||
assert!(editor.message().contains("No buffer"));
|
||||
assert!(editor.notification_visible());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_lsp_cache_is_independent_presentation_metadata() {
|
||||
let mut editor = editor();
|
||||
editor
|
||||
.apply(vec![Effect::Replace("document".into())])
|
||||
.unwrap();
|
||||
type_text(&mut editor, "vl");
|
||||
editor.set_message("Written");
|
||||
editor.notification_visible = false;
|
||||
editor
|
||||
.set_label("lsp".into(), "configured label".into())
|
||||
.unwrap();
|
||||
let targets = editor.active.view.targets.clone();
|
||||
let revision = editor.text_revision();
|
||||
let history = (editor.active.undo.len(), editor.active.redo.len());
|
||||
assert!(editor.lsp_names().is_empty());
|
||||
assert_eq!(editor.lsp_label(), "");
|
||||
assert!(editor.set_lsp_names(vec!["rust-analyzer".into(), "界 server".into()]));
|
||||
assert_eq!(editor.lsp_names(), ["rust-analyzer", "界 server"]);
|
||||
assert_eq!(editor.lsp_label(), "rust-analyzer + 界 server");
|
||||
assert!(!editor.set_lsp_names(vec!["rust-analyzer".into(), "界 server".into()]));
|
||||
assert!(editor.set_lsp_names(vec!["unsafe\nname".into()]));
|
||||
assert_eq!(editor.lsp_label(), "unsafe�name");
|
||||
assert!(editor.set_lsp_names(Vec::new()));
|
||||
assert_eq!(editor.lsp_label(), "");
|
||||
assert_eq!(editor.label("lsp"), "configured label");
|
||||
assert_eq!(editor.message(), "Written");
|
||||
assert!(!editor.notification_visible());
|
||||
assert_eq!(editor.mode(), Mode::Visual);
|
||||
assert_eq!(editor.active.view.targets, targets);
|
||||
assert_eq!(editor.text_revision(), revision);
|
||||
assert_eq!(
|
||||
(editor.active.undo.len(), editor.active.redo.len()),
|
||||
history
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pane_focus_remaps_do_not_steal_printable_picker_filter_input() {
|
||||
let directory = Directory::new();
|
||||
@@ -9571,6 +9794,38 @@ mod tests {
|
||||
assert!(editor.diff_interacting());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_documents_dismiss_notifications_but_files_and_selectors_keep_escape() {
|
||||
let directory = Directory::new();
|
||||
let mut editor = editor();
|
||||
historical_diff(
|
||||
&mut editor,
|
||||
&directory.0,
|
||||
vec!["before".into(), "second".into()],
|
||||
vec!["after".into(), "second".into()],
|
||||
);
|
||||
set_carets(&mut editor, &[(0, 1), (1, 1)], 1);
|
||||
editor.set_message("operation");
|
||||
editor.handle(Input::Escape);
|
||||
assert!(!editor.notification_visible());
|
||||
assert_eq!(editor.selection_count(), 2);
|
||||
assert!(editor.diff_interacting());
|
||||
editor.handle(Input::Escape);
|
||||
assert_eq!(editor.selection_count(), 1);
|
||||
editor.set_message("operation");
|
||||
editor.diff.as_mut().unwrap().focus = Focus::Files;
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.notification_visible());
|
||||
assert_ne!(editor.diff().unwrap().focus, Focus::Files);
|
||||
editor.execute("diff.close");
|
||||
editor.execute("diff");
|
||||
editor.handle(Input::Char('q'));
|
||||
assert_eq!(editor.diff().unwrap().query, "q");
|
||||
editor.handle(Input::Escape);
|
||||
assert_eq!(editor.diff().unwrap().query, "");
|
||||
assert!(editor.notification_visible());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn readonly_native_panes_allow_selection_yank_search_and_multicarets() {
|
||||
for focus in [Focus::Before, Focus::After] {
|
||||
@@ -11451,6 +11706,7 @@ mod tests {
|
||||
);
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.hover().is_none());
|
||||
assert!(editor.notification_visible());
|
||||
assert_eq!(editor.active.view.targets, targets);
|
||||
assert_eq!(editor.active.view.primary, primary);
|
||||
assert_eq!(editor.context().text, text);
|
||||
@@ -11461,6 +11717,9 @@ mod tests {
|
||||
);
|
||||
assert_eq!(editor.mode(), Mode::Normal);
|
||||
editor.handle(Input::Escape);
|
||||
assert!(!editor.notification_visible());
|
||||
assert_eq!(editor.active.view.targets, targets);
|
||||
editor.handle(Input::Escape);
|
||||
assert_eq!(editor.selection_count(), 1);
|
||||
editor.show_hover("documentation".into()).unwrap();
|
||||
editor.handle(Input::Char('i'));
|
||||
@@ -14211,6 +14470,11 @@ mod tests {
|
||||
assert_eq!(editor.count(), None);
|
||||
assert_eq!(editor.sequence_pending(), None);
|
||||
editor.handle(Input::Escape);
|
||||
if !visual {
|
||||
assert!(!editor.notification_visible());
|
||||
assert_eq!(editor.selection_count(), 2);
|
||||
editor.handle(Input::Escape);
|
||||
}
|
||||
assert_eq!(editor.mode(), Mode::Normal);
|
||||
assert_eq!(editor.selection_count(), if visual { 2 } else { 1 });
|
||||
}
|
||||
@@ -16901,6 +17165,10 @@ mod tests {
|
||||
assert_eq!(editor.context().text, "ab\nxyzq");
|
||||
editor.handle(Input::Char('u'));
|
||||
editor.handle(Input::Escape);
|
||||
assert!(!editor.notification_visible());
|
||||
assert_eq!(editor.selection_count(), 2);
|
||||
editor.handle(Input::Escape);
|
||||
assert_eq!(editor.selection_count(), 1);
|
||||
let before = editor.snapshot();
|
||||
let undo = editor.active.undo.len();
|
||||
let redo = editor.active.redo.len();
|
||||
@@ -17979,6 +18247,8 @@ mod tests {
|
||||
fn plugin_effects_validate_before_any_mutation_and_group_undo() {
|
||||
let mut editor = editor();
|
||||
editor.set_message("unchanged");
|
||||
editor.handle(Input::Escape);
|
||||
assert!(!editor.notification_visible());
|
||||
editor
|
||||
.set_label("battery".into(), "unchanged".into())
|
||||
.unwrap();
|
||||
@@ -17996,6 +18266,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(editor.context().text, "");
|
||||
assert_eq!(editor.message(), "unchanged");
|
||||
assert!(!editor.notification_visible());
|
||||
assert_eq!(editor.label("battery"), "unchanged");
|
||||
assert!(!editor.dirty());
|
||||
assert!(
|
||||
@@ -18024,6 +18295,7 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(editor.context().text, "one\ntwo");
|
||||
assert_eq!(editor.message(), "ok");
|
||||
assert!(editor.notification_visible());
|
||||
editor.handle(Input::Char('u'));
|
||||
assert_eq!(editor.context().text, "");
|
||||
assert_eq!(editor.label("battery"), "updated");
|
||||
|
||||
Reference in New Issue
Block a user