multi_buffer: Typed MultiBufferOffset (#42707)
This PR introduces a new `MultiBufferOffset` new type wrapping size. The goal of this is to make it clear at the type level when we are interacting with offsets of a multi buffer versus offsets of a language / text buffer. This improves readability of things quite a bit by making it clear what kind of offsets one is working with while also reducing accidental bugs by using the wrong kin of offset for the wrong API. This PR also uncovered two minor bugs due to that. Does not yet introduce the MultiBufferPoint equivalent, that is for a follow up PR. Release Notes: - N/A *or* Added/Fixed/Improved ...
This commit is contained in:
@@ -13,7 +13,7 @@ use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
Addon, Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
|
||||
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, Inlay,
|
||||
MultiBuffer, ToOffset,
|
||||
MultiBuffer, MultiBufferOffset, ToOffset,
|
||||
actions::Paste,
|
||||
code_context_menus::CodeContextMenu,
|
||||
display_map::{Crease, CreaseId, FoldId},
|
||||
@@ -209,7 +209,7 @@ impl MessageEditor {
|
||||
let acp::AvailableCommandInput::Unstructured { mut hint } =
|
||||
available_command.input.clone()?;
|
||||
|
||||
let mut hint_pos = parsed_command.source_range.end + 1;
|
||||
let mut hint_pos = MultiBufferOffset(parsed_command.source_range.end) + 1usize;
|
||||
if hint_pos > snapshot.len() {
|
||||
hint_pos = snapshot.len();
|
||||
hint.insert(0, ' ');
|
||||
@@ -307,9 +307,9 @@ impl MessageEditor {
|
||||
return Task::ready(());
|
||||
};
|
||||
let excerpt_id = start_anchor.excerpt_id;
|
||||
let end_anchor = snapshot
|
||||
.buffer_snapshot()
|
||||
.anchor_before(start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1);
|
||||
let end_anchor = snapshot.buffer_snapshot().anchor_before(
|
||||
start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1usize,
|
||||
);
|
||||
|
||||
let crease = if let MentionUri::File { abs_path } = &mention_uri
|
||||
&& let Some(extension) = abs_path.extension()
|
||||
@@ -739,8 +739,8 @@ impl MessageEditor {
|
||||
};
|
||||
|
||||
let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot());
|
||||
if crease_range.start > ix {
|
||||
let chunk = text[ix..crease_range.start].into();
|
||||
if crease_range.start.0 > ix {
|
||||
let chunk = text[ix..crease_range.start.0].into();
|
||||
chunks.push(chunk);
|
||||
}
|
||||
let chunk = match mention {
|
||||
@@ -808,7 +808,7 @@ impl MessageEditor {
|
||||
}),
|
||||
};
|
||||
chunks.push(chunk);
|
||||
ix = crease_range.end;
|
||||
ix = crease_range.end.0;
|
||||
}
|
||||
|
||||
if ix < text.len() {
|
||||
@@ -862,7 +862,7 @@ impl MessageEditor {
|
||||
let snapshot = editor.display_snapshot(cx);
|
||||
let cursor = editor.selections.newest::<text::Point>(&snapshot).head();
|
||||
let offset = cursor.to_offset(&snapshot);
|
||||
if offset > 0 {
|
||||
if offset.0 > 0 {
|
||||
snapshot
|
||||
.buffer_snapshot()
|
||||
.reversed_chars_at(offset)
|
||||
@@ -1132,7 +1132,7 @@ impl MessageEditor {
|
||||
let cursor_anchor = editor.selections.newest_anchor().head();
|
||||
let cursor_offset = cursor_anchor.to_offset(&editor_buffer.snapshot(cx));
|
||||
let anchor = buffer.update(cx, |buffer, _cx| {
|
||||
buffer.anchor_before(cursor_offset.min(buffer.len()))
|
||||
buffer.anchor_before(cursor_offset.0.min(buffer.len()))
|
||||
});
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
@@ -1258,7 +1258,7 @@ impl MessageEditor {
|
||||
});
|
||||
|
||||
for (range, mention_uri, mention) in mentions {
|
||||
let anchor = snapshot.anchor_before(range.start);
|
||||
let anchor = snapshot.anchor_before(MultiBufferOffset(range.start));
|
||||
let Some((crease_id, tx)) = insert_crease_for_mention(
|
||||
anchor.excerpt_id,
|
||||
anchor.text_anchor,
|
||||
@@ -1713,7 +1713,7 @@ mod tests {
|
||||
use agent::{HistoryStore, outline};
|
||||
use agent_client_protocol as acp;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use editor::{AnchorRangeExt as _, Editor, EditorMode};
|
||||
use editor::{AnchorRangeExt as _, Editor, EditorMode, MultiBufferOffset};
|
||||
use fs::FakeFs;
|
||||
use futures::StreamExt as _;
|
||||
use gpui::{
|
||||
@@ -2682,7 +2682,7 @@ mod tests {
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map
|
||||
.snapshot(cx)
|
||||
.folds_in_range(0..snapshot.len())
|
||||
.folds_in_range(MultiBufferOffset(0)..snapshot.len())
|
||||
.map(|fold| fold.range.to_point(&snapshot))
|
||||
.collect()
|
||||
})
|
||||
|
||||
@@ -6066,6 +6066,7 @@ pub(crate) mod tests {
|
||||
use acp_thread::StubAgentConnection;
|
||||
use agent_client_protocol::SessionId;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use editor::MultiBufferOffset;
|
||||
use fs::FakeFs;
|
||||
use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext};
|
||||
use project::Project;
|
||||
@@ -7234,7 +7235,7 @@ pub(crate) mod tests {
|
||||
Editor::for_buffer(buffer.clone(), Some(project.clone()), window, cx);
|
||||
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_ranges([8..15]);
|
||||
selections.select_ranges([MultiBufferOffset(8)..MultiBufferOffset(15)]);
|
||||
});
|
||||
|
||||
editor
|
||||
@@ -7296,7 +7297,7 @@ pub(crate) mod tests {
|
||||
Editor::for_buffer(buffer.clone(), Some(project.clone()), window, cx);
|
||||
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_ranges([8..15]);
|
||||
selections.select_ranges([MultiBufferOffset(8)..MultiBufferOffset(15)]);
|
||||
});
|
||||
|
||||
editor
|
||||
|
||||
@@ -12,7 +12,7 @@ use client::zed_urls;
|
||||
use cloud_llm_client::{Plan, PlanV1, PlanV2};
|
||||
use collections::HashMap;
|
||||
use context_server::ContextServerId;
|
||||
use editor::{Editor, SelectionEffects, scroll::Autoscroll};
|
||||
use editor::{Editor, MultiBufferOffset, SelectionEffects, scroll::Autoscroll};
|
||||
use extension::ExtensionManifest;
|
||||
use extension_host::ExtensionStore;
|
||||
use fs::Fs;
|
||||
@@ -1362,7 +1362,15 @@ async fn open_new_agent_servers_entry_in_settings_editor(
|
||||
.map(|(range, _)| range.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
item.edit(edits, cx);
|
||||
item.edit(
|
||||
edits.into_iter().map(|(range, s)| {
|
||||
(
|
||||
MultiBufferOffset(range.start)..MultiBufferOffset(range.end),
|
||||
s,
|
||||
)
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
if let Some((unique_server_name, buffer)) =
|
||||
unique_server_name.zip(item.buffer().read(cx).as_singleton())
|
||||
{
|
||||
@@ -1375,7 +1383,9 @@ async fn open_new_agent_servers_entry_in_settings_editor(
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
selections.select_ranges(vec![range]);
|
||||
selections.select_ranges(vec![
|
||||
MultiBufferOffset(range.start)..MultiBufferOffset(range.end),
|
||||
]);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -429,7 +429,12 @@ impl CodegenAlternative {
|
||||
|
||||
let prompt = self
|
||||
.builder
|
||||
.generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
|
||||
.generate_inline_transformation_prompt(
|
||||
user_prompt,
|
||||
language_name,
|
||||
buffer,
|
||||
range.start.0..range.end.0,
|
||||
)
|
||||
.context("generating content prompt")?;
|
||||
|
||||
let context_task = self.context_store.as_ref().and_then(|context_store| {
|
||||
|
||||
@@ -1082,7 +1082,7 @@ impl MentionCompletion {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use editor::AnchorRangeExt;
|
||||
use editor::{AnchorRangeExt, MultiBufferOffset};
|
||||
use gpui::{EventEmitter, FocusHandle, Focusable, TestAppContext, VisualTestContext};
|
||||
use project::{Project, ProjectPath};
|
||||
use serde_json::json;
|
||||
@@ -1677,7 +1677,7 @@ mod tests {
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map
|
||||
.snapshot(cx)
|
||||
.folds_in_range(0..snapshot.len())
|
||||
.folds_in_range(MultiBufferOffset(0)..snapshot.len())
|
||||
.map(|fold| fold.range.to_point(&snapshot))
|
||||
.collect()
|
||||
})
|
||||
|
||||
@@ -16,6 +16,7 @@ use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||
use editor::MultiBufferOffset;
|
||||
use editor::RowExt;
|
||||
use editor::SelectionEffects;
|
||||
use editor::scroll::ScrollOffset;
|
||||
@@ -803,7 +804,7 @@ impl InlineAssistant {
|
||||
(
|
||||
editor
|
||||
.selections
|
||||
.newest::<usize>(&editor.display_snapshot(cx)),
|
||||
.newest::<MultiBufferOffset>(&editor.display_snapshot(cx)),
|
||||
editor.buffer().read(cx).snapshot(cx),
|
||||
)
|
||||
});
|
||||
@@ -836,7 +837,7 @@ impl InlineAssistant {
|
||||
(
|
||||
editor
|
||||
.selections
|
||||
.newest::<usize>(&editor.display_snapshot(cx)),
|
||||
.newest::<MultiBufferOffset>(&editor.display_snapshot(cx)),
|
||||
editor.buffer().read(cx).snapshot(cx),
|
||||
)
|
||||
});
|
||||
@@ -853,12 +854,14 @@ impl InlineAssistant {
|
||||
} else {
|
||||
let distance_from_selection = assist_range
|
||||
.start
|
||||
.abs_diff(selection.start)
|
||||
.min(assist_range.start.abs_diff(selection.end))
|
||||
.0
|
||||
.abs_diff(selection.start.0)
|
||||
.min(assist_range.start.0.abs_diff(selection.end.0))
|
||||
+ assist_range
|
||||
.end
|
||||
.abs_diff(selection.start)
|
||||
.min(assist_range.end.abs_diff(selection.end));
|
||||
.0
|
||||
.abs_diff(selection.start.0)
|
||||
.min(assist_range.end.0.abs_diff(selection.end.0));
|
||||
match closest_assist_fallback {
|
||||
Some((_, old_distance)) => {
|
||||
if distance_from_selection < old_distance {
|
||||
@@ -935,7 +938,7 @@ impl InlineAssistant {
|
||||
EditorEvent::Edited { transaction_id } => {
|
||||
let buffer = editor.read(cx).buffer().read(cx);
|
||||
let edited_ranges =
|
||||
buffer.edited_ranges_for_transaction::<usize>(*transaction_id, cx);
|
||||
buffer.edited_ranges_for_transaction::<MultiBufferOffset>(*transaction_id, cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
|
||||
for assist_id in editor_assists.assist_ids.clone() {
|
||||
|
||||
@@ -2,7 +2,7 @@ use agent::HistoryStore;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use editor::actions::Paste;
|
||||
use editor::display_map::{CreaseId, EditorMargins};
|
||||
use editor::{Addon, AnchorRangeExt as _};
|
||||
use editor::{Addon, AnchorRangeExt as _, MultiBufferOffset};
|
||||
use editor::{
|
||||
ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
||||
actions::{MoveDown, MoveUp},
|
||||
@@ -1165,7 +1165,7 @@ impl GenerationMode {
|
||||
/// Stored information that can be used to resurrect a context crease when creating an editor for a past message.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MessageCrease {
|
||||
pub range: Range<usize>,
|
||||
pub range: Range<MultiBufferOffset>,
|
||||
pub icon_path: SharedString,
|
||||
pub label: SharedString,
|
||||
/// None for a deserialized message, Some otherwise.
|
||||
|
||||
@@ -9,8 +9,8 @@ use assistant_slash_commands::{DefaultSlashCommand, FileSlashCommand, selections
|
||||
use client::{proto, zed_urls};
|
||||
use collections::{BTreeSet, HashMap, HashSet, hash_map};
|
||||
use editor::{
|
||||
Anchor, Editor, EditorEvent, MenuEditPredictionsPolicy, MultiBuffer, MultiBufferSnapshot,
|
||||
RowExt, ToOffset as _, ToPoint,
|
||||
Anchor, Editor, EditorEvent, MenuEditPredictionsPolicy, MultiBuffer, MultiBufferOffset,
|
||||
MultiBufferSnapshot, RowExt, ToOffset as _, ToPoint,
|
||||
actions::{MoveToEndOfLine, Newline, ShowCompletions},
|
||||
display_map::{
|
||||
BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId,
|
||||
@@ -390,7 +390,7 @@ impl TextThreadEditor {
|
||||
let cursor = user_message
|
||||
.start
|
||||
.to_offset(self.text_thread.read(cx).buffer().read(cx));
|
||||
cursor..cursor
|
||||
MultiBufferOffset(cursor)..MultiBufferOffset(cursor)
|
||||
};
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
@@ -431,7 +431,7 @@ impl TextThreadEditor {
|
||||
let cursors = self.cursors(cx);
|
||||
self.text_thread.update(cx, |text_thread, cx| {
|
||||
let messages = text_thread
|
||||
.messages_for_offsets(cursors, cx)
|
||||
.messages_for_offsets(cursors.into_iter().map(|cursor| cursor.0), cx)
|
||||
.into_iter()
|
||||
.map(|message| message.id)
|
||||
.collect();
|
||||
@@ -439,9 +439,11 @@ impl TextThreadEditor {
|
||||
});
|
||||
}
|
||||
|
||||
fn cursors(&self, cx: &mut App) -> Vec<usize> {
|
||||
fn cursors(&self, cx: &mut App) -> Vec<MultiBufferOffset> {
|
||||
let selections = self.editor.update(cx, |editor, cx| {
|
||||
editor.selections.all::<usize>(&editor.display_snapshot(cx))
|
||||
editor
|
||||
.selections
|
||||
.all::<MultiBufferOffset>(&editor.display_snapshot(cx))
|
||||
});
|
||||
selections
|
||||
.into_iter()
|
||||
@@ -1580,7 +1582,11 @@ impl TextThreadEditor {
|
||||
fn get_clipboard_contents(
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
|
||||
) -> (
|
||||
String,
|
||||
CopyMetadata,
|
||||
Vec<text::Selection<MultiBufferOffset>>,
|
||||
) {
|
||||
let (mut selection, creases) = self.editor.update(cx, |editor, cx| {
|
||||
let mut selection = editor
|
||||
.selections
|
||||
@@ -1638,30 +1644,26 @@ impl TextThreadEditor {
|
||||
|
||||
// If selection is empty, we want to copy the entire line
|
||||
if selection.range().is_empty() {
|
||||
let snapshot = text_thread.buffer().read(cx).snapshot();
|
||||
let snapshot = self.editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
let point = snapshot.offset_to_point(selection.range().start);
|
||||
selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
|
||||
selection.end = snapshot
|
||||
.point_to_offset(cmp::min(Point::new(point.row + 1, 0), snapshot.max_point()));
|
||||
for chunk in text_thread
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.text_for_range(selection.range())
|
||||
{
|
||||
for chunk in snapshot.text_for_range(selection.range()) {
|
||||
text.push_str(chunk);
|
||||
}
|
||||
} else {
|
||||
for message in text_thread.messages(cx) {
|
||||
if message.offset_range.start >= selection.range().end {
|
||||
if message.offset_range.start >= selection.range().end.0 {
|
||||
break;
|
||||
} else if message.offset_range.end >= selection.range().start {
|
||||
let range = cmp::max(message.offset_range.start, selection.range().start)
|
||||
..cmp::min(message.offset_range.end, selection.range().end);
|
||||
} else if message.offset_range.end >= selection.range().start.0 {
|
||||
let range = cmp::max(message.offset_range.start, selection.range().start.0)
|
||||
..cmp::min(message.offset_range.end, selection.range().end.0);
|
||||
if !range.is_empty() {
|
||||
for chunk in text_thread.buffer().read(cx).text_for_range(range) {
|
||||
text.push_str(chunk);
|
||||
}
|
||||
if message.offset_range.end < selection.range().end {
|
||||
if message.offset_range.end < selection.range().end.0 {
|
||||
text.push('\n');
|
||||
}
|
||||
}
|
||||
@@ -1743,7 +1745,7 @@ impl TextThreadEditor {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let paste_position = editor
|
||||
.selections
|
||||
.newest::<usize>(&editor.display_snapshot(cx))
|
||||
.newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
|
||||
.head();
|
||||
editor.paste(action, window, cx);
|
||||
|
||||
@@ -1791,13 +1793,16 @@ impl TextThreadEditor {
|
||||
editor.transact(window, cx, |editor, _window, cx| {
|
||||
let edits = editor
|
||||
.selections
|
||||
.all::<usize>(&editor.display_snapshot(cx))
|
||||
.all::<MultiBufferOffset>(&editor.display_snapshot(cx))
|
||||
.into_iter()
|
||||
.map(|selection| (selection.start..selection.end, "\n"));
|
||||
editor.edit(edits, cx);
|
||||
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
for selection in editor.selections.all::<usize>(&editor.display_snapshot(cx)) {
|
||||
for selection in editor
|
||||
.selections
|
||||
.all::<MultiBufferOffset>(&editor.display_snapshot(cx))
|
||||
{
|
||||
image_positions.push(snapshot.anchor_before(selection.end));
|
||||
}
|
||||
});
|
||||
@@ -1889,7 +1894,7 @@ impl TextThreadEditor {
|
||||
let range = selection
|
||||
.map(|endpoint| endpoint.to_offset(&buffer))
|
||||
.range();
|
||||
text_thread.split_message(range, cx);
|
||||
text_thread.split_message(range.start.0..range.end.0, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -2963,7 +2968,7 @@ pub fn make_lsp_adapter_delegate(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use editor::SelectionEffects;
|
||||
use editor::{MultiBufferOffset, SelectionEffects};
|
||||
use fs::FakeFs;
|
||||
use gpui::{App, TestAppContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
@@ -3169,15 +3174,16 @@ mod tests {
|
||||
text_thread: &Entity<TextThread>,
|
||||
message_ix: usize,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Range<usize> {
|
||||
text_thread.update(cx, |text_thread, cx| {
|
||||
) -> Range<MultiBufferOffset> {
|
||||
let range = text_thread.update(cx, |text_thread, cx| {
|
||||
text_thread
|
||||
.messages(cx)
|
||||
.nth(message_ix)
|
||||
.unwrap()
|
||||
.anchor_range
|
||||
.to_offset(&text_thread.buffer().read(cx).snapshot())
|
||||
})
|
||||
});
|
||||
MultiBufferOffset(range.start)..MultiBufferOffset(range.end)
|
||||
}
|
||||
|
||||
fn assert_copy_paste_text_thread_editor<T: editor::ToOffset>(
|
||||
|
||||
Reference in New Issue
Block a user