Improve the ergonomics of creating local buffers (#10347)

This PR renames `language::Buffer::new` to `language::Buffer::local` and
simplifies its interface. Instead of taking a replica id (which should
always be 0 for the local case) and a `BufferId`, which was awkward and
verbose to construct, it simply takes text and a `cx`.

It uses the `cx` to derive a `BufferId` from the `EntityId` associated
with the `cx`, which should always be positive based on the following
analysis...

We convert the entity id to a u64 using this method on `EntityId`, which
is defined by macros in the `slotmap` crate:

```rust
    pub fn as_ffi(self) -> u64 {
        (u64::from(self.version.get()) << 32) | u64::from(self.idx)
    }
```

If you look at the type of `version` in `KeyData`, it is non-zero:

```rust
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct KeyData {
    idx: u32,
    version: NonZeroU32,
}
```

This commit also adds `Context::reserve_model` and
`Context::insert_model` to determine a model's entity ID before it is
created, which we need in order to assign a `BufferId` in the background
when loading a buffer asynchronously.

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
Nathan Sobo 2024-04-10 07:32:51 -07:00 committed by GitHub
parent 664efef76b
commit 7abb63cfda
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 376 additions and 519 deletions

View file

@ -503,9 +503,9 @@ pub enum CharKind {
impl Buffer {
/// Create a new buffer with the given base text.
pub fn new<T: Into<String>>(replica_id: ReplicaId, id: BufferId, base_text: T) -> Self {
pub fn local<T: Into<String>>(base_text: T, cx: &mut ModelContext<Self>) -> Self {
Self::build(
TextBuffer::new(replica_id, id, base_text.into()),
TextBuffer::new(0, cx.entity_id().as_non_zero_u64().into(), base_text.into()),
None,
None,
Capability::ReadWrite,
@ -517,10 +517,10 @@ impl Buffer {
remote_id: BufferId,
replica_id: ReplicaId,
capability: Capability,
base_text: String,
base_text: impl Into<String>,
) -> Self {
Self::build(
TextBuffer::new(replica_id, remote_id, base_text),
TextBuffer::new(replica_id, remote_id, base_text.into()),
None,
None,
capability,

View file

@ -44,12 +44,8 @@ fn test_line_endings(cx: &mut gpui::AppContext) {
init_settings(cx, |_| {});
cx.new_model(|cx| {
let mut buffer = Buffer::new(
0,
BufferId::new(cx.entity_id().as_u64()).unwrap(),
"one\r\ntwo\rthree",
)
.with_language(Arc::new(rust_lang()), cx);
let mut buffer =
Buffer::local("one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx);
assert_eq!(buffer.text(), "one\ntwo\nthree");
assert_eq!(buffer.line_ending(), LineEnding::Windows);
@ -256,10 +252,15 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
let buffer1 = cx
.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcdef"));
let buffer2 = cx
.new_model(|cx| Buffer::new(1, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcdef"));
let buffer1 = cx.new_model(|cx| Buffer::local("abcdef", cx));
let buffer2 = cx.new_model(|cx| {
Buffer::remote(
BufferId::from(cx.entity_id().as_non_zero_u64()),
1,
Capability::ReadWrite,
"abcdef",
)
});
let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
buffer1.update(cx, {
let buffer1_ops = buffer1_ops.clone();
@ -338,8 +339,7 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
#[gpui::test]
async fn test_apply_diff(cx: &mut TestAppContext) {
let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
let buffer =
cx.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text));
let buffer = cx.new_model(|cx| Buffer::local(text, cx));
let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
let text = "a\nccc\ndddd\nffffff\n";
@ -371,8 +371,7 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
]
.join("\n");
let buffer =
cx.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text));
let buffer = cx.new_model(|cx| Buffer::local(text, cx));
// Spawn a task to format the buffer's whitespace.
// Pause so that the foratting task starts running.
@ -436,10 +435,8 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
#[gpui::test]
async fn test_reparse(cx: &mut gpui::TestAppContext) {
let text = "fn a() {}";
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
// Wait for the initial text to parse
cx.executor().run_until_parked();
@ -566,8 +563,7 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
#[gpui::test]
async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "{}")
.with_language(Arc::new(rust_lang()), cx);
let mut buffer = Buffer::local("{}", cx).with_language(Arc::new(rust_lang()), cx);
buffer.set_sync_parse_timeout(Duration::ZERO);
buffer
});
@ -615,10 +611,8 @@ async fn test_outline(cx: &mut gpui::TestAppContext) {
"#
.unindent();
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let outline = buffer
.update(cx, |buffer, _| buffer.snapshot().outline(None))
.unwrap();
@ -702,10 +696,8 @@ async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
"#
.unindent();
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let outline = buffer
.update(cx, |buffer, _| buffer.snapshot().outline(None))
.unwrap();
@ -741,10 +733,7 @@ async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
"#
.unindent();
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(language), cx)
});
let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(language), cx));
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
// extra context nodes are included in the outline.
@ -786,10 +775,8 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
"#
.unindent();
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
// point is at the start of an item
@ -1010,8 +997,7 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &
fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
cx.new_model(|cx| {
let text = "fn a() { b(|c| {}) }";
let buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx);
let buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
let snapshot = buffer.snapshot();
assert_eq!(
@ -1051,8 +1037,7 @@ fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
cx.new_model(|cx| {
let text = "fn a() {}";
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx);
let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "fn a() {\n \n}");
@ -1094,8 +1079,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
cx.new_model(|cx| {
let text = "fn a() {}";
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx);
let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "fn a() {\n\t\n}");
@ -1134,9 +1118,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
init_settings(cx, |_| {});
cx.new_model(|cx| {
let mut buffer = Buffer::new(
0,
BufferId::new(cx.entity_id().as_u64()).unwrap(),
let mut buffer = Buffer::local(
"
fn a() {
c;
@ -1144,6 +1126,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
}
"
.unindent(),
cx,
)
.with_language(Arc::new(rust_lang()), cx);
@ -1209,9 +1192,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
cx.new_model(|cx| {
eprintln!("second buffer: {:?}", cx.entity_id());
let mut buffer = Buffer::new(
0,
BufferId::new(cx.entity_id().as_u64()).unwrap(),
let mut buffer = Buffer::local(
"
fn a() {
b();
@ -1219,6 +1200,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
"
.replace('|', "") // marker to preserve trailing whitespace
.unindent(),
cx,
)
.with_language(Arc::new(rust_lang()), cx);
@ -1274,15 +1256,14 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap
init_settings(cx, |_| {});
cx.new_model(|cx| {
let mut buffer = Buffer::new(
0,
BufferId::new(cx.entity_id().as_u64()).unwrap(),
let mut buffer = Buffer::local(
"
fn a() {
i
}
"
.unindent(),
cx,
)
.with_language(Arc::new(rust_lang()), cx);
@ -1336,13 +1317,12 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.new_model(|cx| {
let mut buffer = Buffer::new(
0,
BufferId::new(cx.entity_id().as_u64()).unwrap(),
let mut buffer = Buffer::local(
"
fn a() {}
"
.unindent(),
cx,
)
.with_language(Arc::new(rust_lang()), cx);
@ -1394,8 +1374,7 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
cx.new_model(|cx| {
let text = "a\nb";
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx);
let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit(
[(0..1, "\n"), (2..3, "\n")],
Some(AutoindentMode::EachLine),
@ -1421,8 +1400,7 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
"
.unindent();
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx);
let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit(
[(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
Some(AutoindentMode::EachLine),
@ -1459,8 +1437,7 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
}
"#
.unindent();
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx);
let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
// When this text was copied, both of the quotation marks were at the same
// indent level, but the indentation of the first line was not included in
@ -1545,8 +1522,7 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
}
"#
.unindent();
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(Arc::new(rust_lang()), cx);
let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
// The original indent columns are not known, so this text is
// auto-indented in a block as if the first line was copied in
@ -1625,18 +1601,17 @@ fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
"
.unindent();
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
.with_language(
Arc::new(Language::new(
LanguageConfig {
name: "Markdown".into(),
auto_indent_using_last_non_empty_line: false,
..Default::default()
},
Some(tree_sitter_json::language()),
)),
cx,
);
let mut buffer = Buffer::local(text, cx).with_language(
Arc::new(Language::new(
LanguageConfig {
name: "Markdown".into(),
auto_indent_using_last_non_empty_line: false,
..Default::default()
},
Some(tree_sitter_json::language()),
)),
cx,
);
buffer.edit(
[(Point::new(3, 0)..Point::new(3, 0), "\n")],
Some(AutoindentMode::EachLine),
@ -1702,7 +1677,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
false,
);
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text);
let mut buffer = Buffer::local(text, cx);
buffer.set_language_registry(language_registry);
buffer.set_language(Some(html_language), cx);
buffer.edit(
@ -1738,8 +1713,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
});
cx.new_model(|cx| {
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
.with_language(Arc::new(ruby_lang()), cx);
let mut buffer = Buffer::local("", cx).with_language(Arc::new(ruby_lang()), cx);
let text = r#"
class C
@ -1840,8 +1814,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
"#
.unindent();
let buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), &text)
.with_language(Arc::new(language), cx);
let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
let snapshot = buffer.snapshot();
let config = snapshot.language_scope_at(0).unwrap();
@ -1958,12 +1931,7 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) {
"#
.unindent();
let buffer = Buffer::new(
0,
BufferId::new(cx.entity_id().as_u64()).unwrap(),
text.clone(),
)
.with_language(Arc::new(language), cx);
let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
let snapshot = buffer.snapshot();
// By default, all brackets are enabled
@ -2007,7 +1975,7 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
language_registry.add(Arc::new(html_lang()));
language_registry.add(Arc::new(erb_lang()));
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text);
let mut buffer = Buffer::local(text, cx);
buffer.set_language_registry(language_registry.clone());
buffer.set_language(
language_registry
@ -2042,7 +2010,7 @@ fn test_serialization(cx: &mut gpui::AppContext) {
let mut now = Instant::now();
let buffer1 = cx.new_model(|cx| {
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abc");
let mut buffer = Buffer::local("abc", cx);
buffer.edit([(3..3, "D")], None, cx);
now += Duration::from_secs(1);
@ -2097,13 +2065,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
let mut replica_ids = Vec::new();
let mut buffers = Vec::new();
let network = Arc::new(Mutex::new(Network::new(rng.clone())));
let base_buffer = cx.new_model(|cx| {
Buffer::new(
0,
BufferId::new(cx.entity_id().as_u64()).unwrap(),
base_text.as_str(),
)
});
let base_buffer = cx.new_model(|cx| Buffer::local(base_text.as_str(), cx));
for i in 0..rng.gen_range(min_peers..=max_peers) {
let buffer = cx.new_model(|cx| {
@ -2623,12 +2585,7 @@ fn assert_bracket_pairs(
) {
let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
let buffer = cx.new_model(|cx| {
Buffer::new(
0,
BufferId::new(cx.entity_id().as_u64()).unwrap(),
expected_text.clone(),
)
.with_language(Arc::new(language), cx)
Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx)
});
let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());