(&mut self, cx: &mut WindowContext) {
if let Some(State::Dragging {
- payload, window_id, ..
+ payload, window, ..
}) = &self.currently_dragged
{
if payload.is::() {
- let window_id = *window_id;
+ let window = *window;
self.currently_dragged = Some(State::Canceled);
- self.notify_containers_for_window(window_id, cx);
+ self.notify_containers_for_window(window, cx);
}
}
}
fn finish_dragging(&mut self, cx: &mut WindowContext) {
- if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
- self.notify_containers_for_window(window_id, cx);
+ if let Some(State::Dragging { window, .. }) = self.currently_dragged.take() {
+ self.notify_containers_for_window(window, cx);
}
}
- fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut WindowContext) {
+ fn notify_containers_for_window(&mut self, window: AnyWindowHandle, cx: &mut WindowContext) {
self.containers.retain(|container| {
if let Some(container) = container.upgrade(cx) {
- if container.window_id() == window_id {
+ if container.window() == window {
container.update(cx, |_, cx| cx.notify());
}
true
diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml
index bc1c904404..2fdeee56c1 100644
--- a/crates/editor/Cargo.toml
+++ b/crates/editor/Cargo.toml
@@ -47,6 +47,7 @@ workspace = { path = "../workspace" }
aho-corasick = "0.7"
anyhow.workspace = true
+convert_case = "0.6.0"
futures.workspace = true
indoc = "1.0.4"
itertools = "0.10"
@@ -56,12 +57,12 @@ ordered-float.workspace = true
parking_lot.workspace = true
postage.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
+rand.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
smol.workspace = true
-rand.workspace = true
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-html = { workspace = true, optional = true }
diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs
index 6a59cecae8..9794ac45c1 100644
--- a/crates/editor/src/display_map/inlay_map.rs
+++ b/crates/editor/src/display_map/inlay_map.rs
@@ -397,7 +397,7 @@ impl InlayMap {
buffer_snapshot: MultiBufferSnapshot,
mut buffer_edits: Vec>,
) -> (InlaySnapshot, Vec) {
- let mut snapshot = &mut self.snapshot;
+ let snapshot = &mut self.snapshot;
if buffer_edits.is_empty() {
if snapshot.buffer.trailing_excerpt_update_count()
@@ -572,7 +572,6 @@ impl InlayMap {
})
.collect();
let buffer_snapshot = snapshot.buffer.clone();
- drop(snapshot);
let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
(snapshot, edits)
}
@@ -635,7 +634,6 @@ impl InlayMap {
}
log::info!("removing inlays: {:?}", to_remove);
- drop(snapshot);
let (snapshot, edits) = self.splice(to_remove, to_insert);
(snapshot, edits)
}
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index e05837740d..02cd58524b 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -28,6 +28,7 @@ use blink_manager::BlinkManager;
use client::{ClickhouseEvent, TelemetrySettings};
use clock::{Global, ReplicaId};
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
+use convert_case::{Case, Casing};
use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
@@ -89,7 +90,7 @@ use std::{
cmp::{self, Ordering, Reverse},
mem,
num::NonZeroU32,
- ops::{ControlFlow, Deref, DerefMut, Range},
+ ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
path::Path,
sync::Arc,
time::{Duration, Instant},
@@ -231,6 +232,13 @@ actions!(
SortLinesCaseInsensitive,
ReverseLines,
ShuffleLines,
+ ConvertToUpperCase,
+ ConvertToLowerCase,
+ ConvertToTitleCase,
+ ConvertToSnakeCase,
+ ConvertToKebabCase,
+ ConvertToUpperCamelCase,
+ ConvertToLowerCamelCase,
Transpose,
Cut,
Copy,
@@ -353,6 +361,13 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::sort_lines_case_insensitive);
cx.add_action(Editor::reverse_lines);
cx.add_action(Editor::shuffle_lines);
+ cx.add_action(Editor::convert_to_upper_case);
+ cx.add_action(Editor::convert_to_lower_case);
+ cx.add_action(Editor::convert_to_title_case);
+ cx.add_action(Editor::convert_to_snake_case);
+ cx.add_action(Editor::convert_to_kebab_case);
+ cx.add_action(Editor::convert_to_upper_camel_case);
+ cx.add_action(Editor::convert_to_lower_camel_case);
cx.add_action(Editor::delete_to_previous_word_start);
cx.add_action(Editor::delete_to_previous_subword_start);
cx.add_action(Editor::delete_to_next_word_end);
@@ -543,6 +558,7 @@ pub struct Editor {
show_local_selections: bool,
mode: EditorMode,
show_gutter: bool,
+ show_wrap_guides: Option,
placeholder_text: Option>,
highlighted_rows: Option>,
#[allow(clippy::type_complexity)]
@@ -1375,6 +1391,7 @@ impl Editor {
show_local_selections: true,
mode,
show_gutter: mode == EditorMode::Full,
+ show_wrap_guides: None,
placeholder_text: None,
highlighted_rows: None,
background_highlights: Default::default(),
@@ -1537,7 +1554,7 @@ impl Editor {
self.collapse_matches = collapse_matches;
}
- fn range_for_match(&self, range: &Range) -> Range {
+ pub fn range_for_match(&self, range: &Range) -> Range {
if self.collapse_matches {
return range.start..range.start;
}
@@ -4219,7 +4236,7 @@ impl Editor {
_: &SortLinesCaseSensitive,
cx: &mut ViewContext,
) {
- self.manipulate_lines(cx, |text| text.sort())
+ self.manipulate_lines(cx, |lines| lines.sort())
}
pub fn sort_lines_case_insensitive(
@@ -4227,7 +4244,7 @@ impl Editor {
_: &SortLinesCaseInsensitive,
cx: &mut ViewContext,
) {
- self.manipulate_lines(cx, |text| text.sort_by_key(|line| line.to_lowercase()))
+ self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
}
pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext) {
@@ -4265,19 +4282,19 @@ impl Editor {
let text = buffer
.text_for_range(start_point..end_point)
.collect::();
- let mut text = text.split("\n").collect_vec();
+ let mut lines = text.split("\n").collect_vec();
- let text_len = text.len();
- callback(&mut text);
+ let lines_len = lines.len();
+ callback(&mut lines);
// This is a current limitation with selections.
// If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections.
debug_assert!(
- text.len() == text_len,
+ lines.len() == lines_len,
"callback should not change the number of lines"
);
- edits.push((start_point..end_point, text.join("\n")));
+ edits.push((start_point..end_point, lines.join("\n")));
let start_anchor = buffer.anchor_after(start_point);
let end_anchor = buffer.anchor_before(end_point);
@@ -4304,6 +4321,97 @@ impl Editor {
});
}
+ pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext) {
+ self.manipulate_text(cx, |text| text.to_uppercase())
+ }
+
+ pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext) {
+ self.manipulate_text(cx, |text| text.to_lowercase())
+ }
+
+ pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext) {
+ self.manipulate_text(cx, |text| text.to_case(Case::Title))
+ }
+
+ pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext) {
+ self.manipulate_text(cx, |text| text.to_case(Case::Snake))
+ }
+
+ pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext) {
+ self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
+ }
+
+ pub fn convert_to_upper_camel_case(
+ &mut self,
+ _: &ConvertToUpperCamelCase,
+ cx: &mut ViewContext,
+ ) {
+ self.manipulate_text(cx, |text| text.to_case(Case::UpperCamel))
+ }
+
+ pub fn convert_to_lower_camel_case(
+ &mut self,
+ _: &ConvertToLowerCamelCase,
+ cx: &mut ViewContext,
+ ) {
+ self.manipulate_text(cx, |text| text.to_case(Case::Camel))
+ }
+
+ fn manipulate_text(&mut self, cx: &mut ViewContext, mut callback: Fn)
+ where
+ Fn: FnMut(&str) -> String,
+ {
+ let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ let buffer = self.buffer.read(cx).snapshot(cx);
+
+ let mut new_selections = Vec::new();
+ let mut edits = Vec::new();
+ let mut selection_adjustment = 0i32;
+
+ for selection in self.selections.all::(cx) {
+ let selection_is_empty = selection.is_empty();
+
+ let (start, end) = if selection_is_empty {
+ let word_range = movement::surrounding_word(
+ &display_map,
+ selection.start.to_display_point(&display_map),
+ );
+ let start = word_range.start.to_offset(&display_map, Bias::Left);
+ let end = word_range.end.to_offset(&display_map, Bias::Left);
+ (start, end)
+ } else {
+ (selection.start, selection.end)
+ };
+
+ let text = buffer.text_for_range(start..end).collect::();
+ let old_length = text.len() as i32;
+ let text = callback(&text);
+
+ new_selections.push(Selection {
+ start: (start as i32 - selection_adjustment) as usize,
+ end: ((start + text.len()) as i32 - selection_adjustment) as usize,
+ goal: SelectionGoal::None,
+ ..selection
+ });
+
+ selection_adjustment += old_length - text.len() as i32;
+
+ edits.push((start..end, text));
+ }
+
+ self.transact(cx, |this, cx| {
+ this.buffer.update(cx, |buffer, cx| {
+ buffer.edit(edits, None, cx);
+ });
+
+ this.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.select(new_selections);
+ });
+
+ this.request_autoscroll(Autoscroll::fit(), cx);
+ });
+ }
+
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
@@ -6374,8 +6482,8 @@ impl Editor {
.range
.to_offset(definition.target.buffer.read(cx));
+ let range = self.range_for_match(&range);
if Some(&definition.target.buffer) == self.buffer.read(cx).as_singleton().as_ref() {
- let range = self.range_for_match(&range);
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([range]);
});
@@ -6392,7 +6500,6 @@ impl Editor {
// When selecting a definition in a different buffer, disable the nav history
// to avoid creating a history entry at the previous cursor location.
pane.update(cx, |pane, _| pane.disable_history());
- let range = target_editor.range_for_match(&range);
target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([range]);
});
@@ -7188,6 +7295,10 @@ impl Editor {
pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
let mut wrap_guides = smallvec::smallvec![];
+ if self.show_wrap_guides == Some(false) {
+ return wrap_guides;
+ }
+
let settings = self.buffer.read(cx).settings_at(0, cx);
if settings.show_wrap_guides {
if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
@@ -7245,6 +7356,11 @@ impl Editor {
cx.notify();
}
+ pub fn set_show_wrap_guides(&mut self, show_gutter: bool, cx: &mut ViewContext) {
+ self.show_wrap_guides = Some(show_gutter);
+ cx.notify();
+ }
+
pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext) {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
@@ -7433,6 +7549,78 @@ impl Editor {
results
}
+ pub fn background_highlight_row_ranges(
+ &self,
+ search_range: Range,
+ display_snapshot: &DisplaySnapshot,
+ count: usize,
+ ) -> Vec> {
+ let mut results = Vec::new();
+ let buffer = &display_snapshot.buffer_snapshot;
+ let Some((_, ranges)) = self.background_highlights
+ .get(&TypeId::of::()) else {
+ return vec![];
+ };
+
+ let start_ix = match ranges.binary_search_by(|probe| {
+ let cmp = probe.end.cmp(&search_range.start, buffer);
+ if cmp.is_gt() {
+ Ordering::Greater
+ } else {
+ Ordering::Less
+ }
+ }) {
+ Ok(i) | Err(i) => i,
+ };
+ let mut push_region = |start: Option, end: Option| {
+ if let (Some(start_display), Some(end_display)) = (start, end) {
+ results.push(
+ start_display.to_display_point(display_snapshot)
+ ..=end_display.to_display_point(display_snapshot),
+ );
+ }
+ };
+ let mut start_row: Option = None;
+ let mut end_row: Option = None;
+ if ranges.len() > count {
+ return vec![];
+ }
+ for range in &ranges[start_ix..] {
+ if range.start.cmp(&search_range.end, buffer).is_ge() {
+ break;
+ }
+ let end = range.end.to_point(buffer);
+ if let Some(current_row) = &end_row {
+ if end.row == current_row.row {
+ continue;
+ }
+ }
+ let start = range.start.to_point(buffer);
+
+ if start_row.is_none() {
+ assert_eq!(end_row, None);
+ start_row = Some(start);
+ end_row = Some(end);
+ continue;
+ }
+ if let Some(current_end) = end_row.as_mut() {
+ if start.row > current_end.row + 1 {
+ push_region(start_row, end_row);
+ start_row = Some(start);
+ end_row = Some(end);
+ } else {
+ // Merge two hunks.
+ *current_end = end;
+ }
+ } else {
+ unreachable!();
+ }
+ }
+ // We might still have a hunk that was not rendered (if there was a search hit on the last line)
+ push_region(start_row, end_row);
+ results
+ }
+
pub fn highlight_text(
&mut self,
ranges: Vec>,
diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs
index eb03d2bdc0..ec1cc12498 100644
--- a/crates/editor/src/editor_tests.rs
+++ b/crates/editor/src/editor_tests.rs
@@ -48,36 +48,40 @@ fn test_edit_events(cx: &mut TestAppContext) {
});
let events = Rc::new(RefCell::new(Vec::new()));
- let (_, editor1) = cx.add_window({
- let events = events.clone();
- |cx| {
- cx.subscribe(&cx.handle(), move |_, _, event, _| {
- if matches!(
- event,
- Event::Edited | Event::BufferEdited | Event::DirtyChanged
- ) {
- events.borrow_mut().push(("editor1", event.clone()));
- }
- })
- .detach();
- Editor::for_buffer(buffer.clone(), None, cx)
- }
- });
- let (_, editor2) = cx.add_window({
- let events = events.clone();
- |cx| {
- cx.subscribe(&cx.handle(), move |_, _, event, _| {
- if matches!(
- event,
- Event::Edited | Event::BufferEdited | Event::DirtyChanged
- ) {
- events.borrow_mut().push(("editor2", event.clone()));
- }
- })
- .detach();
- Editor::for_buffer(buffer.clone(), None, cx)
- }
- });
+ let editor1 = cx
+ .add_window({
+ let events = events.clone();
+ |cx| {
+ cx.subscribe(&cx.handle(), move |_, _, event, _| {
+ if matches!(
+ event,
+ Event::Edited | Event::BufferEdited | Event::DirtyChanged
+ ) {
+ events.borrow_mut().push(("editor1", event.clone()));
+ }
+ })
+ .detach();
+ Editor::for_buffer(buffer.clone(), None, cx)
+ }
+ })
+ .root(cx);
+ let editor2 = cx
+ .add_window({
+ let events = events.clone();
+ |cx| {
+ cx.subscribe(&cx.handle(), move |_, _, event, _| {
+ if matches!(
+ event,
+ Event::Edited | Event::BufferEdited | Event::DirtyChanged
+ ) {
+ events.borrow_mut().push(("editor2", event.clone()));
+ }
+ })
+ .detach();
+ Editor::for_buffer(buffer.clone(), None, cx)
+ }
+ })
+ .root(cx);
assert_eq!(mem::take(&mut *events.borrow_mut()), []);
// Mutating editor 1 will emit an `Edited` event only for that editor.
@@ -173,7 +177,9 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
+ let editor = cx
+ .add_window(|cx| build_editor(buffer.clone(), cx))
+ .root(cx);
editor.update(cx, |editor, cx| {
editor.start_transaction_at(now, cx);
@@ -343,10 +349,12 @@ fn test_ime_composition(cx: &mut TestAppContext) {
fn test_selection_with_mouse(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
- build_editor(buffer, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
editor.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
});
@@ -410,10 +418,12 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
fn test_canceling_pending_selection(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
@@ -456,10 +466,12 @@ fn test_clone(cx: &mut TestAppContext) {
true,
);
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&text, cx);
- build_editor(buffer, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&text, cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
@@ -473,9 +485,11 @@ fn test_clone(cx: &mut TestAppContext) {
);
});
- let (_, cloned_editor) = editor.update(cx, |editor, cx| {
- cx.add_window(Default::default(), |cx| editor.clone(cx))
- });
+ let cloned_editor = editor
+ .update(cx, |editor, cx| {
+ cx.add_window(Default::default(), |cx| editor.clone(cx))
+ })
+ .root(cx);
let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
@@ -509,9 +523,10 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
- cx.add_view(window_id, |cx| {
+ window.add_view(cx, |cx| {
let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
let mut editor = build_editor(buffer.clone(), cx);
let handle = cx.handle();
@@ -618,10 +633,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
fn test_cancel(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
@@ -661,9 +678,10 @@ fn test_cancel(cx: &mut TestAppContext) {
fn test_fold_action(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(
- &"
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(
+ &"
impl Foo {
// Hello!
@@ -680,11 +698,12 @@ fn test_fold_action(cx: &mut TestAppContext) {
}
}
"
- .unindent(),
- cx,
- );
- build_editor(buffer.clone(), cx)
- });
+ .unindent(),
+ cx,
+ );
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -752,7 +771,9 @@ fn test_move_cursor(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
- let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
+ let view = cx
+ .add_window(|cx| build_editor(buffer.clone(), cx))
+ .root(cx);
buffer.update(cx, |buffer, cx| {
buffer.edit(
@@ -827,10 +848,12 @@ fn test_move_cursor(cx: &mut TestAppContext) {
fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
- build_editor(buffer.clone(), cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
assert_eq!('ⓐ'.len_utf8(), 3);
assert_eq!('α'.len_utf8(), 2);
@@ -932,10 +955,12 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
- build_editor(buffer.clone(), cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
@@ -982,10 +1007,12 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
fn test_beginning_end_of_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\n def", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\n def", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -1145,10 +1172,12 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -1197,10 +1226,13 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer =
+ MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.set_wrap_width(Some(140.), cx);
@@ -1257,7 +1289,8 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
- cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
+ let window = cx.window;
+ window.simulate_resize(vec2f(100., 4. * line_height), &mut cx);
cx.set_state(
&r#"ˇone
@@ -1368,7 +1401,8 @@ async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
- cx.simulate_window_resize(cx.window_id, vec2f(1000., 4. * line_height + 0.5));
+ let window = cx.window;
+ window.simulate_resize(vec2f(1000., 4. * line_height + 0.5), &mut cx);
cx.set_state(
&r#"ˇone
@@ -1406,7 +1440,8 @@ async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
- cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
+ let window = cx.window;
+ window.simulate_resize(vec2f(100., 4. * line_height), &mut cx);
cx.set_state(
&r#"
@@ -1530,10 +1565,12 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("one two three four", cx);
- build_editor(buffer.clone(), cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("one two three four", cx);
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -1566,10 +1603,12 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
fn test_newline(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
- build_editor(buffer.clone(), cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -1589,9 +1628,10 @@ fn test_newline(cx: &mut TestAppContext) {
fn test_newline_with_old_selections(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(
- "
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(
+ "
a
b(
X
@@ -1600,19 +1640,20 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) {
X
)
"
- .unindent()
- .as_str(),
- cx,
- );
- let mut editor = build_editor(buffer.clone(), cx);
- editor.change_selections(None, cx, |s| {
- s.select_ranges([
- Point::new(2, 4)..Point::new(2, 5),
- Point::new(5, 4)..Point::new(5, 5),
- ])
- });
- editor
- });
+ .unindent()
+ .as_str(),
+ cx,
+ );
+ let mut editor = build_editor(buffer.clone(), cx);
+ editor.change_selections(None, cx, |s| {
+ s.select_ranges([
+ Point::new(2, 4)..Point::new(2, 5),
+ Point::new(5, 4)..Point::new(5, 5),
+ ])
+ });
+ editor
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
// Edit the buffer directly, deleting ranges surrounding the editor's selections
@@ -1817,12 +1858,14 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
fn test_insert_with_old_selections(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
- let mut editor = build_editor(buffer.clone(), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
- editor
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
+ let mut editor = build_editor(buffer.clone(), cx);
+ editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
+ editor
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
// Edit the buffer directly, deleting ranges surrounding the editor's selections
@@ -2329,10 +2372,12 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
fn test_delete_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -2352,10 +2397,12 @@ fn test_delete_line(cx: &mut TestAppContext) {
);
});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
@@ -2650,14 +2697,94 @@ async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
"});
}
+#[gpui::test]
+async fn test_manipulate_text(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ // Test convert_to_upper_case()
+ cx.set_state(indoc! {"
+ «hello worldˇ»
+ "});
+ cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «HELLO WORLDˇ»
+ "});
+
+ // Test convert_to_lower_case()
+ cx.set_state(indoc! {"
+ «HELLO WORLDˇ»
+ "});
+ cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «hello worldˇ»
+ "});
+
+ // From here on out, test more complex cases of manipulate_text()
+
+ // Test no selection case - should affect words cursors are in
+ // Cursor at beginning, middle, and end of word
+ cx.set_state(indoc! {"
+ ˇhello big beauˇtiful worldˇ
+ "});
+ cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
+ "});
+
+ // Test multiple selections on a single line and across multiple lines
+ cx.set_state(indoc! {"
+ «Theˇ» quick «brown
+ foxˇ» jumps «overˇ»
+ the «lazyˇ» dog
+ "});
+ cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «THEˇ» quick «BROWN
+ FOXˇ» jumps «OVERˇ»
+ the «LAZYˇ» dog
+ "});
+
+ // Test case where text length grows
+ cx.set_state(indoc! {"
+ «tschüߡ»
+ "});
+ cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «TSCHÜSSˇ»
+ "});
+
+ // Test to make sure we don't crash when text shrinks
+ cx.set_state(indoc! {"
+ aaa_bbbˇ
+ "});
+ cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «aaaBbbˇ»
+ "});
+
+ // Test to make sure we all aware of the fact that each word can grow and shrink
+ // Final selections should be aware of this fact
+ cx.set_state(indoc! {"
+ aaa_bˇbb bbˇb_ccc ˇccc_ddd
+ "});
+ cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
+ cx.assert_editor_state(indoc! {"
+ «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
+ "});
+}
+
#[gpui::test]
fn test_duplicate_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -2680,10 +2807,12 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
);
});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -2707,10 +2836,12 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
fn test_move_line_up_down(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.fold_ranges(
vec![
@@ -2806,10 +2937,12 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
- build_editor(buffer, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
let snapshot = editor.buffer.read(cx).snapshot(cx);
editor.insert_blocks(
@@ -2834,102 +2967,94 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
fn test_transpose(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- _ = cx
- .add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
+ _ = cx.add_window(|cx| {
+ let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bac");
- assert_eq!(editor.selections.ranges(cx), [2..2]);
+ editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bac");
+ assert_eq!(editor.selections.ranges(cx), [2..2]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bca");
- assert_eq!(editor.selections.ranges(cx), [3..3]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bca");
+ assert_eq!(editor.selections.ranges(cx), [3..3]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bac");
- assert_eq!(editor.selections.ranges(cx), [3..3]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bac");
+ assert_eq!(editor.selections.ranges(cx), [3..3]);
- editor
- })
- .1;
+ editor
+ });
- _ = cx
- .add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
+ _ = cx.add_window(|cx| {
+ let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acb\nde");
- assert_eq!(editor.selections.ranges(cx), [3..3]);
+ editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "acb\nde");
+ assert_eq!(editor.selections.ranges(cx), [3..3]);
- editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acbd\ne");
- assert_eq!(editor.selections.ranges(cx), [5..5]);
+ editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "acbd\ne");
+ assert_eq!(editor.selections.ranges(cx), [5..5]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acbde\n");
- assert_eq!(editor.selections.ranges(cx), [6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "acbde\n");
+ assert_eq!(editor.selections.ranges(cx), [6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acbd\ne");
- assert_eq!(editor.selections.ranges(cx), [6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "acbd\ne");
+ assert_eq!(editor.selections.ranges(cx), [6..6]);
- editor
- })
- .1;
+ editor
+ });
- _ = cx
- .add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
+ _ = cx.add_window(|cx| {
+ let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bacd\ne");
- assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
+ editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bacd\ne");
+ assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcade\n");
- assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bcade\n");
+ assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcda\ne");
- assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bcda\ne");
+ assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcade\n");
- assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bcade\n");
+ assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcaed\n");
- assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "bcaed\n");
+ assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
- editor
- })
- .1;
+ editor
+ });
- _ = cx
- .add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
+ _ = cx.add_window(|cx| {
+ let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "🏀🍐✋");
- assert_eq!(editor.selections.ranges(cx), [8..8]);
+ editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "🏀🍐✋");
+ assert_eq!(editor.selections.ranges(cx), [8..8]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "🏀✋🍐");
- assert_eq!(editor.selections.ranges(cx), [11..11]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "🏀✋🍐");
+ assert_eq!(editor.selections.ranges(cx), [11..11]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "🏀🍐✋");
- assert_eq!(editor.selections.ranges(cx), [11..11]);
+ editor.transpose(&Default::default(), cx);
+ assert_eq!(editor.text(cx), "🏀🍐✋");
+ assert_eq!(editor.selections.ranges(cx), [11..11]);
- editor
- })
- .1;
+ editor
+ });
}
#[gpui::test]
@@ -3132,10 +3257,12 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
fn test_select_all(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.select_all(&SelectAll, cx);
assert_eq!(
@@ -3149,10 +3276,12 @@ fn test_select_all(cx: &mut TestAppContext) {
fn test_select_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@@ -3196,10 +3325,12 @@ fn test_select_line(cx: &mut TestAppContext) {
fn test_split_selection_into_lines(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.fold_ranges(
vec![
@@ -3267,10 +3398,12 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
fn test_add_selection_above_below(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, view) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
- build_editor(buffer, cx)
- });
+ let view = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
+ build_editor(buffer, cx)
+ })
+ .root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@@ -3555,7 +3688,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+ let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@@ -3718,7 +3851,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor
.condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
.await;
@@ -4281,7 +4414,7 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+ let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@@ -4429,7 +4562,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor
.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@@ -4519,7 +4652,7 @@ async fn test_snippets(cx: &mut gpui::TestAppContext) {
);
let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| {
let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
@@ -4649,7 +4782,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
@@ -4761,7 +4894,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
@@ -4875,7 +5008,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+ let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
let format = editor.update(cx, |editor, cx| {
@@ -5653,7 +5786,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
multibuffer
});
- let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
+ let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
view.update(cx, |view, cx| {
assert_eq!(view.text(cx), "aaaa\nbbbb");
view.change_selections(None, cx, |s| {
@@ -5723,7 +5856,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
multibuffer
});
- let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
+ let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
view.update(cx, |view, cx| {
let (expected_text, selection_ranges) = marked_text_ranges(
indoc! {"
@@ -5799,22 +5932,24 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
multibuffer
});
- let (_, editor) = cx.add_window(|cx| {
- let mut editor = build_editor(multibuffer.clone(), cx);
- let snapshot = editor.snapshot(cx);
- editor.change_selections(None, cx, |s| {
- s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
- });
- editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
- assert_eq!(
- editor.selections.ranges(cx),
- [
- Point::new(1, 3)..Point::new(1, 3),
- Point::new(2, 1)..Point::new(2, 1),
- ]
- );
- editor
- });
+ let editor = cx
+ .add_window(|cx| {
+ let mut editor = build_editor(multibuffer.clone(), cx);
+ let snapshot = editor.snapshot(cx);
+ editor.change_selections(None, cx, |s| {
+ s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
+ });
+ editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
+ assert_eq!(
+ editor.selections.ranges(cx),
+ [
+ Point::new(1, 3)..Point::new(1, 3),
+ Point::new(2, 1)..Point::new(2, 1),
+ ]
+ );
+ editor
+ })
+ .root(cx);
// Refreshing selections is a no-op when excerpts haven't changed.
editor.update(cx, |editor, cx| {
@@ -5884,16 +6019,18 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
multibuffer
});
- let (_, editor) = cx.add_window(|cx| {
- let mut editor = build_editor(multibuffer.clone(), cx);
- let snapshot = editor.snapshot(cx);
- editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
- assert_eq!(
- editor.selections.ranges(cx),
- [Point::new(1, 3)..Point::new(1, 3)]
- );
- editor
- });
+ let editor = cx
+ .add_window(|cx| {
+ let mut editor = build_editor(multibuffer.clone(), cx);
+ let snapshot = editor.snapshot(cx);
+ editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
+ assert_eq!(
+ editor.selections.ranges(cx),
+ [Point::new(1, 3)..Point::new(1, 3)]
+ );
+ editor
+ })
+ .root(cx);
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
@@ -5956,7 +6093,7 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
- let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+ let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@@ -5992,10 +6129,12 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
fn test_highlighted_ranges(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
- build_editor(buffer.clone(), cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
+ build_editor(buffer.clone(), cx)
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
struct Type1;
@@ -6084,16 +6223,20 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
.unwrap();
cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
});
- let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
- let (_, follower) = cx.update(|cx| {
- cx.add_window(
- WindowOptions {
- bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
- ..Default::default()
- },
- |cx| build_editor(buffer.clone(), cx),
- )
- });
+ let leader = cx
+ .add_window(|cx| build_editor(buffer.clone(), cx))
+ .root(cx);
+ let follower = cx
+ .update(|cx| {
+ cx.add_window(
+ WindowOptions {
+ bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
+ ..Default::default()
+ },
+ |cx| build_editor(buffer.clone(), cx),
+ )
+ })
+ .root(cx);
let is_still_following = Rc::new(RefCell::new(true));
let follower_edit_event_count = Rc::new(RefCell::new(0));
@@ -6224,7 +6367,9 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let leader = pane.update(cx, |_, cx| {
@@ -6968,7 +7113,7 @@ async fn test_copilot_multibuffer(
);
multibuffer
});
- let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
+ let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
handle_copilot_completion_request(
&copilot_lsp,
@@ -7098,7 +7243,7 @@ async fn test_copilot_disabled_globs(
);
multibuffer
});
- let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
+ let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
let mut copilot_requests = copilot_lsp
.handle_request::(move |_params, _cx| async move {
@@ -7177,7 +7322,9 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -7282,7 +7429,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
- let (_, _workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let _buffer = project
.update(cx, |project, cx| {
project.open_local_buffer("/a/main.rs", cx)
diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs
index 94a849b0d0..0ea4ff758b 100644
--- a/crates/editor/src/element.rs
+++ b/crates/editor/src/element.rs
@@ -172,6 +172,10 @@ impl EditorElement {
.on_drag(MouseButton::Left, {
let position_map = position_map.clone();
move |event, editor, cx| {
+ if event.end {
+ return;
+ }
+
if !Self::mouse_dragged(
editor,
event.platform_event,
@@ -542,8 +546,20 @@ impl EditorElement {
});
}
+ let scroll_left =
+ layout.position_map.snapshot.scroll_position().x() * layout.position_map.em_width;
+
for (wrap_position, active) in layout.wrap_guides.iter() {
- let x = text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.;
+ let x =
+ (text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.)
+ - scroll_left;
+
+ if x < text_bounds.origin_x()
+ || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
+ {
+ continue;
+ }
+
let color = if *active {
self.style.active_wrap_guide
} else {
@@ -1032,6 +1048,10 @@ impl EditorElement {
scene.pop_layer();
}
+ fn scrollbar_left(&self, bounds: &RectF) -> f32 {
+ bounds.max_x() - self.style.theme.scrollbar.width
+ }
+
fn paint_scrollbar(
&mut self,
scene: &mut SceneBuilder,
@@ -1050,7 +1070,7 @@ impl EditorElement {
let top = bounds.min_y();
let bottom = bounds.max_y();
let right = bounds.max_x();
- let left = right - style.width;
+ let left = self.scrollbar_left(&bounds);
let row_range = &layout.scrollbar_row_range;
let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
@@ -1087,8 +1107,6 @@ impl EditorElement {
if layout.is_singleton && scrollbar_settings.selections {
let start_anchor = Anchor::min();
let end_anchor = Anchor::max();
- let mut start_row = None;
- let mut end_row = None;
let color = scrollbar_theme.selections;
let border = Border {
width: 1.,
@@ -1099,54 +1117,32 @@ impl EditorElement {
bottom: false,
left: true,
};
- let mut push_region = |start, end| {
- if let (Some(start_display), Some(end_display)) = (start, end) {
- let start_y = y_for_row(start_display as f32);
- let mut end_y = y_for_row(end_display as f32);
- if end_y - start_y < 1. {
- end_y = start_y + 1.;
- }
- let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
-
- scene.push_quad(Quad {
- bounds,
- background: Some(color),
- border,
- corner_radius: style.thumb.corner_radius,
- })
+ let mut push_region = |start: DisplayPoint, end: DisplayPoint| {
+ let start_y = y_for_row(start.row() as f32);
+ let mut end_y = y_for_row(end.row() as f32);
+ if end_y - start_y < 1. {
+ end_y = start_y + 1.;
}
+ let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
+
+ scene.push_quad(Quad {
+ bounds,
+ background: Some(color),
+ border,
+ corner_radius: style.thumb.corner_radius,
+ })
};
- for (row, _) in &editor
- .background_highlights_in_range_for::(
+ let background_ranges = editor
+ .background_highlight_row_ranges::(
start_anchor..end_anchor,
&layout.position_map.snapshot,
- &theme,
- )
- {
- let start_display = row.start;
- let end_display = row.end;
-
- if start_row.is_none() {
- assert_eq!(end_row, None);
- start_row = Some(start_display.row());
- end_row = Some(end_display.row());
- continue;
- }
- if let Some(current_end) = end_row.as_mut() {
- if start_display.row() > *current_end + 1 {
- push_region(start_row, end_row);
- start_row = Some(start_display.row());
- end_row = Some(end_display.row());
- } else {
- // Merge two hunks.
- *current_end = end_display.row();
- }
- } else {
- unreachable!();
- }
+ 50000,
+ );
+ for row in background_ranges {
+ let start = row.start();
+ let end = row.end();
+ push_region(*start, *end);
}
- // We might still have a hunk that was not rendered (if there was a search hit on the last line)
- push_region(start_row, end_row);
}
if layout.is_singleton && scrollbar_settings.git_diff {
@@ -1235,6 +1231,10 @@ impl EditorElement {
})
.on_drag(MouseButton::Left, {
move |event, editor: &mut Editor, cx| {
+ if event.end {
+ return;
+ }
+
let y = event.prev_mouse_position.y();
let new_y = event.position.y();
if thumb_top < y && y < thumb_bottom {
@@ -2978,10 +2978,12 @@ mod tests {
fn test_layout_line_numbers(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
- Editor::new(EditorMode::Full, buffer, None, None, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
+ Editor::new(EditorMode::Full, buffer, None, None, cx)
+ })
+ .root(cx);
let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let layouts = editor.update(cx, |editor, cx| {
@@ -2997,10 +2999,12 @@ mod tests {
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("", cx);
- Editor::new(EditorMode::Full, buffer, None, None, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple("", cx);
+ Editor::new(EditorMode::Full, buffer, None, None, cx)
+ })
+ .root(cx);
editor.update(cx, |editor, cx| {
editor.set_placeholder_text("hello", cx);
@@ -3214,10 +3218,12 @@ mod tests {
info!(
"Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
);
- let (_, editor) = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple(&input_text, cx);
- Editor::new(editor_mode, buffer, None, None, cx)
- });
+ let editor = cx
+ .add_window(|cx| {
+ let buffer = MultiBuffer::build_simple(&input_text, cx);
+ Editor::new(editor_mode, buffer, None, None, cx)
+ })
+ .root(cx);
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, layout_state) = editor.update(cx, |editor, cx| {
diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs
index 63076ba234..2d75b4d2ce 100644
--- a/crates/editor/src/inlay_hint_cache.rs
+++ b/crates/editor/src/inlay_hint_cache.rs
@@ -571,7 +571,6 @@ fn new_update_task(
if let Some(buffer) =
refresh_multi_buffer.buffer(pending_refresh_query.buffer_id)
{
- drop(refresh_multi_buffer);
editor.inlay_hint_cache.update_tasks.insert(
pending_refresh_query.excerpt_id,
UpdateTask {
@@ -1136,7 +1135,9 @@ mod tests {
)
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -1836,7 +1837,9 @@ mod tests {
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -1989,7 +1992,9 @@ mod tests {
project.update(cx, |project, _| {
project.languages().add(Arc::clone(&language))
});
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -2075,8 +2080,9 @@ mod tests {
deterministic.run_until_parked();
cx.foreground().run_until_parked();
- let (_, editor) =
- cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
+ let editor = cx
+ .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
+ .root(cx);
let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap();
let closure_editor_edited = Arc::clone(&editor_edited);
@@ -2328,7 +2334,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
project.update(cx, |project, _| {
project.languages().add(Arc::clone(&language))
});
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -2373,8 +2381,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
deterministic.run_until_parked();
cx.foreground().run_until_parked();
- let (_, editor) =
- cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
+ let editor = cx
+ .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
+ .root(cx);
let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap();
let closure_editor_edited = Arc::clone(&editor_edited);
@@ -2562,7 +2571,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project.clone(), cx))
+ .root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs
index 7c8fe12aa0..b99977a60e 100644
--- a/crates/editor/src/items.rs
+++ b/crates/editor/src/items.rs
@@ -28,7 +28,10 @@ use std::{
path::{Path, PathBuf},
};
use text::Selection;
-use util::{paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
+use util::{
+ paths::{PathExt, FILE_ROW_COLUMN_DELIMITER},
+ ResultExt, TryFutureExt,
+};
use workspace::item::{BreadcrumbText, FollowableItemHandle};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
@@ -546,9 +549,7 @@ impl Item for Editor {
.and_then(|f| f.as_local())?
.abs_path(cx);
- let file_path = util::paths::compact(&file_path)
- .to_string_lossy()
- .to_string();
+ let file_path = file_path.compact().to_string_lossy().to_string();
Some(file_path.into())
}
diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs
index 0fe49d4d04..83aaa3b703 100644
--- a/crates/editor/src/test/editor_lsp_test_context.rs
+++ b/crates/editor/src/test/editor_lsp_test_context.rs
@@ -69,7 +69,8 @@ impl<'a> EditorLspTestContext<'a> {
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
.await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let workspace = window.root(cx);
project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/root", true, cx)
@@ -98,7 +99,7 @@ impl<'a> EditorLspTestContext<'a> {
Self {
cx: EditorTestContext {
cx,
- window_id,
+ window: window.into(),
editor,
},
lsp,
diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs
index bac70f139a..118cddaa92 100644
--- a/crates/editor/src/test/editor_test_context.rs
+++ b/crates/editor/src/test/editor_test_context.rs
@@ -3,7 +3,8 @@ use crate::{
};
use futures::Future;
use gpui::{
- keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
+ keymap_matcher::Keystroke, AnyWindowHandle, AppContext, ContextHandle, ModelContext,
+ ViewContext, ViewHandle,
};
use indoc::indoc;
use language::{Buffer, BufferSnapshot};
@@ -21,7 +22,7 @@ use super::build_editor;
pub struct EditorTestContext<'a> {
pub cx: &'a mut gpui::TestAppContext,
- pub window_id: usize,
+ pub window: AnyWindowHandle,
pub editor: ViewHandle,
}
@@ -32,16 +33,14 @@ impl<'a> EditorTestContext<'a> {
let buffer = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.unwrap();
- let (window_id, editor) = cx.update(|cx| {
- cx.add_window(Default::default(), |cx| {
- cx.focus_self();
- build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
- })
+ let window = cx.add_window(|cx| {
+ cx.focus_self();
+ build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
});
-
+ let editor = window.root(cx);
Self {
cx,
- window_id,
+ window: window.into(),
editor,
}
}
@@ -113,7 +112,8 @@ impl<'a> EditorTestContext<'a> {
let keystroke_under_test_handle =
self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
let keystroke = Keystroke::parse(keystroke_text).unwrap();
- self.cx.dispatch_keystroke(self.window_id, keystroke, false);
+
+ self.cx.dispatch_keystroke(self.window, keystroke, false);
keystroke_under_test_handle
}
diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs
index b6701f12d6..523d6e8a5c 100644
--- a/crates/file_finder/src/file_finder.rs
+++ b/crates/file_finder/src/file_finder.rs
@@ -617,8 +617,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- cx.dispatch_action(window_id, Toggle);
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
+ cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap());
finder
@@ -631,8 +632,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window_id, SelectNext);
- cx.dispatch_action(window_id, Confirm);
+ cx.dispatch_action(window.into(), SelectNext);
+ cx.dispatch_action(window.into(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@@ -671,8 +672,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- cx.dispatch_action(window_id, Toggle);
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
+ cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap());
let file_query = &first_file_name[..3];
@@ -704,8 +706,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window_id, SelectNext);
- cx.dispatch_action(window_id, Confirm);
+ cx.dispatch_action(window.into(), SelectNext);
+ cx.dispatch_action(window.into(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@@ -754,8 +756,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- cx.dispatch_action(window_id, Toggle);
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
+ cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap());
let file_query = &first_file_name[..3];
@@ -787,8 +790,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window_id, SelectNext);
- cx.dispatch_action(window_id, Confirm);
+ cx.dispatch_action(window.into(), SelectNext);
+ cx.dispatch_action(window.into(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@@ -837,19 +840,23 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
+ let finder = cx
+ .add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ Vec::new(),
+ cx,
+ ),
cx,
- ),
- cx,
- )
- });
+ )
+ })
+ .root(cx);
let query = test_path_like("hi");
finder
@@ -931,19 +938,23 @@ mod tests {
cx,
)
.await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
+ let finder = cx
+ .add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ Vec::new(),
+ cx,
+ ),
cx,
- ),
- cx,
- )
- });
+ )
+ })
+ .root(cx);
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("hi"), cx)
@@ -967,19 +978,23 @@ mod tests {
cx,
)
.await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
+ let finder = cx
+ .add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ Vec::new(),
+ cx,
+ ),
cx,
- ),
- cx,
- )
- });
+ )
+ })
+ .root(cx);
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
@@ -1015,61 +1030,6 @@ mod tests {
finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
}
- #[gpui::test]
- async fn test_multiple_matches_with_same_relative_path(cx: &mut TestAppContext) {
- let app_state = init_test(cx);
- app_state
- .fs
- .as_fake()
- .insert_tree(
- "/root",
- json!({
- "dir1": { "a.txt": "" },
- "dir2": { "a.txt": "" }
- }),
- )
- .await;
-
- let project = Project::test(
- app_state.fs.clone(),
- ["/root/dir1".as_ref(), "/root/dir2".as_ref()],
- cx,
- )
- .await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
- cx,
- ),
- cx,
- )
- });
-
- // Run a search that matches two files with the same relative path.
- finder
- .update(cx, |f, cx| {
- f.delegate_mut().spawn_search(test_path_like("a.t"), cx)
- })
- .await;
-
- // Can switch between different matches with the same relative path.
- finder.update(cx, |finder, cx| {
- let delegate = finder.delegate_mut();
- assert_eq!(delegate.matches.len(), 2);
- assert_eq!(delegate.selected_index(), 0);
- delegate.set_selected_index(1, cx);
- assert_eq!(delegate.selected_index(), 1);
- delegate.set_selected_index(0, cx);
- assert_eq!(delegate.selected_index(), 0);
- });
- }
-
#[gpui::test]
async fn test_path_distance_ordering(cx: &mut TestAppContext) {
let app_state = init_test(cx);
@@ -1089,7 +1049,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::>();
assert_eq!(worktrees.len(), 1);
@@ -1103,18 +1065,20 @@ mod tests {
worktree_id,
path: Arc::from(Path::new("/root/dir2/b.txt")),
}));
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- b_path,
- Vec::new(),
+ let finder = cx
+ .add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ b_path,
+ Vec::new(),
+ cx,
+ ),
cx,
- ),
- cx,
- )
- });
+ )
+ })
+ .root(cx);
finder
.update(cx, |f, cx| {
@@ -1151,19 +1115,23 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
- let (_, finder) = cx.add_window(|cx| {
- Picker::new(
- FileFinderDelegate::new(
- workspace.downgrade(),
- workspace.read(cx).project().clone(),
- None,
- Vec::new(),
+ let workspace = cx
+ .add_window(|cx| Workspace::test_new(project, cx))
+ .root(cx);
+ let finder = cx
+ .add_window(|cx| {
+ Picker::new(
+ FileFinderDelegate::new(
+ workspace.downgrade(),
+ workspace.read(cx).project().clone(),
+ None,
+ Vec::new(),
+ cx,
+ ),
cx,
- ),
- cx,
- )
- });
+ )
+ })
+ .root(cx);
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("dir"), cx)
@@ -1198,7 +1166,8 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::>();
assert_eq!(worktrees.len(), 1);
@@ -1216,7 +1185,7 @@ mod tests {
"fir",
1,
"first.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1231,7 +1200,7 @@ mod tests {
"sec",
1,
"second.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1253,7 +1222,7 @@ mod tests {
"thi",
1,
"third.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1285,7 +1254,7 @@ mod tests {
"sec",
1,
"second.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1324,7 +1293,7 @@ mod tests {
"thi",
1,
"third.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1404,7 +1373,8 @@ mod tests {
.detach();
deterministic.run_until_parked();
- let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let workspace = window.root(cx);
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::>();
assert_eq!(worktrees.len(), 1,);
@@ -1439,7 +1409,7 @@ mod tests {
"sec",
1,
"second.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1461,7 +1431,7 @@ mod tests {
"fir",
1,
"first.rs",
- window_id,
+ window.into(),
&workspace,
&deterministic,
cx,
@@ -1493,12 +1463,12 @@ mod tests {
input: &str,
expected_matches: usize,
expected_editor_title: &str,
- window_id: usize,
+ window: gpui::AnyWindowHandle,
workspace: &ViewHandle,
deterministic: &gpui::executor::Deterministic,
cx: &mut gpui::TestAppContext,
) -> Vec {
- cx.dispatch_action(window_id, Toggle);
+ cx.dispatch_action(window, Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap());
finder
.update(cx, |finder, cx| {
@@ -1515,8 +1485,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
- cx.dispatch_action(window_id, SelectNext);
- cx.dispatch_action(window_id, Confirm);
+ cx.dispatch_action(window, SelectNext);
+ cx.dispatch_action(window, Confirm);
deterministic.run_until_parked();
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs
index 769f2eda55..1d3b44fa43 100644
--- a/crates/go_to_line/src/go_to_line.rs
+++ b/crates/go_to_line/src/go_to_line.rs
@@ -135,7 +135,7 @@ impl Entity for GoToLine {
fn release(&mut self, cx: &mut AppContext) {
let scroll_position = self.prev_scroll_position.take();
- cx.update_window(self.active_editor.window_id(), |cx| {
+ self.active_editor.window().update(cx, |cx| {
self.active_editor.update(cx, |editor, cx| {
editor.highlight_rows(None);
if let Some(scroll_position) = scroll_position {
diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml
index 31b7db008d..5bd7d03184 100644
--- a/crates/gpui/Cargo.toml
+++ b/crates/gpui/Cargo.toml
@@ -22,6 +22,7 @@ sqlez = { path = "../sqlez" }
async-task = "4.0.3"
backtrace = { version = "0.3", optional = true }
ctor.workspace = true
+derive_more.workspace = true
dhat = { version = "0.3", optional = true }
env_logger = { version = "0.9", optional = true }
etagere = "0.2"
diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs
index fd22dc466e..2a9d9f4768 100644
--- a/crates/gpui/src/app.rs
+++ b/crates/gpui/src/app.rs
@@ -23,6 +23,8 @@ use std::{
};
use anyhow::{anyhow, Context, Result};
+
+use derive_more::Deref;
use parking_lot::Mutex;
use postage::oneshot;
use smallvec::SmallVec;
@@ -131,8 +133,20 @@ pub trait BorrowAppContext {
}
pub trait BorrowWindowContext {
- fn read_with T>(&self, window_id: usize, f: F) -> T;
- fn update T>(&mut self, window_id: usize, f: F) -> T;
+ type Result;
+
+ fn read_window(&self, window: AnyWindowHandle, f: F) -> Self::Result
+ where
+ F: FnOnce(&WindowContext) -> T;
+ fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option
+ where
+ F: FnOnce(&WindowContext) -> Option;
+ fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Self::Result
+ where
+ F: FnOnce(&mut WindowContext) -> T;
+ fn update_window_optional(&mut self, window: AnyWindowHandle, f: F) -> Option
+ where
+ F: FnOnce(&mut WindowContext) -> Option;
}
#[derive(Clone)]
@@ -295,13 +309,12 @@ impl App {
result
}
- fn update_window T>(
- &mut self,
- window_id: usize,
- callback: F,
- ) -> Option {
+ fn update_window(&mut self, window: AnyWindowHandle, callback: F) -> Option
+ where
+ F: FnOnce(&mut WindowContext) -> T,
+ {
let mut state = self.0.borrow_mut();
- let result = state.update_window(window_id, callback);
+ let result = state.update_window(window, callback);
state.pending_notifications.clear();
result
}
@@ -328,67 +341,8 @@ impl AsyncAppContext {
self.0.borrow_mut().update(callback)
}
- pub fn read_window T>(
- &self,
- window_id: usize,
- callback: F,
- ) -> Option {
- self.0.borrow_mut().read_window(window_id, callback)
- }
-
- pub fn update_window T>(
- &mut self,
- window_id: usize,
- callback: F,
- ) -> Option {
- self.0.borrow_mut().update_window(window_id, callback)
- }
-
- pub fn debug_elements(&self, window_id: usize) -> Option {
- self.0.borrow().read_window(window_id, |cx| {
- let root_view = cx.window.root_view();
- let root_element = cx.window.rendered_views.get(&root_view.id())?;
- root_element.debug(cx).log_err()
- })?
- }
-
- pub fn dispatch_action(
- &mut self,
- window_id: usize,
- view_id: usize,
- action: &dyn Action,
- ) -> Result<()> {
- self.0
- .borrow_mut()
- .update_window(window_id, |window| {
- window.dispatch_action(Some(view_id), action);
- })
- .ok_or_else(|| anyhow!("window not found"))
- }
-
- pub fn available_actions(
- &self,
- window_id: usize,
- view_id: usize,
- ) -> Vec<(&'static str, Box, SmallVec<[Binding; 1]>)> {
- self.read_window(window_id, |cx| cx.available_actions(view_id))
- .unwrap_or_default()
- }
-
- pub fn has_window(&self, window_id: usize) -> bool {
- self.read(|cx| cx.windows.contains_key(&window_id))
- }
-
- pub fn window_is_active(&self, window_id: usize) -> bool {
- self.read(|cx| cx.windows.get(&window_id).map_or(false, |w| w.is_active))
- }
-
- pub fn root_view(&self, window_id: usize) -> Option {
- self.read(|cx| cx.windows.get(&window_id).map(|w| w.root_view().clone()))
- }
-
- pub fn window_ids(&self) -> Vec {
- self.read(|cx| cx.windows.keys().copied().collect())
+ pub fn windows(&self) -> Vec {
+ self.0.borrow().windows().collect()
}
pub fn add_model(&mut self, build_model: F) -> ModelHandle
@@ -403,7 +357,7 @@ impl AsyncAppContext {
&mut self,
window_options: WindowOptions,
build_root_view: F,
- ) -> (usize, ViewHandle)
+ ) -> WindowHandle
where
T: View,
F: FnOnce(&mut ViewContext) -> T,
@@ -411,25 +365,6 @@ impl AsyncAppContext {
self.update(|cx| cx.add_window(window_options, build_root_view))
}
- pub fn remove_window(&mut self, window_id: usize) {
- self.update_window(window_id, |cx| cx.remove_window());
- }
-
- pub fn activate_window(&mut self, window_id: usize) {
- self.update_window(window_id, |cx| cx.activate_window());
- }
-
- // TODO: Can we eliminate this method and move it to WindowContext then call it with update_window?s
- pub fn prompt(
- &mut self,
- window_id: usize,
- level: PromptLevel,
- msg: &str,
- answers: &[&str],
- ) -> Option> {
- self.update_window(window_id, |cx| cx.prompt(level, msg, answers))
- }
-
pub fn platform(&self) -> Arc {
self.0.borrow().platform().clone()
}
@@ -453,6 +388,42 @@ impl BorrowAppContext for AsyncAppContext {
}
}
+impl BorrowWindowContext for AsyncAppContext {
+ type Result = Option;
+
+ fn read_window(&self, window: AnyWindowHandle, f: F) -> Self::Result
+ where
+ F: FnOnce(&WindowContext) -> T,
+ {
+ self.0.borrow().read_with(|cx| cx.read_window(window, f))
+ }
+
+ fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option
+ where
+ F: FnOnce(&WindowContext) -> Option,
+ {
+ self.0
+ .borrow_mut()
+ .update(|cx| cx.read_window_optional(window, f))
+ }
+
+ fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Self::Result
+ where
+ F: FnOnce(&mut WindowContext) -> T,
+ {
+ self.0.borrow_mut().update(|cx| cx.update_window(window, f))
+ }
+
+ fn update_window_optional(&mut self, window: AnyWindowHandle, f: F) -> Option
+ where
+ F: FnOnce(&mut WindowContext) -> Option,
+ {
+ self.0
+ .borrow_mut()
+ .update(|cx| cx.update_window_optional(window, f))
+ }
+}
+
type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize);
type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext);
@@ -474,9 +445,9 @@ type WindowShouldCloseSubscriptionCallback = Box b
pub struct AppContext {
models: HashMap>,
- views: HashMap<(usize, usize), Box>,
- views_metadata: HashMap<(usize, usize), ViewMetadata>,
- windows: HashMap,
+ views: HashMap<(AnyWindowHandle, usize), Box>,
+ views_metadata: HashMap<(AnyWindowHandle, usize), ViewMetadata>,
+ windows: HashMap,
globals: HashMap>,
element_states: HashMap>,
background: Arc,
@@ -495,8 +466,8 @@ pub struct AppContext {
// Action Types -> Action Handlers
global_actions: HashMap>,
keystroke_matcher: KeymapMatcher,
- next_entity_id: usize,
- next_window_id: usize,
+ next_id: usize,
+ // next_window: AnyWindowHandle,
next_subscription_id: usize,
frame_count: usize,
@@ -507,10 +478,10 @@ pub struct AppContext {
focus_observations: CallbackCollection,
release_observations: CallbackCollection,
action_dispatch_observations: CallbackCollection<(), ActionObservationCallback>,
- window_activation_observations: CallbackCollection,
- window_fullscreen_observations: CallbackCollection,
- window_bounds_observations: CallbackCollection,
- keystroke_observations: CallbackCollection,
+ window_activation_observations: CallbackCollection,
+ window_fullscreen_observations: CallbackCollection,
+ window_bounds_observations: CallbackCollection,
+ keystroke_observations: CallbackCollection,
active_labeled_task_observations: CallbackCollection<(), ActiveLabeledTasksCallback>,
foreground: Rc,
@@ -555,8 +526,7 @@ impl AppContext {
actions: Default::default(),
global_actions: Default::default(),
keystroke_matcher: KeymapMatcher::default(),
- next_entity_id: 0,
- next_window_id: 0,
+ next_id: 0,
next_subscription_id: 0,
frame_count: 0,
subscriptions: Default::default(),
@@ -757,13 +727,13 @@ impl AppContext {
}
}
- pub fn view_ui_name(&self, window_id: usize, view_id: usize) -> Option<&'static str> {
- Some(self.views.get(&(window_id, view_id))?.ui_name())
+ pub fn view_ui_name(&self, window: AnyWindowHandle, view_id: usize) -> Option<&'static str> {
+ Some(self.views.get(&(window, view_id))?.ui_name())
}
- pub fn view_type_id(&self, window_id: usize, view_id: usize) -> Option {
+ pub fn view_type_id(&self, window: AnyWindowHandle, view_id: usize) -> Option {
self.views_metadata
- .get(&(window_id, view_id))
+ .get(&(window, view_id))
.map(|metadata| metadata.type_id)
}
@@ -784,39 +754,22 @@ impl AppContext {
result
}
- pub fn read_window T>(
+ fn read_window T>(
&self,
- window_id: usize,
+ handle: AnyWindowHandle,
callback: F,
) -> Option {
- let window = self.windows.get(&window_id)?;
- let window_context = WindowContext::immutable(self, &window, window_id);
+ let window = self.windows.get(&handle)?;
+ let window_context = WindowContext::immutable(self, &window, handle);
Some(callback(&window_context))
}
- pub fn update_window T>(
- &mut self,
- window_id: usize,
- callback: F,
- ) -> Option {
- self.update(|app_context| {
- let mut window = app_context.windows.remove(&window_id)?;
- let mut window_context = WindowContext::mutable(app_context, &mut window, window_id);
- let result = callback(&mut window_context);
- if !window_context.removed {
- app_context.windows.insert(window_id, window);
- }
- Some(result)
- })
- }
-
pub fn update_active_window T>(
&mut self,
callback: F,
) -> Option {
- self.platform
- .main_window_id()
- .and_then(|id| self.update_window(id, callback))
+ self.active_window()
+ .and_then(|window| window.update(self, callback))
}
pub fn prompt_for_paths(
@@ -1054,10 +1007,10 @@ impl AppContext {
}
}
- fn notify_view(&mut self, window_id: usize, view_id: usize) {
+ fn notify_view(&mut self, window: AnyWindowHandle, view_id: usize) {
if self.pending_notifications.insert(view_id) {
self.pending_effects
- .push_back(Effect::ViewNotification { window_id, view_id });
+ .push_back(Effect::ViewNotification { window, view_id });
}
}
@@ -1075,13 +1028,13 @@ impl AppContext {
pub fn is_action_available(&self, action: &dyn Action) -> bool {
let mut available_in_window = false;
let action_id = action.id();
- if let Some(window_id) = self.platform.main_window_id() {
+ if let Some(window) = self.active_window() {
available_in_window = self
- .read_window(window_id, |cx| {
+ .read_window(window, |cx| {
if let Some(focused_view_id) = cx.focused_view_id() {
for view_id in cx.ancestors(focused_view_id) {
if let Some(view_metadata) =
- cx.views_metadata.get(&(window_id, view_id))
+ cx.views_metadata.get(&(cx.window_handle, view_id))
{
if let Some(actions) = cx.actions.get(&view_metadata.type_id) {
if actions.contains_key(&action_id) {
@@ -1129,6 +1082,12 @@ impl AppContext {
self.keystroke_matcher.clear_bindings();
}
+ pub fn binding_for_action(&self, action: &dyn Action) -> Option<&Binding> {
+ self.keystroke_matcher
+ .bindings_for_action(action.id())
+ .find(|binding| binding.action().eq(action))
+ }
+
pub fn default_global(&mut self) -> &T {
let type_id = TypeId::of::();
self.update(|this| {
@@ -1221,7 +1180,7 @@ impl AppContext {
F: FnOnce(&mut ModelContext) -> T,
{
self.update(|this| {
- let model_id = post_inc(&mut this.next_entity_id);
+ let model_id = post_inc(&mut this.next_id);
let handle = ModelHandle::new(model_id, &this.ref_counts);
let mut cx = ModelContext::new(this, model_id);
let model = build_model(&mut cx);
@@ -1295,46 +1254,40 @@ impl AppContext {
&mut self,
window_options: WindowOptions,
build_root_view: F,
- ) -> (usize, ViewHandle)
+ ) -> WindowHandle
where
V: View,
F: FnOnce(&mut ViewContext) -> V,
{
self.update(|this| {
- let window_id = post_inc(&mut this.next_window_id);
+ let handle = WindowHandle::::new(post_inc(&mut this.next_id));
let platform_window =
this.platform
- .open_window(window_id, window_options, this.foreground.clone());
- let window = this.build_window(window_id, platform_window, build_root_view);
- let root_view = window.root_view().clone().downcast::().unwrap();
- this.windows.insert(window_id, window);
- (window_id, root_view)
+ .open_window(handle.into(), window_options, this.foreground.clone());
+ let window = this.build_window(handle.into(), platform_window, build_root_view);
+ this.windows.insert(handle.into(), window);
+ handle
})
}
- pub fn add_status_bar_item(&mut self, build_root_view: F) -> (usize, ViewHandle)
+ pub fn add_status_bar_item(&mut self, build_root_view: F) -> WindowHandle
where
V: View,
F: FnOnce(&mut ViewContext) -> V,
{
self.update(|this| {
- let window_id = post_inc(&mut this.next_window_id);
- let platform_window = this.platform.add_status_item(window_id);
- let window = this.build_window(window_id, platform_window, build_root_view);
- let root_view = window.root_view().clone().downcast::().unwrap();
-
- this.windows.insert(window_id, window);
- this.update_window(window_id, |cx| {
- root_view.update(cx, |view, cx| view.focus_in(cx.handle().into_any(), cx))
- });
-
- (window_id, root_view)
+ let handle = WindowHandle::::new(post_inc(&mut this.next_id));
+ let platform_window = this.platform.add_status_item(handle.into());
+ let window = this.build_window(handle.into(), platform_window, build_root_view);
+ this.windows.insert(handle.into(), window);
+ handle.update_root(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
+ handle
})
}
pub fn build_window(
&mut self,
- window_id: usize,
+ handle: AnyWindowHandle,
mut platform_window: Box,
build_root_view: F,
) -> Window
@@ -1346,7 +1299,7 @@ impl AppContext {
let mut app = self.upgrade();
platform_window.on_event(Box::new(move |event| {
- app.update_window(window_id, |cx| {
+ app.update_window(handle, |cx| {
if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
if cx.dispatch_keystroke(keystroke) {
return true;
@@ -1362,35 +1315,35 @@ impl AppContext {
{
let mut app = self.upgrade();
platform_window.on_active_status_change(Box::new(move |is_active| {
- app.update(|cx| cx.window_changed_active_status(window_id, is_active))
+ app.update(|cx| cx.window_changed_active_status(handle, is_active))
}));
}
{
let mut app = self.upgrade();
platform_window.on_resize(Box::new(move || {
- app.update(|cx| cx.window_was_resized(window_id))
+ app.update(|cx| cx.window_was_resized(handle))
}));
}
{
let mut app = self.upgrade();
platform_window.on_moved(Box::new(move || {
- app.update(|cx| cx.window_was_moved(window_id))
+ app.update(|cx| cx.window_was_moved(handle))
}));
}
{
let mut app = self.upgrade();
platform_window.on_fullscreen(Box::new(move |is_fullscreen| {
- app.update(|cx| cx.window_was_fullscreen_changed(window_id, is_fullscreen))
+ app.update(|cx| cx.window_was_fullscreen_changed(handle, is_fullscreen))
}));
}
{
let mut app = self.upgrade();
platform_window.on_close(Box::new(move || {
- app.update(|cx| cx.update_window(window_id, |cx| cx.remove_window()));
+ app.update(|cx| cx.update_window(handle, |cx| cx.remove_window()));
}));
}
@@ -1402,31 +1355,27 @@ impl AppContext {
platform_window.set_input_handler(Box::new(WindowInputHandler {
app: self.upgrade().0,
- window_id,
+ window: handle,
}));
- let mut window = Window::new(window_id, platform_window, self, build_root_view);
- let mut cx = WindowContext::mutable(self, &mut window, window_id);
+ let mut window = Window::new(handle, platform_window, self, build_root_view);
+ let mut cx = WindowContext::mutable(self, &mut window, handle);
cx.layout(false).expect("initial layout should not error");
let scene = cx.paint().expect("initial paint should not error");
window.platform_window.present_scene(scene);
window
}
- pub fn replace_root_view(
- &mut self,
- window_id: usize,
- build_root_view: F,
- ) -> Option>
- where
- V: View,
- F: FnOnce(&mut ViewContext) -> V,
- {
- self.update_window(window_id, |cx| cx.replace_root_view(build_root_view))
+ pub fn active_window(&self) -> Option {
+ self.platform.main_window()
+ }
+
+ pub fn windows(&self) -> impl '_ + Iterator- {
+ self.windows.keys().copied()
}
pub fn read_view(&self, handle: &ViewHandle) -> &T {
- if let Some(view) = self.views.get(&(handle.window_id, handle.view_id)) {
+ if let Some(view) = self.views.get(&(handle.window, handle.view_id)) {
view.as_any().downcast_ref().expect("downcast is type safe")
} else {
panic!("circular view reference for type {}", type_name::());
@@ -1436,7 +1385,7 @@ impl AppContext {
fn upgrade_view_handle(&self, handle: &WeakViewHandle) -> Option> {
if self.ref_counts.lock().is_entity_alive(handle.view_id) {
Some(ViewHandle::new(
- handle.window_id,
+ handle.window,
handle.view_id,
&self.ref_counts,
))
@@ -1448,7 +1397,7 @@ impl AppContext {
fn upgrade_any_view_handle(&self, handle: &AnyWeakViewHandle) -> Option {
if self.ref_counts.lock().is_entity_alive(handle.view_id) {
Some(AnyViewHandle::new(
- handle.window_id,
+ handle.window,
handle.view_id,
handle.view_type,
self.ref_counts.clone(),
@@ -1478,13 +1427,13 @@ impl AppContext {
.push_back(Effect::ModelRelease { model_id, model });
}
- for (window_id, view_id) in dropped_views {
+ for (window, view_id) in dropped_views {
self.subscriptions.remove(view_id);
self.observations.remove(view_id);
- self.views_metadata.remove(&(window_id, view_id));
- let mut view = self.views.remove(&(window_id, view_id)).unwrap();
+ self.views_metadata.remove(&(window, view_id));
+ let mut view = self.views.remove(&(window, view_id)).unwrap();
view.release(self);
- if let Some(window) = self.windows.get_mut(&window_id) {
+ if let Some(window) = self.windows.get_mut(&window) {
window.parents.remove(&view_id);
window
.invalidation
@@ -1512,7 +1461,7 @@ impl AppContext {
let mut refreshing = false;
let mut updated_windows = HashSet::default();
- let mut focus_effects = HashMap::::default();
+ let mut focus_effects = HashMap::::default();
loop {
self.remove_dropped_entities();
if let Some(effect) = self.pending_effects.pop_front() {
@@ -1556,9 +1505,10 @@ impl AppContext {
observations.emit(model_id, |callback| callback(self));
}
- Effect::ViewNotification { window_id, view_id } => {
- self.handle_view_notification_effect(window_id, view_id)
- }
+ Effect::ViewNotification {
+ window: window_id,
+ view_id,
+ } => self.handle_view_notification_effect(window_id, view_id),
Effect::GlobalNotification { type_id } => {
let mut subscriptions = self.global_observations.clone();
@@ -1589,13 +1539,13 @@ impl AppContext {
Effect::Focus(mut effect) => {
if focus_effects
- .get(&effect.window_id())
+ .get(&effect.window())
.map_or(false, |prev_effect| prev_effect.is_forced())
{
effect.force();
}
- focus_effects.insert(effect.window_id(), effect);
+ focus_effects.insert(effect.window(), effect);
}
Effect::FocusObservation {
@@ -1610,42 +1560,38 @@ impl AppContext {
);
}
- Effect::ResizeWindow { window_id } => {
- if let Some(window) = self.windows.get_mut(&window_id) {
+ Effect::ResizeWindow { window } => {
+ if let Some(window) = self.windows.get_mut(&window) {
window
.invalidation
.get_or_insert(WindowInvalidation::default());
}
- self.handle_window_moved(window_id);
+ self.handle_window_moved(window);
}
- Effect::MoveWindow { window_id } => {
- self.handle_window_moved(window_id);
+ Effect::MoveWindow { window } => {
+ self.handle_window_moved(window);
}
Effect::WindowActivationObservation {
- window_id,
+ window,
subscription_id,
callback,
} => self.window_activation_observations.add_callback(
- window_id,
+ window,
subscription_id,
callback,
),
- Effect::ActivateWindow {
- window_id,
- is_active,
- } => {
- if self.handle_window_activation_effect(window_id, is_active)
- && is_active
+ Effect::ActivateWindow { window, is_active } => {
+ if self.handle_window_activation_effect(window, is_active) && is_active
{
focus_effects
- .entry(window_id)
+ .entry(window)
.or_insert_with(|| FocusEffect::View {
- window_id,
+ window,
view_id: self
- .read_window(window_id, |cx| cx.focused_view_id())
+ .read_window(window, |cx| cx.focused_view_id())
.flatten(),
is_forced: true,
})
@@ -1654,26 +1600,26 @@ impl AppContext {
}
Effect::WindowFullscreenObservation {
- window_id,
+ window,
subscription_id,
callback,
} => self.window_fullscreen_observations.add_callback(
- window_id,
+ window,
subscription_id,
callback,
),
Effect::FullscreenWindow {
- window_id,
+ window,
is_fullscreen,
- } => self.handle_fullscreen_effect(window_id, is_fullscreen),
+ } => self.handle_fullscreen_effect(window, is_fullscreen),
Effect::WindowBoundsObservation {
- window_id,
+ window,
subscription_id,
callback,
} => self.window_bounds_observations.add_callback(
- window_id,
+ window,
subscription_id,
callback,
),
@@ -1685,18 +1631,15 @@ impl AppContext {
Effect::ActionDispatchNotification { action_id } => {
self.handle_action_dispatch_notification_effect(action_id)
}
- Effect::WindowShouldCloseSubscription {
- window_id,
- callback,
- } => {
- self.handle_window_should_close_subscription_effect(window_id, callback)
+ Effect::WindowShouldCloseSubscription { window, callback } => {
+ self.handle_window_should_close_subscription_effect(window, callback)
}
Effect::Keystroke {
- window_id,
+ window,
keystroke,
handled_by,
result,
- } => self.handle_keystroke_effect(window_id, keystroke, handled_by, result),
+ } => self.handle_keystroke_effect(window, keystroke, handled_by, result),
Effect::ActiveLabeledTasksChanged => {
self.handle_active_labeled_tasks_changed_effect()
}
@@ -1711,8 +1654,8 @@ impl AppContext {
}
self.pending_notifications.clear();
} else {
- for window_id in self.windows.keys().cloned().collect::>() {
- self.update_window(window_id, |cx| {
+ for window in self.windows().collect::>() {
+ self.update_window(window, |cx| {
let invalidation = if refreshing {
let mut invalidation =
cx.window.invalidation.take().unwrap_or_default();
@@ -1728,7 +1671,7 @@ impl AppContext {
let appearance = cx.window.platform_window.appearance();
cx.invalidate(invalidation, appearance);
if let Some(old_parents) = cx.layout(refreshing).log_err() {
- updated_windows.insert(window_id);
+ updated_windows.insert(window);
if let Some(focused_view_id) = cx.focused_view_id() {
let old_ancestors = std::iter::successors(
@@ -1743,15 +1686,14 @@ impl AppContext {
for old_ancestor in old_ancestors.iter().copied() {
if !new_ancestors.contains(&old_ancestor) {
if let Some(mut view) =
- cx.views.remove(&(window_id, old_ancestor))
+ cx.views.remove(&(window, old_ancestor))
{
view.focus_out(
focused_view_id,
cx,
old_ancestor,
);
- cx.views
- .insert((window_id, old_ancestor), view);
+ cx.views.insert((window, old_ancestor), view);
}
}
}
@@ -1760,15 +1702,14 @@ impl AppContext {
for new_ancestor in new_ancestors.iter().copied() {
if !old_ancestors.contains(&new_ancestor) {
if let Some(mut view) =
- cx.views.remove(&(window_id, new_ancestor))
+ cx.views.remove(&(window, new_ancestor))
{
view.focus_in(
focused_view_id,
cx,
new_ancestor,
);
- cx.views
- .insert((window_id, new_ancestor), view);
+ cx.views.insert((window, new_ancestor), view);
}
}
}
@@ -1777,13 +1718,13 @@ impl AppContext {
// there isn't any pending focus, focus the root view.
let root_view_id = cx.window.root_view().id();
if focused_view_id != root_view_id
- && !cx.views.contains_key(&(window_id, focused_view_id))
- && !focus_effects.contains_key(&window_id)
+ && !cx.views.contains_key(&(window, focused_view_id))
+ && !focus_effects.contains_key(&window)
{
focus_effects.insert(
- window_id,
+ window,
FocusEffect::View {
- window_id,
+ window,
view_id: Some(root_view_id),
is_forced: false,
},
@@ -1804,8 +1745,8 @@ impl AppContext {
callback(self);
}
- for window_id in updated_windows.drain() {
- self.update_window(window_id, |cx| {
+ for window in updated_windows.drain() {
+ self.update_window(window, |cx| {
if let Some(scene) = cx.paint().log_err() {
cx.window.platform_window.present_scene(scene);
}
@@ -1826,39 +1767,37 @@ impl AppContext {
}
}
- fn window_was_resized(&mut self, window_id: usize) {
+ fn window_was_resized(&mut self, window: AnyWindowHandle) {
self.pending_effects
- .push_back(Effect::ResizeWindow { window_id });
+ .push_back(Effect::ResizeWindow { window });
}
- fn window_was_moved(&mut self, window_id: usize) {
+ fn window_was_moved(&mut self, window: AnyWindowHandle) {
self.pending_effects
- .push_back(Effect::MoveWindow { window_id });
+ .push_back(Effect::MoveWindow { window });
}
- fn window_was_fullscreen_changed(&mut self, window_id: usize, is_fullscreen: bool) {
+ fn window_was_fullscreen_changed(&mut self, window: AnyWindowHandle, is_fullscreen: bool) {
self.pending_effects.push_back(Effect::FullscreenWindow {
- window_id,
+ window,
is_fullscreen,
});
}
- fn window_changed_active_status(&mut self, window_id: usize, is_active: bool) {
- self.pending_effects.push_back(Effect::ActivateWindow {
- window_id,
- is_active,
- });
+ fn window_changed_active_status(&mut self, window: AnyWindowHandle, is_active: bool) {
+ self.pending_effects
+ .push_back(Effect::ActivateWindow { window, is_active });
}
fn keystroke(
&mut self,
- window_id: usize,
+ window: AnyWindowHandle,
keystroke: Keystroke,
handled_by: Option>,
result: MatchResult,
) {
self.pending_effects.push_back(Effect::Keystroke {
- window_id,
+ window,
keystroke,
handled_by,
result,
@@ -1881,16 +1820,16 @@ impl AppContext {
fn handle_view_notification_effect(
&mut self,
- observed_window_id: usize,
+ observed_window: AnyWindowHandle,
observed_view_id: usize,
) {
- let view_key = (observed_window_id, observed_view_id);
+ let view_key = (observed_window, observed_view_id);
if let Some((view, mut view_metadata)) = self
.views
.remove(&view_key)
.zip(self.views_metadata.remove(&view_key))
{
- if let Some(window) = self.windows.get_mut(&observed_window_id) {
+ if let Some(window) = self.windows.get_mut(&observed_window) {
window
.invalidation
.get_or_insert_with(Default::default)
@@ -1917,17 +1856,17 @@ impl AppContext {
})
}
- fn handle_fullscreen_effect(&mut self, window_id: usize, is_fullscreen: bool) {
- self.update_window(window_id, |cx| {
+ fn handle_fullscreen_effect(&mut self, window: AnyWindowHandle, is_fullscreen: bool) {
+ self.update_window(window, |cx| {
cx.window.is_fullscreen = is_fullscreen;
let mut fullscreen_observations = cx.window_fullscreen_observations.clone();
- fullscreen_observations.emit(window_id, |callback| callback(is_fullscreen, cx));
+ fullscreen_observations.emit(window, |callback| callback(is_fullscreen, cx));
if let Some(uuid) = cx.window_display_uuid() {
let bounds = cx.window_bounds();
let mut bounds_observations = cx.window_bounds_observations.clone();
- bounds_observations.emit(window_id, |callback| callback(bounds, uuid, cx));
+ bounds_observations.emit(window, |callback| callback(bounds, uuid, cx));
}
Some(())
@@ -1936,42 +1875,42 @@ impl AppContext {
fn handle_keystroke_effect(
&mut self,
- window_id: usize,
+ window: AnyWindowHandle,
keystroke: Keystroke,
handled_by: Option>,
result: MatchResult,
) {
- self.update_window(window_id, |cx| {
+ self.update_window(window, |cx| {
let mut observations = cx.keystroke_observations.clone();
- observations.emit(window_id, move |callback| {
+ observations.emit(window, move |callback| {
callback(&keystroke, &result, handled_by.as_ref(), cx)
});
});
}
- fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) -> bool {
- self.update_window(window_id, |cx| {
+ fn handle_window_activation_effect(&mut self, window: AnyWindowHandle, active: bool) -> bool {
+ self.update_window(window, |cx| {
if cx.window.is_active == active {
return false;
}
cx.window.is_active = active;
let mut observations = cx.window_activation_observations.clone();
- observations.emit(window_id, |callback| callback(active, cx));
+ observations.emit(window, |callback| callback(active, cx));
true
})
.unwrap_or(false)
}
fn handle_focus_effect(&mut self, effect: FocusEffect) {
- let window_id = effect.window_id();
- self.update_window(window_id, |cx| {
+ let window = effect.window();
+ self.update_window(window, |cx| {
// Ensure the newly-focused view still exists, otherwise focus
// the root view instead.
let focused_id = match effect {
FocusEffect::View { view_id, .. } => {
if let Some(view_id) = view_id {
- if cx.views.contains_key(&(window_id, view_id)) {
+ if cx.views.contains_key(&(window, view_id)) {
Some(view_id)
} else {
Some(cx.root_view().id())
@@ -1996,9 +1935,9 @@ impl AppContext {
if focus_changed {
if let Some(blurred_id) = blurred_id {
for view_id in cx.ancestors(blurred_id).collect::>() {
- if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
+ if let Some(mut view) = cx.views.remove(&(window, view_id)) {
view.focus_out(blurred_id, cx, view_id);
- cx.views.insert((window_id, view_id), view);
+ cx.views.insert((window, view_id), view);
}
}
@@ -2010,9 +1949,9 @@ impl AppContext {
if focus_changed || effect.is_forced() {
if let Some(focused_id) = focused_id {
for view_id in cx.ancestors(focused_id).collect::>() {
- if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
+ if let Some(mut view) = cx.views.remove(&(window, view_id)) {
view.focus_in(focused_id, cx, view_id);
- cx.views.insert((window_id, view_id), view);
+ cx.views.insert((window, view_id), view);
}
}
@@ -2034,24 +1973,24 @@ impl AppContext {
fn handle_window_should_close_subscription_effect(
&mut self,
- window_id: usize,
+ window: AnyWindowHandle,
mut callback: WindowShouldCloseSubscriptionCallback,
) {
let mut app = self.upgrade();
- if let Some(window) = self.windows.get_mut(&window_id) {
+ if let Some(window) = self.windows.get_mut(&window) {
window
.platform_window
.on_should_close(Box::new(move || app.update(|cx| callback(cx))))
}
}
- fn handle_window_moved(&mut self, window_id: usize) {
- self.update_window(window_id, |cx| {
+ fn handle_window_moved(&mut self, window: AnyWindowHandle) {
+ self.update_window(window, |cx| {
if let Some(display) = cx.window_display_uuid() {
let bounds = cx.window_bounds();
cx.window_bounds_observations
.clone()
- .emit(window_id, move |callback| {
+ .emit(window, move |callback| {
callback(bounds, display, cx);
true
});
@@ -2068,10 +2007,10 @@ impl AppContext {
});
}
- pub fn focus(&mut self, window_id: usize, view_id: Option) {
+ pub fn focus(&mut self, window: AnyWindowHandle, view_id: Option) {
self.pending_effects
.push_back(Effect::Focus(FocusEffect::View {
- window_id,
+ window,
view_id,
is_forced: false,
}));
@@ -2153,6 +2092,46 @@ impl BorrowAppContext for AppContext {
}
}
+impl BorrowWindowContext for AppContext {
+ type Result = Option;
+
+ fn read_window(&self, window: AnyWindowHandle, f: F) -> Self::Result
+ where
+ F: FnOnce(&WindowContext) -> T,
+ {
+ AppContext::read_window(self, window, f)
+ }
+
+ fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option
+ where
+ F: FnOnce(&WindowContext) -> Option,
+ {
+ AppContext::read_window(self, window, f).flatten()
+ }
+
+ fn update_window(&mut self, handle: AnyWindowHandle, f: F) -> Self::Result
+ where
+ F: FnOnce(&mut WindowContext) -> T,
+ {
+ self.update(|cx| {
+ let mut window = cx.windows.remove(&handle)?;
+ let mut window_context = WindowContext::mutable(cx, &mut window, handle);
+ let result = f(&mut window_context);
+ if !window_context.removed {
+ cx.windows.insert(handle, window);
+ }
+ Some(result)
+ })
+ }
+
+ fn update_window_optional(&mut self, handle: AnyWindowHandle, f: F) -> Option
+ where
+ F: FnOnce(&mut WindowContext) -> Option,
+ {
+ AppContext::update_window(self, handle, f).flatten()
+ }
+}
+
#[derive(Debug)]
pub enum ParentId {
View(usize),
@@ -2173,22 +2152,22 @@ pub struct WindowInvalidation {
#[derive(Debug)]
pub enum FocusEffect {
View {
- window_id: usize,
+ window: AnyWindowHandle,
view_id: Option,
is_forced: bool,
},
ViewParent {
- window_id: usize,
+ window: AnyWindowHandle,
view_id: usize,
is_forced: bool,
},
}
impl FocusEffect {
- fn window_id(&self) -> usize {
+ fn window(&self) -> AnyWindowHandle {
match self {
- FocusEffect::View { window_id, .. } => *window_id,
- FocusEffect::ViewParent { window_id, .. } => *window_id,
+ FocusEffect::View { window, .. } => *window,
+ FocusEffect::ViewParent { window, .. } => *window,
}
}
@@ -2234,7 +2213,7 @@ pub enum Effect {
model_id: usize,
},
ViewNotification {
- window_id: usize,
+ window: AnyWindowHandle,
view_id: usize,
},
Deferred {
@@ -2259,36 +2238,36 @@ pub enum Effect {
callback: FocusObservationCallback,
},
ResizeWindow {
- window_id: usize,
+ window: AnyWindowHandle,
},
MoveWindow {
- window_id: usize,
+ window: AnyWindowHandle,
},
ActivateWindow {
- window_id: usize,
+ window: AnyWindowHandle,
is_active: bool,
},
WindowActivationObservation {
- window_id: usize,
+ window: AnyWindowHandle,
subscription_id: usize,
callback: WindowActivationCallback,
},
FullscreenWindow {
- window_id: usize,
+ window: AnyWindowHandle,
is_fullscreen: bool,
},
WindowFullscreenObservation {
- window_id: usize,
+ window: AnyWindowHandle,
subscription_id: usize,
callback: WindowFullscreenCallback,
},
WindowBoundsObservation {
- window_id: usize,
+ window: AnyWindowHandle,
subscription_id: usize,
callback: WindowBoundsCallback,
},
Keystroke {
- window_id: usize,
+ window: AnyWindowHandle,
keystroke: Keystroke,
handled_by: Option>,
result: MatchResult,
@@ -2298,7 +2277,7 @@ pub enum Effect {
action_id: TypeId,
},
WindowShouldCloseSubscription {
- window_id: usize,
+ window: AnyWindowHandle,
callback: WindowShouldCloseSubscriptionCallback,
},
ActiveLabeledTasksChanged,
@@ -2350,9 +2329,9 @@ impl Debug for Effect {
.debug_struct("Effect::ModelNotification")
.field("model_id", model_id)
.finish(),
- Effect::ViewNotification { window_id, view_id } => f
+ Effect::ViewNotification { window, view_id } => f
.debug_struct("Effect::ViewNotification")
- .field("window_id", window_id)
+ .field("window_id", &window.id())
.field("view_id", view_id)
.finish(),
Effect::GlobalNotification { type_id } => f
@@ -2382,71 +2361,68 @@ impl Debug for Effect {
.debug_struct("Effect::ActionDispatchNotification")
.field("action_id", action_id)
.finish(),
- Effect::ResizeWindow { window_id } => f
+ Effect::ResizeWindow { window } => f
.debug_struct("Effect::RefreshWindow")
- .field("window_id", window_id)
+ .field("window_id", &window.id())
.finish(),
- Effect::MoveWindow { window_id } => f
+ Effect::MoveWindow { window } => f
.debug_struct("Effect::MoveWindow")
- .field("window_id", window_id)
+ .field("window_id", &window.id())
.finish(),
Effect::WindowActivationObservation {
- window_id,
+ window,
subscription_id,
..
} => f
.debug_struct("Effect::WindowActivationObservation")
- .field("window_id", window_id)
+ .field("window_id", &window.id())
.field("subscription_id", subscription_id)
.finish(),
- Effect::ActivateWindow {
- window_id,
- is_active,
- } => f
+ Effect::ActivateWindow { window, is_active } => f
.debug_struct("Effect::ActivateWindow")
- .field("window_id", window_id)
+ .field("window_id", &window.id())
.field("is_active", is_active)
.finish(),
Effect::FullscreenWindow {
- window_id,
+ window,
is_fullscreen,
} => f
.debug_struct("Effect::FullscreenWindow")
- .field("window_id", window_id)
+ .field("window_id", &window.id())
.field("is_fullscreen", is_fullscreen)
.finish(),
Effect::WindowFullscreenObservation {
- window_id,
+ window,
subscription_id,
callback: _,
} => f
.debug_struct("Effect::WindowFullscreenObservation")
- .field("window_id", window_id)
+ .field("window_id", &window.id())
.field("subscription_id", subscription_id)
.finish(),
Effect::WindowBoundsObservation {
- window_id,
+ window,
subscription_id,
callback: _,
} => f
.debug_struct("Effect::WindowBoundsObservation")
- .field("window_id", window_id)
+ .field("window_id", &window.id())
.field("subscription_id", subscription_id)
.finish(),
Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(),
- Effect::WindowShouldCloseSubscription { window_id, .. } => f
+ Effect::WindowShouldCloseSubscription { window, .. } => f
.debug_struct("Effect::WindowShouldCloseSubscription")
- .field("window_id", window_id)
+ .field("window_id", &window.id())
.finish(),
Effect::Keystroke {
- window_id,
+ window,
keystroke,
handled_by,
result,
} => f
.debug_struct("Effect::Keystroke")
- .field("window_id", window_id)
+ .field("window_id", &window.id())
.field("keystroke", keystroke)
.field(
"keystroke",
@@ -2544,9 +2520,14 @@ pub trait AnyView {
cx: &mut WindowContext,
view_id: usize,
);
- fn any_handle(&self, window_id: usize, view_id: usize, cx: &AppContext) -> AnyViewHandle {
+ fn any_handle(
+ &self,
+ window: AnyWindowHandle,
+ view_id: usize,
+ cx: &AppContext,
+ ) -> AnyViewHandle {
AnyViewHandle::new(
- window_id,
+ window,
view_id,
self.as_any().type_id(),
cx.ref_counts.clone(),
@@ -2584,7 +2565,7 @@ where
fn render(&mut self, cx: &mut WindowContext, view_id: usize) -> Box {
let mut view_context = ViewContext::mutable(cx, view_id);
let element = V::render(self, &mut view_context);
- let view = WeakViewHandle::new(cx.window_id, view_id);
+ let view = WeakViewHandle::new(cx.window_handle, view_id);
Box::new(RootElement::new(element, view))
}
@@ -2595,11 +2576,11 @@ where
} else {
let focused_type = cx
.views_metadata
- .get(&(cx.window_id, focused_id))
+ .get(&(cx.window_handle, focused_id))
.unwrap()
.type_id;
AnyViewHandle::new(
- cx.window_id,
+ cx.window_handle,
focused_id,
focused_type,
cx.ref_counts.clone(),
@@ -2615,11 +2596,11 @@ where
} else {
let blurred_type = cx
.views_metadata
- .get(&(cx.window_id, blurred_id))
+ .get(&(cx.window_handle, blurred_id))
.unwrap()
.type_id;
AnyViewHandle::new(
- cx.window_id,
+ cx.window_handle,
blurred_id,
blurred_type,
cx.ref_counts.clone(),
@@ -2926,18 +2907,18 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
pub fn handle(&self) -> ViewHandle {
ViewHandle::new(
- self.window_id,
+ self.window_handle,
self.view_id,
&self.window_context.ref_counts,
)
}
pub fn weak_handle(&self) -> WeakViewHandle {
- WeakViewHandle::new(self.window_id, self.view_id)
+ WeakViewHandle::new(self.window_handle, self.view_id)
}
- pub fn window_id(&self) -> usize {
- self.window_id
+ pub fn window(&self) -> AnyWindowHandle {
+ self.window_handle
}
pub fn view_id(&self) -> usize {
@@ -2985,11 +2966,11 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
}
pub fn focus_parent(&mut self) {
- let window_id = self.window_id;
+ let window = self.window_handle;
let view_id = self.view_id;
self.pending_effects
.push_back(Effect::Focus(FocusEffect::ViewParent {
- window_id,
+ window,
view_id,
is_forced: false,
}));
@@ -3003,13 +2984,13 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
where
F: 'static + FnMut(&mut V, &mut ViewContext) -> bool,
{
- let window_id = self.window_id;
+ let window = self.window_handle;
let view = self.weak_handle();
self.pending_effects
.push_back(Effect::WindowShouldCloseSubscription {
- window_id,
+ window,
callback: Box::new(move |cx| {
- cx.update_window(window_id, |cx| {
+ cx.update_window(window, |cx| {
if let Some(view) = view.upgrade(cx) {
view.update(cx, |view, cx| callback(view, cx))
} else {
@@ -3048,11 +3029,11 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
H: Handle,
F: 'static + FnMut(&mut V, H, &mut ViewContext),
{
- let window_id = self.window_id;
+ let window = self.window_handle;
let observer = self.weak_handle();
self.window_context
.observe_internal(handle, move |observed, cx| {
- cx.update_window(window_id, |cx| {
+ cx.update_window(window, |cx| {
if let Some(observer) = observer.upgrade(cx) {
observer.update(cx, |observer, cx| {
callback(observer, observed, cx);
@@ -3071,10 +3052,10 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
G: Any,
F: 'static + FnMut(&mut V, &mut ViewContext),
{
- let window_id = self.window_id;
+ let window = self.window_handle;
let observer = self.weak_handle();
self.window_context.observe_global::(move |cx| {
- cx.update_window(window_id, |cx| {
+ cx.update_window(window, |cx| {
if let Some(observer) = observer.upgrade(cx) {
observer.update(cx, |observer, cx| callback(observer, cx));
}
@@ -3107,11 +3088,11 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
H: Handle,
F: 'static + FnMut(&mut V, &E, &mut ViewContext),
{
- let window_id = self.window_id;
+ let window = self.window_handle;
let observer = self.weak_handle();
self.window_context
.observe_release(handle, move |released, cx| {
- cx.update_window(window_id, |cx| {
+ cx.update_window(window, |cx| {
if let Some(observer) = observer.upgrade(cx) {
observer.update(cx, |observer, cx| {
callback(observer, released, cx);
@@ -3125,10 +3106,10 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
where
F: 'static + FnMut(&mut V, TypeId, &mut ViewContext),
{
- let window_id = self.window_id;
+ let window = self.window_handle;
let observer = self.weak_handle();
self.window_context.observe_actions(move |action_id, cx| {
- cx.update_window(window_id, |cx| {
+ cx.update_window(window, |cx| {
if let Some(observer) = observer.upgrade(cx) {
observer.update(cx, |observer, cx| {
callback(observer, action_id, cx);
@@ -3220,10 +3201,10 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
where
F: 'static + FnMut(&mut V, &mut ViewContext),
{
- let window_id = self.window_id;
+ let window = self.window_handle;
let observer = self.weak_handle();
self.window_context.observe_active_labeled_tasks(move |cx| {
- cx.update_window(window_id, |cx| {
+ cx.update_window(window, |cx| {
if let Some(observer) = observer.upgrade(cx) {
observer.update(cx, |observer, cx| {
callback(observer, cx);
@@ -3247,9 +3228,9 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
}
pub fn notify(&mut self) {
- let window_id = self.window_id;
+ let window = self.window_handle;
let view_id = self.view_id;
- self.window_context.notify_view(window_id, view_id);
+ self.window_context.notify_view(window, view_id);
}
pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut V, &mut ViewContext)) {
@@ -3262,10 +3243,10 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
&mut self,
callback: impl 'static + FnOnce(&mut V, &mut ViewContext),
) {
- let window_id = self.window_id;
+ let window = self.window_handle;
let handle = self.handle();
self.window_context.after_window_update(move |cx| {
- cx.update_window(window_id, |cx| {
+ cx.update_window(window, |cx| {
handle.update(cx, |view, cx| {
callback(view, cx);
})
@@ -3351,12 +3332,32 @@ impl BorrowAppContext for ViewContext<'_, '_, V> {
}
impl BorrowWindowContext for ViewContext<'_, '_, V> {
- fn read_with T>(&self, window_id: usize, f: F) -> T {
- BorrowWindowContext::read_with(&*self.window_context, window_id, f)
+ type Result = T;
+
+ fn read_window T>(&self, window: AnyWindowHandle, f: F) -> T {
+ BorrowWindowContext::read_window(&*self.window_context, window, f)
}
- fn update T>(&mut self, window_id: usize, f: F) -> T {
- BorrowWindowContext::update(&mut *self.window_context, window_id, f)
+ fn read_window_optional(&self, window: AnyWindowHandle, f: F) -> Option
+ where
+ F: FnOnce(&WindowContext) -> Option,
+ {
+ BorrowWindowContext::read_window_optional(&*self.window_context, window, f)
+ }
+
+ fn update_window T>(
+ &mut self,
+ window: AnyWindowHandle,
+ f: F,
+ ) -> T {
+ BorrowWindowContext::update_window(&mut *self.window_context, window, f)
+ }
+
+ fn update_window_optional(&mut self, window: AnyWindowHandle, f: F) -> Option
+ where
+ F: FnOnce(&mut WindowContext) -> Option,
+ {
+ BorrowWindowContext::update_window_optional(&mut *self.window_context, window, f)
}
}
@@ -3396,11 +3397,11 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
) -> Option> {
self.notify_if_view_ancestors_change(view_id);
- let window_id = self.window_id;
+ let window = self.window_handle;
let mut contexts = Vec::new();
let mut handler_depth = None;
for (i, view_id) in self.ancestors(view_id).enumerate() {
- if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
+ if let Some(view_metadata) = self.views_metadata.get(&(window, view_id)) {
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
if actions.contains_key(&action.id()) {
handler_depth = Some(i);
@@ -3476,12 +3477,32 @@ impl