Compare commits
36 Commits
v0.66.0
...
collab-v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b995eb645d | ||
|
|
a6c89838dd | ||
|
|
eb7de02574 | ||
|
|
d70996bb99 | ||
|
|
5a0c39cbed | ||
|
|
41b2fde10d | ||
|
|
023ecd595b | ||
|
|
2b979d3b88 | ||
|
|
5965113fc8 | ||
|
|
3a1cd6ed3a | ||
|
|
9f9398476d | ||
|
|
049c0f8ba4 | ||
|
|
4436ec48eb | ||
|
|
5a9a0f9fa5 | ||
|
|
d2cd9c94f7 | ||
|
|
3adc0b947f | ||
|
|
718f802157 | ||
|
|
f71145bb32 | ||
|
|
0b0fe91545 | ||
|
|
aeea47323a | ||
|
|
e4185f38cf | ||
|
|
09e6d44873 | ||
|
|
525d84e5bf | ||
|
|
55ca085d7d | ||
|
|
03cfd23ac5 | ||
|
|
a666ca3e40 | ||
|
|
b58ae8bdd7 | ||
|
|
5e7652698d | ||
|
|
e51cbf67ab | ||
|
|
8c75df30cb | ||
|
|
1c84e77c37 | ||
|
|
436c89650a | ||
|
|
4ead1ecbbf | ||
|
|
074e3cfbd6 | ||
|
|
bb32599ded | ||
|
|
f9cbed5a1f |
2
.github/workflows/release_actions.yml
vendored
2
.github/workflows/release_actions.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
content: |
|
||||
📣 Zed ${{ github.event.release.tag_name }} was just released!
|
||||
|
||||
Restart your Zed or head to https://zed.dev/releases/latest to grab it.
|
||||
Restart your Zed or head to https://zed.dev/releases to grab it.
|
||||
|
||||
```md
|
||||
# Changelog
|
||||
|
||||
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -1028,7 +1028,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.2.4"
|
||||
version = "0.2.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -6785,6 +6785,7 @@ name = "util"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"backtrace",
|
||||
"futures 0.3.24",
|
||||
"git2",
|
||||
"lazy_static",
|
||||
@@ -7673,7 +7674,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.66.0"
|
||||
version = "0.67.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
@@ -70,14 +70,7 @@ pub fn init(db: project::Db, http_client: Arc<dyn HttpClient>, cx: &mut MutableA
|
||||
}
|
||||
});
|
||||
cx.add_global_action(move |_: &ViewReleaseNotes, cx| {
|
||||
let latest_release_url = if cx.has_global::<ReleaseChannel>()
|
||||
&& *cx.global::<ReleaseChannel>() == ReleaseChannel::Preview
|
||||
{
|
||||
format!("{server_url}/releases/preview/latest")
|
||||
} else {
|
||||
format!("{server_url}/releases/latest")
|
||||
};
|
||||
cx.platform().open_url(&latest_release_url);
|
||||
cx.platform().open_url(&format!("{server_url}/releases"));
|
||||
});
|
||||
cx.add_action(UpdateNotification::dismiss);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||
default-run = "collab"
|
||||
edition = "2021"
|
||||
name = "collab"
|
||||
version = "0.2.4"
|
||||
version = "0.2.6"
|
||||
|
||||
[[bin]]
|
||||
name = "collab"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "signups"
|
||||
ADD "added_to_mailing_list" BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
@@ -350,17 +350,24 @@ impl Db<sqlx::Postgres> {
|
||||
.await?;
|
||||
|
||||
if let Some(inviting_user_id) = inviting_user_id {
|
||||
let (user_id_a, user_id_b, a_to_b) = if inviting_user_id < user_id {
|
||||
(inviting_user_id, user_id, true)
|
||||
} else {
|
||||
(user_id, inviting_user_id, false)
|
||||
};
|
||||
|
||||
sqlx::query(
|
||||
"
|
||||
INSERT INTO contacts
|
||||
(user_id_a, user_id_b, a_to_b, should_notify, accepted)
|
||||
VALUES
|
||||
($1, $2, TRUE, TRUE, TRUE)
|
||||
($1, $2, $3, TRUE, TRUE)
|
||||
ON CONFLICT DO NOTHING
|
||||
",
|
||||
)
|
||||
.bind(inviting_user_id)
|
||||
.bind(user_id)
|
||||
.bind(user_id_a)
|
||||
.bind(user_id_b)
|
||||
.bind(a_to_b)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
@@ -390,10 +397,11 @@ impl Db<sqlx::Postgres> {
|
||||
platform_unknown,
|
||||
editor_features,
|
||||
programming_languages,
|
||||
device_id
|
||||
device_id,
|
||||
added_to_mailing_list
|
||||
)
|
||||
VALUES
|
||||
($1, $2, FALSE, $3, $4, $5, FALSE, $6, $7, $8)
|
||||
($1, $2, FALSE, $3, $4, $5, FALSE, $6, $7, $8, $9)
|
||||
ON CONFLICT (email_address) DO UPDATE SET
|
||||
email_address = excluded.email_address
|
||||
RETURNING id
|
||||
@@ -407,6 +415,7 @@ impl Db<sqlx::Postgres> {
|
||||
.bind(&signup.editor_features)
|
||||
.bind(&signup.programming_languages)
|
||||
.bind(&signup.device_id)
|
||||
.bind(&signup.added_to_mailing_list)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -773,6 +782,8 @@ where
|
||||
WHERE
|
||||
NOT email_confirmation_sent AND
|
||||
(platform_mac OR platform_unknown)
|
||||
ORDER BY
|
||||
created_at
|
||||
LIMIT $1
|
||||
",
|
||||
)
|
||||
@@ -1270,6 +1281,7 @@ pub struct Signup {
|
||||
pub editor_features: Vec<String>,
|
||||
pub programming_languages: Vec<String>,
|
||||
pub device_id: Option<String>,
|
||||
pub added_to_mailing_list: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]
|
||||
|
||||
@@ -514,6 +514,8 @@ async fn test_invite_codes() {
|
||||
should_notify: false
|
||||
}]
|
||||
);
|
||||
assert!(db.has_contact(user1, user2).await.unwrap());
|
||||
assert!(db.has_contact(user2, user1).await.unwrap());
|
||||
assert_eq!(
|
||||
db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
|
||||
7
|
||||
@@ -565,6 +567,8 @@ async fn test_invite_codes() {
|
||||
should_notify: false
|
||||
}]
|
||||
);
|
||||
assert!(db.has_contact(user1, user3).await.unwrap());
|
||||
assert!(db.has_contact(user3, user1).await.unwrap());
|
||||
assert_eq!(
|
||||
db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
|
||||
3
|
||||
@@ -626,6 +630,8 @@ async fn test_invite_codes() {
|
||||
should_notify: false
|
||||
}]
|
||||
);
|
||||
assert!(db.has_contact(user1, user4).await.unwrap());
|
||||
assert!(db.has_contact(user4, user1).await.unwrap());
|
||||
assert_eq!(
|
||||
db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
|
||||
5
|
||||
@@ -637,6 +643,72 @@ async fn test_invite_codes() {
|
||||
.unwrap_err();
|
||||
let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
|
||||
assert_eq!(invite_count, 1);
|
||||
|
||||
// A newer user can invite an existing one via a different email address
|
||||
// than the one they used to sign up.
|
||||
let user5 = db
|
||||
.create_user(
|
||||
"user5@example.com",
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user5".into(),
|
||||
github_user_id: 5,
|
||||
invite_count: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
db.set_invite_count_for_user(user5, 5).await.unwrap();
|
||||
let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap();
|
||||
let user5_invite_to_user1 = db
|
||||
.create_invite_from_code(&user5_invite_code, "user1@different.com", None)
|
||||
.await
|
||||
.unwrap();
|
||||
let user1_2 = db
|
||||
.create_user_from_invite(
|
||||
&user5_invite_to_user1,
|
||||
NewUserParams {
|
||||
github_login: "user1".into(),
|
||||
github_user_id: 1,
|
||||
invite_count: 5,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.user_id;
|
||||
assert_eq!(user1_2, user1);
|
||||
assert_eq!(
|
||||
db.get_contacts(user1).await.unwrap(),
|
||||
[
|
||||
Contact::Accepted {
|
||||
user_id: user2,
|
||||
should_notify: true,
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user3,
|
||||
should_notify: true,
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user4,
|
||||
should_notify: true,
|
||||
},
|
||||
Contact::Accepted {
|
||||
user_id: user5,
|
||||
should_notify: false,
|
||||
}
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_contacts(user5).await.unwrap(),
|
||||
[Contact::Accepted {
|
||||
user_id: user1,
|
||||
should_notify: true,
|
||||
}]
|
||||
);
|
||||
assert!(db.has_contact(user1, user5).await.unwrap());
|
||||
assert!(db.has_contact(user5, user1).await.unwrap());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -657,6 +729,7 @@ async fn test_signups() {
|
||||
editor_features: vec!["speed".into()],
|
||||
programming_languages: vec!["rust".into(), "c".into()],
|
||||
device_id: Some(format!("device_id_{i}")),
|
||||
added_to_mailing_list: i != 0, // One user failed to subscribe
|
||||
})
|
||||
.collect::<Vec<Signup>>();
|
||||
|
||||
|
||||
@@ -322,7 +322,7 @@ impl ProjectDiagnosticsEditor {
|
||||
);
|
||||
let excerpt_id = excerpts
|
||||
.insert_excerpts_after(
|
||||
&prev_excerpt_id,
|
||||
prev_excerpt_id,
|
||||
buffer.clone(),
|
||||
[ExcerptRange {
|
||||
context: excerpt_start..excerpt_end,
|
||||
@@ -384,7 +384,7 @@ impl ProjectDiagnosticsEditor {
|
||||
|
||||
groups_to_add.push(group_state);
|
||||
} else if let Some((group_ix, group_state)) = to_remove {
|
||||
excerpts.remove_excerpts(group_state.excerpts.iter(), excerpts_cx);
|
||||
excerpts.remove_excerpts(group_state.excerpts.iter().copied(), excerpts_cx);
|
||||
group_ixs_to_remove.push(group_ix);
|
||||
blocks_to_remove.extend(group_state.blocks.iter().copied());
|
||||
} else if let Some((_, group)) = to_keep {
|
||||
@@ -457,10 +457,15 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
|
||||
// If any selection has lost its position, move it to start of the next primary diagnostic.
|
||||
let snapshot = editor.snapshot(cx);
|
||||
for selection in &mut selections {
|
||||
if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) {
|
||||
let group_ix = match groups.binary_search_by(|probe| {
|
||||
probe.excerpts.last().unwrap().cmp(new_excerpt_id)
|
||||
probe
|
||||
.excerpts
|
||||
.last()
|
||||
.unwrap()
|
||||
.cmp(new_excerpt_id, &snapshot.buffer_snapshot)
|
||||
}) {
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
@@ -738,7 +743,7 @@ mod tests {
|
||||
DisplayPoint,
|
||||
};
|
||||
use gpui::TestAppContext;
|
||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16};
|
||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
|
||||
use serde_json::json;
|
||||
use unindent::Unindent as _;
|
||||
use workspace::AppState;
|
||||
@@ -788,7 +793,7 @@ mod tests {
|
||||
None,
|
||||
vec![
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9),
|
||||
range: Unclipped(PointUtf16::new(1, 8))..Unclipped(PointUtf16::new(1, 9)),
|
||||
diagnostic: Diagnostic {
|
||||
message:
|
||||
"move occurs because `x` has type `Vec<char>`, which does not implement the `Copy` trait"
|
||||
@@ -801,7 +806,7 @@ mod tests {
|
||||
},
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(2, 8)..PointUtf16::new(2, 9),
|
||||
range: Unclipped(PointUtf16::new(2, 8))..Unclipped(PointUtf16::new(2, 9)),
|
||||
diagnostic: Diagnostic {
|
||||
message:
|
||||
"move occurs because `y` has type `Vec<char>`, which does not implement the `Copy` trait"
|
||||
@@ -814,7 +819,7 @@ mod tests {
|
||||
},
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(3, 6)..PointUtf16::new(3, 7),
|
||||
range: Unclipped(PointUtf16::new(3, 6))..Unclipped(PointUtf16::new(3, 7)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "value moved here".to_string(),
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
@@ -825,7 +830,7 @@ mod tests {
|
||||
},
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(4, 6)..PointUtf16::new(4, 7),
|
||||
range: Unclipped(PointUtf16::new(4, 6))..Unclipped(PointUtf16::new(4, 7)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "value moved here".to_string(),
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
@@ -836,7 +841,7 @@ mod tests {
|
||||
},
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(7, 6)..PointUtf16::new(7, 7),
|
||||
range: Unclipped(PointUtf16::new(7, 6))..Unclipped(PointUtf16::new(7, 7)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "use of moved value\nvalue used here after move".to_string(),
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
@@ -847,7 +852,7 @@ mod tests {
|
||||
},
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(8, 6)..PointUtf16::new(8, 7),
|
||||
range: Unclipped(PointUtf16::new(8, 6))..Unclipped(PointUtf16::new(8, 7)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "use of moved value\nvalue used here after move".to_string(),
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
@@ -939,7 +944,7 @@ mod tests {
|
||||
PathBuf::from("/test/consts.rs"),
|
||||
None,
|
||||
vec![DiagnosticEntry {
|
||||
range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15),
|
||||
range: Unclipped(PointUtf16::new(0, 15))..Unclipped(PointUtf16::new(0, 15)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "mismatched types\nexpected `usize`, found `char`".to_string(),
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
@@ -1040,7 +1045,8 @@ mod tests {
|
||||
None,
|
||||
vec![
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15),
|
||||
range: Unclipped(PointUtf16::new(0, 15))
|
||||
..Unclipped(PointUtf16::new(0, 15)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "mismatched types\nexpected `usize`, found `char`"
|
||||
.to_string(),
|
||||
@@ -1052,7 +1058,8 @@ mod tests {
|
||||
},
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(1, 15)..PointUtf16::new(1, 15),
|
||||
range: Unclipped(PointUtf16::new(1, 15))
|
||||
..Unclipped(PointUtf16::new(1, 15)),
|
||||
diagnostic: Diagnostic {
|
||||
message: "unresolved name `c`".to_string(),
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{
|
||||
wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
|
||||
TextHighlights,
|
||||
};
|
||||
use crate::{Anchor, ExcerptRange, ToPoint as _};
|
||||
use crate::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
|
||||
use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{ElementBox, RenderContext};
|
||||
use language::{BufferSnapshot, Chunk, Patch, Point};
|
||||
@@ -107,7 +107,7 @@ struct Transform {
|
||||
pub enum TransformBlock {
|
||||
Custom(Arc<Block>),
|
||||
ExcerptHeader {
|
||||
key: usize,
|
||||
id: ExcerptId,
|
||||
buffer: BufferSnapshot,
|
||||
range: ExcerptRange<text::Anchor>,
|
||||
height: u8,
|
||||
@@ -371,7 +371,7 @@ impl BlockMap {
|
||||
.make_wrap_point(Point::new(excerpt_boundary.row, 0), Bias::Left)
|
||||
.row(),
|
||||
TransformBlock::ExcerptHeader {
|
||||
key: excerpt_boundary.key,
|
||||
id: excerpt_boundary.id,
|
||||
buffer: excerpt_boundary.buffer,
|
||||
range: excerpt_boundary.range,
|
||||
height: if excerpt_boundary.starts_new_buffer {
|
||||
|
||||
@@ -1162,7 +1162,7 @@ impl Editor {
|
||||
});
|
||||
clone.selections.set_state(&self.selections);
|
||||
clone.scroll_position = self.scroll_position;
|
||||
clone.scroll_top_anchor = self.scroll_top_anchor.clone();
|
||||
clone.scroll_top_anchor = self.scroll_top_anchor;
|
||||
clone.searchable = self.searchable;
|
||||
clone
|
||||
}
|
||||
@@ -1305,7 +1305,7 @@ impl Editor {
|
||||
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
||||
ongoing_scroll: self.ongoing_scroll,
|
||||
scroll_position: self.scroll_position,
|
||||
scroll_top_anchor: self.scroll_top_anchor.clone(),
|
||||
scroll_top_anchor: self.scroll_top_anchor,
|
||||
placeholder_text: self.placeholder_text.clone(),
|
||||
is_focused: self
|
||||
.handle
|
||||
@@ -1791,17 +1791,15 @@ impl Editor {
|
||||
.pending_anchor()
|
||||
.expect("extend_selection not called with pending selection");
|
||||
if position >= tail {
|
||||
pending_selection.start = tail_anchor.clone();
|
||||
pending_selection.start = tail_anchor;
|
||||
} else {
|
||||
pending_selection.end = tail_anchor.clone();
|
||||
pending_selection.end = tail_anchor;
|
||||
pending_selection.reversed = true;
|
||||
}
|
||||
|
||||
let mut pending_mode = self.selections.pending_mode().unwrap();
|
||||
match &mut pending_mode {
|
||||
SelectMode::Word(range) | SelectMode::Line(range) => {
|
||||
*range = tail_anchor.clone()..tail_anchor
|
||||
}
|
||||
SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -2145,10 +2143,9 @@ impl Editor {
|
||||
));
|
||||
if following_text_allows_autoclose && preceding_text_matches_prefix {
|
||||
let anchor = snapshot.anchor_before(selection.end);
|
||||
new_selections
|
||||
.push((selection.map(|_| anchor.clone()), text.len()));
|
||||
new_selections.push((selection.map(|_| anchor), text.len()));
|
||||
new_autoclose_regions.push((
|
||||
anchor.clone(),
|
||||
anchor,
|
||||
text.len(),
|
||||
selection.id,
|
||||
bracket_pair.clone(),
|
||||
@@ -2169,10 +2166,8 @@ impl Editor {
|
||||
&& text.as_ref() == region.pair.end.as_str();
|
||||
if should_skip {
|
||||
let anchor = snapshot.anchor_after(selection.end);
|
||||
new_selections.push((
|
||||
selection.map(|_| anchor.clone()),
|
||||
region.pair.end.len(),
|
||||
));
|
||||
new_selections
|
||||
.push((selection.map(|_| anchor), region.pair.end.len()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2204,7 +2199,7 @@ impl Editor {
|
||||
// text with the given input and move the selection to the end of the
|
||||
// newly inserted text.
|
||||
let anchor = snapshot.anchor_after(selection.end);
|
||||
new_selections.push((selection.map(|_| anchor.clone()), 0));
|
||||
new_selections.push((selection.map(|_| anchor), 0));
|
||||
edits.push((selection.start..selection.end, text.clone()));
|
||||
}
|
||||
|
||||
@@ -2306,7 +2301,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
let anchor = buffer.anchor_after(end);
|
||||
let new_selection = selection.map(|_| anchor.clone());
|
||||
let new_selection = selection.map(|_| anchor);
|
||||
(
|
||||
(start..end, new_text),
|
||||
(insert_extra_newline, new_selection),
|
||||
@@ -2386,7 +2381,7 @@ impl Editor {
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let anchor = snapshot.anchor_after(s.end);
|
||||
s.map(|_| anchor.clone())
|
||||
s.map(|_| anchor)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
@@ -3650,7 +3645,7 @@ impl Editor {
|
||||
String::new(),
|
||||
));
|
||||
let insertion_anchor = buffer.anchor_after(insertion_point);
|
||||
edits.push((insertion_anchor.clone()..insertion_anchor, text));
|
||||
edits.push((insertion_anchor..insertion_anchor, text));
|
||||
|
||||
let row_delta = range_to_move.start.row - insertion_point.row + 1;
|
||||
|
||||
@@ -3755,7 +3750,7 @@ impl Editor {
|
||||
String::new(),
|
||||
));
|
||||
let insertion_anchor = buffer.anchor_after(insertion_point);
|
||||
edits.push((insertion_anchor.clone()..insertion_anchor, text));
|
||||
edits.push((insertion_anchor..insertion_anchor, text));
|
||||
|
||||
let row_delta = insertion_point.row - range_to_move.end.row + 1;
|
||||
|
||||
@@ -4625,7 +4620,7 @@ impl Editor {
|
||||
cursor_anchor: position,
|
||||
cursor_position: point,
|
||||
scroll_position: self.scroll_position,
|
||||
scroll_top_anchor: self.scroll_top_anchor.clone(),
|
||||
scroll_top_anchor: self.scroll_top_anchor,
|
||||
scroll_top_row,
|
||||
}),
|
||||
cx,
|
||||
|
||||
@@ -542,7 +542,7 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
|
||||
// Set scroll position to check later
|
||||
editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
|
||||
let original_scroll_position = editor.scroll_position;
|
||||
let original_scroll_top_anchor = editor.scroll_top_anchor.clone();
|
||||
let original_scroll_top_anchor = editor.scroll_top_anchor;
|
||||
|
||||
// Jump to the end of the document and adjust scroll
|
||||
editor.move_to_end(&MoveToEnd, cx);
|
||||
@@ -556,12 +556,12 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
|
||||
assert_eq!(editor.scroll_top_anchor, original_scroll_top_anchor);
|
||||
|
||||
// Ensure we don't panic when navigation data contains invalid anchors *and* points.
|
||||
let mut invalid_anchor = editor.scroll_top_anchor.clone();
|
||||
let mut invalid_anchor = editor.scroll_top_anchor;
|
||||
invalid_anchor.text_anchor.buffer_id = Some(999);
|
||||
let invalid_point = Point::new(9999, 0);
|
||||
editor.navigate(
|
||||
Box::new(NavigationData {
|
||||
cursor_anchor: invalid_anchor.clone(),
|
||||
cursor_anchor: invalid_anchor,
|
||||
cursor_position: invalid_point,
|
||||
scroll_top_anchor: invalid_anchor,
|
||||
scroll_top_row: invalid_point.row,
|
||||
@@ -4718,9 +4718,7 @@ fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
|
||||
|
||||
// Refreshing selections is a no-op when excerpts haven't changed.
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.refresh();
|
||||
});
|
||||
editor.change_selections(None, cx, |s| s.refresh());
|
||||
assert_eq!(
|
||||
editor.selections.ranges(cx),
|
||||
[
|
||||
@@ -4731,7 +4729,7 @@ fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
|
||||
});
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx);
|
||||
multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
|
||||
});
|
||||
editor.update(cx, |editor, cx| {
|
||||
// Removing an excerpt causes the first selection to become degenerate.
|
||||
@@ -4745,9 +4743,7 @@ fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
|
||||
|
||||
// Refreshing selections will relocate the first selection to the original buffer
|
||||
// location.
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.refresh();
|
||||
});
|
||||
editor.change_selections(None, cx, |s| s.refresh());
|
||||
assert_eq!(
|
||||
editor.selections.ranges(cx),
|
||||
[
|
||||
@@ -4801,7 +4797,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppC
|
||||
});
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx);
|
||||
multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
|
||||
});
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
@@ -4810,9 +4806,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppC
|
||||
);
|
||||
|
||||
// Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.refresh();
|
||||
});
|
||||
editor.change_selections(None, cx, |s| s.refresh());
|
||||
assert_eq!(
|
||||
editor.selections.ranges(cx),
|
||||
[Point::new(0, 3)..Point::new(0, 3)]
|
||||
|
||||
@@ -1334,12 +1334,13 @@ impl EditorElement {
|
||||
})
|
||||
}
|
||||
TransformBlock::ExcerptHeader {
|
||||
key,
|
||||
id,
|
||||
buffer,
|
||||
range,
|
||||
starts_new_buffer,
|
||||
..
|
||||
} => {
|
||||
let id = *id;
|
||||
let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
|
||||
let jump_position = range
|
||||
.primary
|
||||
@@ -1356,7 +1357,7 @@ impl EditorElement {
|
||||
|
||||
enum JumpIcon {}
|
||||
cx.render(&editor, |_, cx| {
|
||||
MouseEventHandler::<JumpIcon>::new(*key, cx, |state, _| {
|
||||
MouseEventHandler::<JumpIcon>::new(id.into(), cx, |state, _| {
|
||||
let style = style.jump_icon.style_for(state, false);
|
||||
Svg::new("icons/arrow_up_right_8.svg")
|
||||
.with_color(style.color)
|
||||
@@ -1375,7 +1376,7 @@ impl EditorElement {
|
||||
cx.dispatch_action(jump_action.clone())
|
||||
})
|
||||
.with_tooltip::<JumpIcon, _>(
|
||||
*key,
|
||||
id.into(),
|
||||
"Jump to Buffer".to_string(),
|
||||
Some(Box::new(crate::OpenExcerpts)),
|
||||
tooltip_style.clone(),
|
||||
@@ -1606,16 +1607,13 @@ impl Element for EditorElement {
|
||||
|
||||
highlighted_rows = view.highlighted_rows();
|
||||
let theme = cx.global::<Settings>().theme.as_ref();
|
||||
highlighted_ranges = view.background_highlights_in_range(
|
||||
start_anchor.clone()..end_anchor.clone(),
|
||||
&display_map,
|
||||
theme,
|
||||
);
|
||||
highlighted_ranges =
|
||||
view.background_highlights_in_range(start_anchor..end_anchor, &display_map, theme);
|
||||
|
||||
let mut remote_selections = HashMap::default();
|
||||
for (replica_id, line_mode, cursor_shape, selection) in display_map
|
||||
.buffer_snapshot
|
||||
.remote_selections_in_range(&(start_anchor.clone()..end_anchor.clone()))
|
||||
.remote_selections_in_range(&(start_anchor..end_anchor))
|
||||
{
|
||||
// The local selections match the leader's selections.
|
||||
if Some(replica_id) == view.leader_replica_id {
|
||||
|
||||
@@ -221,7 +221,7 @@ fn show_hover(
|
||||
|
||||
start..end
|
||||
} else {
|
||||
anchor.clone()..anchor.clone()
|
||||
anchor..anchor
|
||||
};
|
||||
|
||||
Some(InfoPopover {
|
||||
|
||||
@@ -11,7 +11,7 @@ use language::{
|
||||
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
|
||||
DiagnosticEntry, Event, File, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline,
|
||||
OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _,
|
||||
ToPoint as _, ToPointUtf16 as _, TransactionId,
|
||||
ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
@@ -36,13 +36,13 @@ use util::post_inc;
|
||||
|
||||
const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
|
||||
|
||||
pub type ExcerptId = Locator;
|
||||
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ExcerptId(usize);
|
||||
|
||||
pub struct MultiBuffer {
|
||||
snapshot: RefCell<MultiBufferSnapshot>,
|
||||
buffers: RefCell<HashMap<usize, BufferState>>,
|
||||
used_excerpt_ids: SumTree<ExcerptId>,
|
||||
next_excerpt_key: usize,
|
||||
next_excerpt_id: usize,
|
||||
subscriptions: Topic,
|
||||
singleton: bool,
|
||||
replica_id: ReplicaId,
|
||||
@@ -92,7 +92,7 @@ struct BufferState {
|
||||
last_diagnostics_update_count: usize,
|
||||
last_file_update_count: usize,
|
||||
last_git_diff_update_count: usize,
|
||||
excerpts: Vec<ExcerptId>,
|
||||
excerpts: Vec<Locator>,
|
||||
_subscriptions: [gpui::Subscription; 2],
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ struct BufferState {
|
||||
pub struct MultiBufferSnapshot {
|
||||
singleton: bool,
|
||||
excerpts: SumTree<Excerpt>,
|
||||
excerpt_ids: SumTree<ExcerptIdMapping>,
|
||||
parse_count: usize,
|
||||
diagnostics_update_count: usize,
|
||||
trailing_excerpt_update_count: usize,
|
||||
@@ -111,7 +112,6 @@ pub struct MultiBufferSnapshot {
|
||||
|
||||
pub struct ExcerptBoundary {
|
||||
pub id: ExcerptId,
|
||||
pub key: usize,
|
||||
pub row: u32,
|
||||
pub buffer: BufferSnapshot,
|
||||
pub range: ExcerptRange<text::Anchor>,
|
||||
@@ -121,7 +121,7 @@ pub struct ExcerptBoundary {
|
||||
#[derive(Clone)]
|
||||
struct Excerpt {
|
||||
id: ExcerptId,
|
||||
key: usize,
|
||||
locator: Locator,
|
||||
buffer_id: usize,
|
||||
buffer: BufferSnapshot,
|
||||
range: ExcerptRange<text::Anchor>,
|
||||
@@ -130,6 +130,12 @@ struct Excerpt {
|
||||
has_trailing_newline: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ExcerptIdMapping {
|
||||
id: ExcerptId,
|
||||
locator: Locator,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ExcerptRange<T> {
|
||||
pub context: Range<T>,
|
||||
@@ -139,6 +145,7 @@ pub struct ExcerptRange<T> {
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct ExcerptSummary {
|
||||
excerpt_id: ExcerptId,
|
||||
excerpt_locator: Locator,
|
||||
max_buffer_row: u32,
|
||||
text: TextSummary,
|
||||
}
|
||||
@@ -178,8 +185,7 @@ impl MultiBuffer {
|
||||
Self {
|
||||
snapshot: Default::default(),
|
||||
buffers: Default::default(),
|
||||
used_excerpt_ids: Default::default(),
|
||||
next_excerpt_key: Default::default(),
|
||||
next_excerpt_id: 1,
|
||||
subscriptions: Default::default(),
|
||||
singleton: false,
|
||||
replica_id,
|
||||
@@ -218,8 +224,7 @@ impl MultiBuffer {
|
||||
Self {
|
||||
snapshot: RefCell::new(self.snapshot.borrow().clone()),
|
||||
buffers: RefCell::new(buffers),
|
||||
used_excerpt_ids: self.used_excerpt_ids.clone(),
|
||||
next_excerpt_key: self.next_excerpt_key,
|
||||
next_excerpt_id: 1,
|
||||
subscriptions: Default::default(),
|
||||
singleton: self.singleton,
|
||||
replica_id: self.replica_id,
|
||||
@@ -610,11 +615,14 @@ impl MultiBuffer {
|
||||
let mut selections_by_buffer: HashMap<usize, Vec<Selection<text::Anchor>>> =
|
||||
Default::default();
|
||||
let snapshot = self.read(cx);
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
|
||||
for selection in selections {
|
||||
cursor.seek(&Some(&selection.start.excerpt_id), Bias::Left, &());
|
||||
let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id);
|
||||
let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id);
|
||||
|
||||
cursor.seek(&Some(start_locator), Bias::Left, &());
|
||||
while let Some(excerpt) = cursor.item() {
|
||||
if excerpt.id > selection.end.excerpt_id {
|
||||
if excerpt.locator > *end_locator {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -745,7 +753,7 @@ impl MultiBuffer {
|
||||
where
|
||||
O: text::ToOffset,
|
||||
{
|
||||
self.insert_excerpts_after(&ExcerptId::max(), buffer, ranges, cx)
|
||||
self.insert_excerpts_after(ExcerptId::max(), buffer, ranges, cx)
|
||||
}
|
||||
|
||||
pub fn push_excerpts_with_context_lines<O>(
|
||||
@@ -818,7 +826,7 @@ impl MultiBuffer {
|
||||
|
||||
pub fn insert_excerpts_after<O>(
|
||||
&mut self,
|
||||
prev_excerpt_id: &ExcerptId,
|
||||
prev_excerpt_id: ExcerptId,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
ranges: impl IntoIterator<Item = ExcerptRange<O>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
@@ -854,8 +862,12 @@ impl MultiBuffer {
|
||||
});
|
||||
|
||||
let mut snapshot = self.snapshot.borrow_mut();
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
|
||||
let mut new_excerpts = cursor.slice(&Some(prev_excerpt_id), Bias::Right, &());
|
||||
|
||||
let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone();
|
||||
let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids);
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
|
||||
let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right, &());
|
||||
prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone();
|
||||
|
||||
let edit_start = new_excerpts.summary().text.len;
|
||||
new_excerpts.update_last(
|
||||
@@ -865,25 +877,17 @@ impl MultiBuffer {
|
||||
&(),
|
||||
);
|
||||
|
||||
let mut used_cursor = self.used_excerpt_ids.cursor::<Locator>();
|
||||
used_cursor.seek(prev_excerpt_id, Bias::Right, &());
|
||||
let mut prev_id = if let Some(excerpt_id) = used_cursor.prev_item() {
|
||||
excerpt_id.clone()
|
||||
let next_locator = if let Some(excerpt) = cursor.item() {
|
||||
excerpt.locator.clone()
|
||||
} else {
|
||||
ExcerptId::min()
|
||||
Locator::max()
|
||||
};
|
||||
let next_id = if let Some(excerpt_id) = used_cursor.item() {
|
||||
excerpt_id.clone()
|
||||
} else {
|
||||
ExcerptId::max()
|
||||
};
|
||||
drop(used_cursor);
|
||||
|
||||
let mut ids = Vec::new();
|
||||
while let Some(range) = ranges.next() {
|
||||
let id = ExcerptId::between(&prev_id, &next_id);
|
||||
if let Err(ix) = buffer_state.excerpts.binary_search(&id) {
|
||||
buffer_state.excerpts.insert(ix, id.clone());
|
||||
let locator = Locator::between(&prev_locator, &next_locator);
|
||||
if let Err(ix) = buffer_state.excerpts.binary_search(&locator) {
|
||||
buffer_state.excerpts.insert(ix, locator.clone());
|
||||
}
|
||||
let range = ExcerptRange {
|
||||
context: buffer_snapshot.anchor_before(&range.context.start)
|
||||
@@ -893,22 +897,20 @@ impl MultiBuffer {
|
||||
..buffer_snapshot.anchor_after(&primary.end)
|
||||
}),
|
||||
};
|
||||
let id = ExcerptId(post_inc(&mut self.next_excerpt_id));
|
||||
let excerpt = Excerpt::new(
|
||||
id.clone(),
|
||||
post_inc(&mut self.next_excerpt_key),
|
||||
id,
|
||||
locator.clone(),
|
||||
buffer_id,
|
||||
buffer_snapshot.clone(),
|
||||
range,
|
||||
ranges.peek().is_some() || cursor.item().is_some(),
|
||||
);
|
||||
new_excerpts.push(excerpt, &());
|
||||
prev_id = id.clone();
|
||||
prev_locator = locator.clone();
|
||||
new_excerpt_ids.push(ExcerptIdMapping { id, locator }, &());
|
||||
ids.push(id);
|
||||
}
|
||||
self.used_excerpt_ids.edit(
|
||||
ids.iter().cloned().map(sum_tree::Edit::Insert).collect(),
|
||||
&(),
|
||||
);
|
||||
|
||||
let edit_end = new_excerpts.summary().text.len;
|
||||
|
||||
@@ -917,6 +919,7 @@ impl MultiBuffer {
|
||||
new_excerpts.push_tree(suffix, &());
|
||||
drop(cursor);
|
||||
snapshot.excerpts = new_excerpts;
|
||||
snapshot.excerpt_ids = new_excerpt_ids;
|
||||
if changed_trailing_excerpt {
|
||||
snapshot.trailing_excerpt_update_count += 1;
|
||||
}
|
||||
@@ -956,16 +959,16 @@ impl MultiBuffer {
|
||||
let mut excerpts = Vec::new();
|
||||
let snapshot = self.read(cx);
|
||||
let buffers = self.buffers.borrow();
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
|
||||
for excerpt_id in buffers
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
|
||||
for locator in buffers
|
||||
.get(&buffer.id())
|
||||
.map(|state| &state.excerpts)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
cursor.seek_forward(&Some(excerpt_id), Bias::Left, &());
|
||||
cursor.seek_forward(&Some(locator), Bias::Left, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
if excerpt.id == *excerpt_id {
|
||||
if excerpt.locator == *locator {
|
||||
excerpts.push((excerpt.id.clone(), excerpt.range.clone()));
|
||||
}
|
||||
}
|
||||
@@ -975,10 +978,11 @@ impl MultiBuffer {
|
||||
}
|
||||
|
||||
pub fn excerpt_ids(&self) -> Vec<ExcerptId> {
|
||||
self.buffers
|
||||
self.snapshot
|
||||
.borrow()
|
||||
.values()
|
||||
.flat_map(|state| state.excerpts.iter().cloned())
|
||||
.excerpts
|
||||
.iter()
|
||||
.map(|entry| entry.id)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1061,32 +1065,34 @@ impl MultiBuffer {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn remove_excerpts<'a>(
|
||||
pub fn remove_excerpts(
|
||||
&mut self,
|
||||
excerpt_ids: impl IntoIterator<Item = &'a ExcerptId>,
|
||||
excerpt_ids: impl IntoIterator<Item = ExcerptId>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.sync(cx);
|
||||
let mut buffers = self.buffers.borrow_mut();
|
||||
let mut snapshot = self.snapshot.borrow_mut();
|
||||
let mut new_excerpts = SumTree::new();
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&ExcerptId>, usize)>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
|
||||
let mut edits = Vec::new();
|
||||
let mut excerpt_ids = excerpt_ids.into_iter().peekable();
|
||||
|
||||
while let Some(mut excerpt_id) = excerpt_ids.next() {
|
||||
while let Some(excerpt_id) = excerpt_ids.next() {
|
||||
// Seek to the next excerpt to remove, preserving any preceding excerpts.
|
||||
new_excerpts.push_tree(cursor.slice(&Some(excerpt_id), Bias::Left, &()), &());
|
||||
let locator = snapshot.excerpt_locator_for_id(excerpt_id);
|
||||
new_excerpts.push_tree(cursor.slice(&Some(locator), Bias::Left, &()), &());
|
||||
|
||||
if let Some(mut excerpt) = cursor.item() {
|
||||
if excerpt.id != *excerpt_id {
|
||||
if excerpt.id != excerpt_id {
|
||||
continue;
|
||||
}
|
||||
let mut old_start = cursor.start().1;
|
||||
|
||||
// Skip over the removed excerpt.
|
||||
loop {
|
||||
'remove_excerpts: loop {
|
||||
if let Some(buffer_state) = buffers.get_mut(&excerpt.buffer_id) {
|
||||
buffer_state.excerpts.retain(|id| id != excerpt_id);
|
||||
buffer_state.excerpts.retain(|l| l != &excerpt.locator);
|
||||
if buffer_state.excerpts.is_empty() {
|
||||
buffers.remove(&excerpt.buffer_id);
|
||||
}
|
||||
@@ -1094,14 +1100,16 @@ impl MultiBuffer {
|
||||
cursor.next(&());
|
||||
|
||||
// Skip over any subsequent excerpts that are also removed.
|
||||
if let Some(&next_excerpt_id) = excerpt_ids.peek() {
|
||||
while let Some(&next_excerpt_id) = excerpt_ids.peek() {
|
||||
let next_locator = snapshot.excerpt_locator_for_id(next_excerpt_id);
|
||||
if let Some(next_excerpt) = cursor.item() {
|
||||
if next_excerpt.id == *next_excerpt_id {
|
||||
if next_excerpt.locator == *next_locator {
|
||||
excerpt_ids.next();
|
||||
excerpt = next_excerpt;
|
||||
excerpt_id = excerpt_ids.next().unwrap();
|
||||
continue;
|
||||
continue 'remove_excerpts;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1128,6 +1136,7 @@ impl MultiBuffer {
|
||||
new_excerpts.push_tree(suffix, &());
|
||||
drop(cursor);
|
||||
snapshot.excerpts = new_excerpts;
|
||||
|
||||
if changed_trailing_excerpt {
|
||||
snapshot.trailing_excerpt_update_count += 1;
|
||||
}
|
||||
@@ -1307,7 +1316,7 @@ impl MultiBuffer {
|
||||
buffer_state
|
||||
.excerpts
|
||||
.iter()
|
||||
.map(|excerpt_id| (excerpt_id, buffer_state.buffer.clone(), buffer_edited)),
|
||||
.map(|locator| (locator, buffer_state.buffer.clone(), buffer_edited)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1333,14 +1342,14 @@ impl MultiBuffer {
|
||||
snapshot.is_dirty = is_dirty;
|
||||
snapshot.has_conflict = has_conflict;
|
||||
|
||||
excerpts_to_edit.sort_unstable_by_key(|(excerpt_id, _, _)| *excerpt_id);
|
||||
excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
let mut new_excerpts = SumTree::new();
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&ExcerptId>, usize)>();
|
||||
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
|
||||
|
||||
for (id, buffer, buffer_edited) in excerpts_to_edit {
|
||||
new_excerpts.push_tree(cursor.slice(&Some(id), Bias::Left, &()), &());
|
||||
for (locator, buffer, buffer_edited) in excerpts_to_edit {
|
||||
new_excerpts.push_tree(cursor.slice(&Some(locator), Bias::Left, &()), &());
|
||||
let old_excerpt = cursor.item().unwrap();
|
||||
let buffer_id = buffer.id();
|
||||
let buffer = buffer.read(cx);
|
||||
@@ -1365,8 +1374,8 @@ impl MultiBuffer {
|
||||
);
|
||||
|
||||
new_excerpt = Excerpt::new(
|
||||
id.clone(),
|
||||
old_excerpt.key,
|
||||
old_excerpt.id,
|
||||
locator.clone(),
|
||||
buffer_id,
|
||||
buffer.snapshot(),
|
||||
old_excerpt.range.clone(),
|
||||
@@ -1467,13 +1476,7 @@ impl MultiBuffer {
|
||||
continue;
|
||||
}
|
||||
|
||||
let excerpt_ids = self
|
||||
.buffers
|
||||
.borrow()
|
||||
.values()
|
||||
.flat_map(|b| &b.excerpts)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let excerpt_ids = self.excerpt_ids();
|
||||
if excerpt_ids.is_empty() || (rng.gen() && excerpt_ids.len() < max_excerpts) {
|
||||
let buffer_handle = if rng.gen() || self.buffers.borrow().is_empty() {
|
||||
let text = RandomCharIter::new(&mut *rng).take(10).collect::<String>();
|
||||
@@ -1511,24 +1514,26 @@ impl MultiBuffer {
|
||||
log::info!(
|
||||
"Inserting excerpts from buffer {} and ranges {:?}: {:?}",
|
||||
buffer_handle.id(),
|
||||
ranges,
|
||||
ranges.iter().map(|r| &r.context).collect::<Vec<_>>(),
|
||||
ranges
|
||||
.iter()
|
||||
.map(|range| &buffer_text[range.context.clone()])
|
||||
.map(|r| &buffer_text[r.context.clone()])
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let excerpt_id = self.push_excerpts(buffer_handle.clone(), ranges, cx);
|
||||
log::info!("Inserted with id: {:?}", excerpt_id);
|
||||
log::info!("Inserted with ids: {:?}", excerpt_id);
|
||||
} else {
|
||||
let remove_count = rng.gen_range(1..=excerpt_ids.len());
|
||||
let mut excerpts_to_remove = excerpt_ids
|
||||
.choose_multiple(rng, remove_count)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
excerpts_to_remove.sort();
|
||||
let snapshot = self.snapshot.borrow();
|
||||
excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &*snapshot));
|
||||
drop(snapshot);
|
||||
log::info!("Removing excerpts {:?}", excerpts_to_remove);
|
||||
self.remove_excerpts(&excerpts_to_remove, cx);
|
||||
self.remove_excerpts(excerpts_to_remove, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1563,6 +1568,38 @@ impl MultiBuffer {
|
||||
} else {
|
||||
self.randomly_edit_excerpts(rng, mutation_count, cx);
|
||||
}
|
||||
|
||||
self.check_invariants(cx);
|
||||
}
|
||||
|
||||
fn check_invariants(&self, cx: &mut ModelContext<Self>) {
|
||||
let snapshot = self.read(cx);
|
||||
let excerpts = snapshot.excerpts.items(&());
|
||||
let excerpt_ids = snapshot.excerpt_ids.items(&());
|
||||
|
||||
for (ix, excerpt) in excerpts.iter().enumerate() {
|
||||
if ix == 0 {
|
||||
if excerpt.locator <= Locator::min() {
|
||||
panic!("invalid first excerpt locator {:?}", excerpt.locator);
|
||||
}
|
||||
} else {
|
||||
if excerpt.locator <= excerpts[ix - 1].locator {
|
||||
panic!("excerpts are out-of-order: {:?}", excerpts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ix, entry) in excerpt_ids.iter().enumerate() {
|
||||
if ix == 0 {
|
||||
if entry.id.cmp(&ExcerptId::min(), &*snapshot).is_le() {
|
||||
panic!("invalid first excerpt id {:?}", entry.id);
|
||||
}
|
||||
} else {
|
||||
if entry.id <= excerpt_ids[ix - 1].id {
|
||||
panic!("excerpt ids are out-of-order: {:?}", excerpt_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1749,20 +1786,20 @@ impl MultiBufferSnapshot {
|
||||
*cursor.start() + overshoot
|
||||
}
|
||||
|
||||
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
|
||||
pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
|
||||
if let Some((_, _, buffer)) = self.as_singleton() {
|
||||
return buffer.clip_point_utf16(point, bias);
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<PointUtf16>();
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
cursor.seek(&point.0, Bias::Right, &());
|
||||
let overshoot = if let Some(excerpt) = cursor.item() {
|
||||
let excerpt_start = excerpt
|
||||
.buffer
|
||||
.offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer));
|
||||
let buffer_point = excerpt
|
||||
.buffer
|
||||
.clip_point_utf16(excerpt_start + (point - cursor.start()), bias);
|
||||
.clip_point_utf16(Unclipped(excerpt_start + (point.0 - cursor.start())), bias);
|
||||
buffer_point.saturating_sub(excerpt_start)
|
||||
} else {
|
||||
PointUtf16::zero()
|
||||
@@ -2151,7 +2188,9 @@ impl MultiBufferSnapshot {
|
||||
D: TextDimension + Ord + Sub<D, Output = D>,
|
||||
{
|
||||
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
|
||||
cursor.seek(&Some(&anchor.excerpt_id), Bias::Left, &());
|
||||
let locator = self.excerpt_locator_for_id(anchor.excerpt_id);
|
||||
|
||||
cursor.seek(locator, Bias::Left, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.next(&());
|
||||
}
|
||||
@@ -2189,24 +2228,25 @@ impl MultiBufferSnapshot {
|
||||
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
|
||||
let mut summaries = Vec::new();
|
||||
while let Some(anchor) = anchors.peek() {
|
||||
let excerpt_id = &anchor.excerpt_id;
|
||||
let excerpt_id = anchor.excerpt_id;
|
||||
let excerpt_anchors = iter::from_fn(|| {
|
||||
let anchor = anchors.peek()?;
|
||||
if anchor.excerpt_id == *excerpt_id {
|
||||
if anchor.excerpt_id == excerpt_id {
|
||||
Some(&anchors.next().unwrap().text_anchor)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
cursor.seek_forward(&Some(excerpt_id), Bias::Left, &());
|
||||
let locator = self.excerpt_locator_for_id(excerpt_id);
|
||||
cursor.seek_forward(locator, Bias::Left, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
let position = D::from_text_summary(&cursor.start().text);
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
if excerpt.id == *excerpt_id {
|
||||
if excerpt.id == excerpt_id {
|
||||
let excerpt_buffer_start =
|
||||
excerpt.range.context.start.summary::<D>(&excerpt.buffer);
|
||||
let excerpt_buffer_end =
|
||||
@@ -2240,13 +2280,18 @@ impl MultiBufferSnapshot {
|
||||
I: 'a + IntoIterator<Item = &'a Anchor>,
|
||||
{
|
||||
let mut anchors = anchors.into_iter().enumerate().peekable();
|
||||
let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
|
||||
cursor.next(&());
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
while let Some((_, anchor)) = anchors.peek() {
|
||||
let old_excerpt_id = &anchor.excerpt_id;
|
||||
let old_excerpt_id = anchor.excerpt_id;
|
||||
|
||||
// Find the location where this anchor's excerpt should be.
|
||||
cursor.seek_forward(&Some(old_excerpt_id), Bias::Left, &());
|
||||
let old_locator = self.excerpt_locator_for_id(old_excerpt_id);
|
||||
cursor.seek_forward(&Some(old_locator), Bias::Left, &());
|
||||
|
||||
if cursor.item().is_none() {
|
||||
cursor.next(&());
|
||||
}
|
||||
@@ -2256,27 +2301,22 @@ impl MultiBufferSnapshot {
|
||||
|
||||
// Process all of the anchors for this excerpt.
|
||||
while let Some((_, anchor)) = anchors.peek() {
|
||||
if anchor.excerpt_id != *old_excerpt_id {
|
||||
if anchor.excerpt_id != old_excerpt_id {
|
||||
break;
|
||||
}
|
||||
let mut kept_position = false;
|
||||
let (anchor_ix, anchor) = anchors.next().unwrap();
|
||||
let mut anchor = anchor.clone();
|
||||
|
||||
let id_invalid =
|
||||
*old_excerpt_id == ExcerptId::max() || *old_excerpt_id == ExcerptId::min();
|
||||
let still_exists = next_excerpt.map_or(false, |excerpt| {
|
||||
excerpt.id == *old_excerpt_id && excerpt.contains(&anchor)
|
||||
});
|
||||
let mut anchor = *anchor;
|
||||
|
||||
// Leave min and max anchors unchanged if invalid or
|
||||
// if the old excerpt still exists at this location
|
||||
if id_invalid || still_exists {
|
||||
kept_position = true;
|
||||
}
|
||||
let mut kept_position = next_excerpt
|
||||
.map_or(false, |e| e.id == old_excerpt_id && e.contains(&anchor))
|
||||
|| old_excerpt_id == ExcerptId::max()
|
||||
|| old_excerpt_id == ExcerptId::min();
|
||||
|
||||
// If the old excerpt no longer exists at this location, then attempt to
|
||||
// find an equivalent position for this anchor in an adjacent excerpt.
|
||||
else {
|
||||
if !kept_position {
|
||||
for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) {
|
||||
if excerpt.contains(&anchor) {
|
||||
anchor.excerpt_id = excerpt.id.clone();
|
||||
@@ -2285,6 +2325,7 @@ impl MultiBufferSnapshot {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's no adjacent excerpt that contains the anchor's position,
|
||||
// then report that the anchor has lost its position.
|
||||
if !kept_position {
|
||||
@@ -2354,7 +2395,7 @@ impl MultiBufferSnapshot {
|
||||
};
|
||||
}
|
||||
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Option<&ExcerptId>)>();
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Option<ExcerptId>)>();
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
if cursor.item().is_none() && offset == cursor.start().0 && bias == Bias::Left {
|
||||
cursor.prev(&());
|
||||
@@ -2382,8 +2423,9 @@ impl MultiBufferSnapshot {
|
||||
}
|
||||
|
||||
pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor {
|
||||
let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
|
||||
cursor.seek(&Some(&excerpt_id), Bias::Left, &());
|
||||
let locator = self.excerpt_locator_for_id(excerpt_id);
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
|
||||
cursor.seek(locator, Bias::Left, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
if excerpt.id == excerpt_id {
|
||||
let text_anchor = excerpt.clip_anchor(text_anchor);
|
||||
@@ -2401,7 +2443,7 @@ impl MultiBufferSnapshot {
|
||||
pub fn can_resolve(&self, anchor: &Anchor) -> bool {
|
||||
if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() {
|
||||
true
|
||||
} else if let Some(excerpt) = self.excerpt(&anchor.excerpt_id) {
|
||||
} else if let Some(excerpt) = self.excerpt(anchor.excerpt_id) {
|
||||
excerpt.buffer.can_resolve(&anchor.text_anchor)
|
||||
} else {
|
||||
false
|
||||
@@ -2456,7 +2498,6 @@ impl MultiBufferSnapshot {
|
||||
let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id;
|
||||
let boundary = ExcerptBoundary {
|
||||
id: excerpt.id.clone(),
|
||||
key: excerpt.key,
|
||||
row: cursor.start().1.row,
|
||||
buffer: excerpt.buffer.clone(),
|
||||
range: excerpt.range.clone(),
|
||||
@@ -2678,8 +2719,8 @@ impl MultiBufferSnapshot {
|
||||
.flatten()
|
||||
.map(|item| OutlineItem {
|
||||
depth: item.depth,
|
||||
range: self.anchor_in_excerpt(excerpt_id.clone(), item.range.start)
|
||||
..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end),
|
||||
range: self.anchor_in_excerpt(excerpt_id, item.range.start)
|
||||
..self.anchor_in_excerpt(excerpt_id, item.range.end),
|
||||
text: item.text,
|
||||
highlight_ranges: item.highlight_ranges,
|
||||
name_ranges: item.name_ranges,
|
||||
@@ -2688,11 +2729,29 @@ impl MultiBufferSnapshot {
|
||||
))
|
||||
}
|
||||
|
||||
fn excerpt<'a>(&'a self, excerpt_id: &'a ExcerptId) -> Option<&'a Excerpt> {
|
||||
let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
|
||||
cursor.seek(&Some(excerpt_id), Bias::Left, &());
|
||||
fn excerpt_locator_for_id<'a>(&'a self, id: ExcerptId) -> &'a Locator {
|
||||
if id == ExcerptId::min() {
|
||||
Locator::min_ref()
|
||||
} else if id == ExcerptId::max() {
|
||||
Locator::max_ref()
|
||||
} else {
|
||||
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>();
|
||||
cursor.seek(&id, Bias::Left, &());
|
||||
if let Some(entry) = cursor.item() {
|
||||
if entry.id == id {
|
||||
return &entry.locator;
|
||||
}
|
||||
}
|
||||
panic!("invalid excerpt id {:?}", id)
|
||||
}
|
||||
}
|
||||
|
||||
fn excerpt<'a>(&'a self, excerpt_id: ExcerptId) -> Option<&'a Excerpt> {
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
|
||||
let locator = self.excerpt_locator_for_id(excerpt_id);
|
||||
cursor.seek(&Some(locator), Bias::Left, &());
|
||||
if let Some(excerpt) = cursor.item() {
|
||||
if excerpt.id == *excerpt_id {
|
||||
if excerpt.id == excerpt_id {
|
||||
return Some(excerpt);
|
||||
}
|
||||
}
|
||||
@@ -2703,10 +2762,12 @@ impl MultiBufferSnapshot {
|
||||
&'a self,
|
||||
range: &'a Range<Anchor>,
|
||||
) -> impl 'a + Iterator<Item = (ReplicaId, bool, CursorShape, Selection<Anchor>)> {
|
||||
let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
|
||||
cursor.seek(&Some(&range.start.excerpt_id), Bias::Left, &());
|
||||
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
|
||||
let start_locator = self.excerpt_locator_for_id(range.start.excerpt_id);
|
||||
let end_locator = self.excerpt_locator_for_id(range.end.excerpt_id);
|
||||
cursor.seek(start_locator, Bias::Left, &());
|
||||
cursor
|
||||
.take_while(move |excerpt| excerpt.id <= range.end.excerpt_id)
|
||||
.take_while(move |excerpt| excerpt.locator <= *end_locator)
|
||||
.flat_map(move |excerpt| {
|
||||
let mut query_range = excerpt.range.context.start..excerpt.range.context.end;
|
||||
if excerpt.id == range.start.excerpt_id {
|
||||
@@ -2916,7 +2977,7 @@ impl History {
|
||||
impl Excerpt {
|
||||
fn new(
|
||||
id: ExcerptId,
|
||||
key: usize,
|
||||
locator: Locator,
|
||||
buffer_id: usize,
|
||||
buffer: BufferSnapshot,
|
||||
range: ExcerptRange<text::Anchor>,
|
||||
@@ -2924,7 +2985,7 @@ impl Excerpt {
|
||||
) -> Self {
|
||||
Excerpt {
|
||||
id,
|
||||
key,
|
||||
locator,
|
||||
max_buffer_row: range.context.end.to_point(&buffer).row,
|
||||
text_summary: buffer
|
||||
.text_summary_for_range::<TextSummary, _>(range.context.to_offset(&buffer)),
|
||||
@@ -3010,10 +3071,33 @@ impl Excerpt {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExcerptId {
|
||||
pub fn min() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub fn max() -> Self {
|
||||
Self(usize::MAX)
|
||||
}
|
||||
|
||||
pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering {
|
||||
let a = snapshot.excerpt_locator_for_id(*self);
|
||||
let b = snapshot.excerpt_locator_for_id(*other);
|
||||
a.cmp(&b).then_with(|| self.0.cmp(&other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<usize> for ExcerptId {
|
||||
fn into(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Excerpt {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Excerpt")
|
||||
.field("id", &self.id)
|
||||
.field("locator", &self.locator)
|
||||
.field("buffer_id", &self.buffer_id)
|
||||
.field("range", &self.range)
|
||||
.field("text_summary", &self.text_summary)
|
||||
@@ -3031,19 +3115,44 @@ impl sum_tree::Item for Excerpt {
|
||||
text += TextSummary::from("\n");
|
||||
}
|
||||
ExcerptSummary {
|
||||
excerpt_id: self.id.clone(),
|
||||
excerpt_id: self.id,
|
||||
excerpt_locator: self.locator.clone(),
|
||||
max_buffer_row: self.max_buffer_row,
|
||||
text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for ExcerptIdMapping {
|
||||
type Summary = ExcerptId;
|
||||
|
||||
fn summary(&self) -> Self::Summary {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::KeyedItem for ExcerptIdMapping {
|
||||
type Key = ExcerptId;
|
||||
|
||||
fn key(&self) -> Self::Key {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for ExcerptId {
|
||||
type Context = ();
|
||||
|
||||
fn add_summary(&mut self, other: &Self, _: &()) {
|
||||
*self = *other;
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for ExcerptSummary {
|
||||
type Context = ();
|
||||
|
||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||
debug_assert!(summary.excerpt_id > self.excerpt_id);
|
||||
self.excerpt_id = summary.excerpt_id.clone();
|
||||
debug_assert!(summary.excerpt_locator > self.excerpt_locator);
|
||||
self.excerpt_locator = summary.excerpt_locator.clone();
|
||||
self.text.add_summary(&summary.text, &());
|
||||
self.max_buffer_row = cmp::max(self.max_buffer_row, summary.max_buffer_row);
|
||||
}
|
||||
@@ -3067,9 +3176,15 @@ impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Option<&'a ExcerptId> {
|
||||
impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator {
|
||||
fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering {
|
||||
Ord::cmp(&Some(self), cursor_location)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Locator {
|
||||
fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
|
||||
Ord::cmp(self, &Some(&cursor_location.excerpt_id))
|
||||
Ord::cmp(self, &cursor_location.excerpt_locator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3091,9 +3206,15 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a ExcerptId> {
|
||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a Locator> {
|
||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||
*self = Some(&summary.excerpt_id);
|
||||
*self = Some(&summary.excerpt_locator);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<ExcerptId> {
|
||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||
*self = Some(summary.excerpt_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3274,12 +3395,6 @@ impl ToOffset for Point {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for PointUtf16 {
|
||||
fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
|
||||
snapshot.point_utf16_to_offset(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for usize {
|
||||
fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
|
||||
assert!(*self <= snapshot.len(), "offset is out of range");
|
||||
@@ -3293,6 +3408,12 @@ impl ToOffset for OffsetUtf16 {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for PointUtf16 {
|
||||
fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
|
||||
snapshot.point_utf16_to_offset(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffsetUtf16 for OffsetUtf16 {
|
||||
fn to_offset_utf16(&self, _snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
|
||||
*self
|
||||
@@ -3591,7 +3712,7 @@ mod tests {
|
||||
let snapshot = multibuffer.update(cx, |multibuffer, cx| {
|
||||
let (buffer_2_excerpt_id, _) =
|
||||
multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone();
|
||||
multibuffer.remove_excerpts(&[buffer_2_excerpt_id], cx);
|
||||
multibuffer.remove_excerpts([buffer_2_excerpt_id], cx);
|
||||
multibuffer.snapshot(cx)
|
||||
});
|
||||
|
||||
@@ -3780,7 +3901,7 @@ mod tests {
|
||||
|
||||
// Replace the buffer 1 excerpt with new excerpts from buffer 2.
|
||||
let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts([&excerpt_id_1], cx);
|
||||
multibuffer.remove_excerpts([excerpt_id_1], cx);
|
||||
let mut ids = multibuffer
|
||||
.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
@@ -3810,9 +3931,8 @@ mod tests {
|
||||
assert_ne!(excerpt_id_2, excerpt_id_1);
|
||||
|
||||
// Resolve some anchors from the previous snapshot in the new snapshot.
|
||||
// Although there is still an excerpt with the same id, it is for
|
||||
// a different buffer, so we don't attempt to resolve the old text
|
||||
// anchor in the new buffer.
|
||||
// The current excerpts are from a different buffer, so we don't attempt to
|
||||
// resolve the old text anchor in the new buffer.
|
||||
assert_eq!(
|
||||
snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
|
||||
0
|
||||
@@ -3824,6 +3944,9 @@ mod tests {
|
||||
]),
|
||||
vec![0, 0]
|
||||
);
|
||||
|
||||
// Refresh anchors from the old snapshot. The return value indicates that both
|
||||
// anchors lost their original excerpt.
|
||||
let refresh =
|
||||
snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
|
||||
assert_eq!(
|
||||
@@ -3837,10 +3960,10 @@ mod tests {
|
||||
// Replace the middle excerpt with a smaller excerpt in buffer 2,
|
||||
// that intersects the old excerpt.
|
||||
let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts([&excerpt_id_3], cx);
|
||||
multibuffer.remove_excerpts([excerpt_id_3], cx);
|
||||
multibuffer
|
||||
.insert_excerpts_after(
|
||||
&excerpt_id_3,
|
||||
excerpt_id_2,
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange {
|
||||
context: 5..8,
|
||||
@@ -3857,8 +3980,8 @@ mod tests {
|
||||
assert_ne!(excerpt_id_5, excerpt_id_3);
|
||||
|
||||
// Resolve some anchors from the previous snapshot in the new snapshot.
|
||||
// The anchor in the middle excerpt snaps to the beginning of the
|
||||
// excerpt, since it is not
|
||||
// The third anchor can't be resolved, since its excerpt has been removed,
|
||||
// so it resolves to the same position as its predecessor.
|
||||
let anchors = [
|
||||
snapshot_2.anchor_before(0),
|
||||
snapshot_2.anchor_after(2),
|
||||
@@ -3867,7 +3990,7 @@ mod tests {
|
||||
];
|
||||
assert_eq!(
|
||||
snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
|
||||
&[0, 2, 5, 13]
|
||||
&[0, 2, 9, 13]
|
||||
);
|
||||
|
||||
let new_anchors = snapshot_3.refresh_anchors(&anchors);
|
||||
@@ -3889,7 +4012,7 @@ mod tests {
|
||||
|
||||
let mut buffers: Vec<ModelHandle<Buffer>> = Vec::new();
|
||||
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
|
||||
let mut excerpt_ids = Vec::new();
|
||||
let mut excerpt_ids = Vec::<ExcerptId>::new();
|
||||
let mut expected_excerpts = Vec::<(ModelHandle<Buffer>, Range<text::Anchor>)>::new();
|
||||
let mut anchors = Vec::new();
|
||||
let mut old_versions = Vec::new();
|
||||
@@ -3919,9 +4042,11 @@ mod tests {
|
||||
.collect::<String>(),
|
||||
);
|
||||
}
|
||||
ids_to_remove.sort_unstable();
|
||||
let snapshot = multibuffer.read(cx).read(cx);
|
||||
ids_to_remove.sort_unstable_by(|a, b| a.cmp(&b, &snapshot));
|
||||
drop(snapshot);
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts(&ids_to_remove, cx)
|
||||
multibuffer.remove_excerpts(ids_to_remove, cx)
|
||||
});
|
||||
}
|
||||
30..=39 if !expected_excerpts.is_empty() => {
|
||||
@@ -3945,7 +4070,6 @@ mod tests {
|
||||
// Ensure the newly-refreshed anchors point to a valid excerpt and don't
|
||||
// overshoot its boundaries.
|
||||
assert_eq!(anchors.len(), prev_len);
|
||||
let mut cursor = multibuffer.excerpts.cursor::<Option<&ExcerptId>>();
|
||||
for anchor in &anchors {
|
||||
if anchor.excerpt_id == ExcerptId::min()
|
||||
|| anchor.excerpt_id == ExcerptId::max()
|
||||
@@ -3953,8 +4077,7 @@ mod tests {
|
||||
continue;
|
||||
}
|
||||
|
||||
cursor.seek_forward(&Some(&anchor.excerpt_id), Bias::Left, &());
|
||||
let excerpt = cursor.item().unwrap();
|
||||
let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
|
||||
assert_eq!(excerpt.id, anchor.excerpt_id);
|
||||
assert!(excerpt.contains(anchor));
|
||||
}
|
||||
@@ -3994,7 +4117,7 @@ mod tests {
|
||||
let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer
|
||||
.insert_excerpts_after(
|
||||
&prev_excerpt_id,
|
||||
prev_excerpt_id,
|
||||
buffer_handle.clone(),
|
||||
[ExcerptRange {
|
||||
context: start_ix..end_ix,
|
||||
@@ -4158,12 +4281,14 @@ mod tests {
|
||||
}
|
||||
|
||||
for _ in 0..ch.len_utf16() {
|
||||
let left_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Left);
|
||||
let right_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Right);
|
||||
let left_point_utf16 =
|
||||
snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Left);
|
||||
let right_point_utf16 =
|
||||
snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Right);
|
||||
let buffer_left_point_utf16 =
|
||||
buffer.clip_point_utf16(buffer_point_utf16, Bias::Left);
|
||||
buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Left);
|
||||
let buffer_right_point_utf16 =
|
||||
buffer.clip_point_utf16(buffer_point_utf16, Bias::Right);
|
||||
buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Right);
|
||||
assert_eq!(
|
||||
left_point_utf16,
|
||||
excerpt_start.lines_utf16()
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
};
|
||||
use sum_tree::Bias;
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
|
||||
pub struct Anchor {
|
||||
pub(crate) buffer_id: Option<usize>,
|
||||
pub(crate) excerpt_id: ExcerptId,
|
||||
@@ -30,16 +30,16 @@ impl Anchor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn excerpt_id(&self) -> &ExcerptId {
|
||||
&self.excerpt_id
|
||||
pub fn excerpt_id(&self) -> ExcerptId {
|
||||
self.excerpt_id
|
||||
}
|
||||
|
||||
pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
|
||||
let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id);
|
||||
let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot);
|
||||
if excerpt_id_cmp.is_eq() {
|
||||
if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() {
|
||||
Ordering::Equal
|
||||
} else if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) {
|
||||
} else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
|
||||
self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer)
|
||||
} else {
|
||||
Ordering::Equal
|
||||
@@ -51,7 +51,7 @@ impl Anchor {
|
||||
|
||||
pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
|
||||
if self.text_anchor.bias != Bias::Left {
|
||||
if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) {
|
||||
if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
|
||||
return Self {
|
||||
buffer_id: self.buffer_id,
|
||||
excerpt_id: self.excerpt_id.clone(),
|
||||
@@ -64,7 +64,7 @@ impl Anchor {
|
||||
|
||||
pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
|
||||
if self.text_anchor.bias != Bias::Right {
|
||||
if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) {
|
||||
if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
|
||||
return Self {
|
||||
buffer_id: self.buffer_id,
|
||||
excerpt_id: self.excerpt_id.clone(),
|
||||
|
||||
@@ -544,11 +544,21 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
T: ToOffset,
|
||||
{
|
||||
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||
let ranges = ranges
|
||||
.into_iter()
|
||||
.map(|range| range.start.to_offset(&buffer)..range.end.to_offset(&buffer));
|
||||
self.select_offset_ranges(ranges);
|
||||
}
|
||||
|
||||
fn select_offset_ranges<I>(&mut self, ranges: I)
|
||||
where
|
||||
I: IntoIterator<Item = Range<usize>>,
|
||||
{
|
||||
let selections = ranges
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let mut start = range.start.to_offset(&buffer);
|
||||
let mut end = range.end.to_offset(&buffer);
|
||||
let mut start = range.start;
|
||||
let mut end = range.end;
|
||||
let reversed = if start > end {
|
||||
mem::swap(&mut start, &mut end);
|
||||
true
|
||||
|
||||
@@ -2225,11 +2225,12 @@ impl BufferSnapshot {
|
||||
range: Range<T>,
|
||||
) -> Option<(Range<usize>, Range<usize>)> {
|
||||
// Find bracket pairs that *inclusively* contain the given range.
|
||||
let range = range.start.to_offset(self).saturating_sub(1)
|
||||
..self.len().min(range.end.to_offset(self) + 1);
|
||||
let mut matches = self.syntax.matches(range, &self.text, |grammar| {
|
||||
grammar.brackets_config.as_ref().map(|c| &c.query)
|
||||
});
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let mut matches = self.syntax.matches(
|
||||
range.start.saturating_sub(1)..self.len().min(range.end + 1),
|
||||
&self.text,
|
||||
|grammar| grammar.brackets_config.as_ref().map(|c| &c.query),
|
||||
);
|
||||
let configs = matches
|
||||
.grammars()
|
||||
.iter()
|
||||
@@ -2252,18 +2253,20 @@ impl BufferSnapshot {
|
||||
|
||||
matches.advance();
|
||||
|
||||
if let Some((open, close)) = open.zip(close) {
|
||||
let len = close.end - open.start;
|
||||
|
||||
if let Some((existing_open, existing_close)) = &result {
|
||||
let existing_len = existing_close.end - existing_open.start;
|
||||
if len > existing_len {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
result = Some((open, close));
|
||||
let Some((open, close)) = open.zip(close) else { continue };
|
||||
if open.start > range.start || close.end < range.end {
|
||||
continue;
|
||||
}
|
||||
let len = close.end - open.start;
|
||||
|
||||
if let Some((existing_open, existing_close)) = &result {
|
||||
let existing_len = existing_close.end - existing_open.start;
|
||||
if len > existing_len {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
result = Some((open, close));
|
||||
}
|
||||
|
||||
result
|
||||
|
||||
@@ -573,14 +573,72 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
|
||||
))
|
||||
);
|
||||
|
||||
// Regression test: avoid crash when querying at the end of the buffer.
|
||||
assert_eq!(
|
||||
buffer.enclosing_bracket_point_ranges(buffer.len() - 1..buffer.len()),
|
||||
buffer.enclosing_bracket_point_ranges(Point::new(4, 1)..Point::new(4, 1)),
|
||||
Some((
|
||||
Point::new(0, 6)..Point::new(0, 7),
|
||||
Point::new(4, 0)..Point::new(4, 1)
|
||||
))
|
||||
);
|
||||
|
||||
// Regression test: avoid crash when querying at the end of the buffer.
|
||||
assert_eq!(
|
||||
buffer.enclosing_bracket_point_ranges(Point::new(4, 1)..Point::new(5, 0)),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(
|
||||
cx: &mut MutableAppContext,
|
||||
) {
|
||||
let javascript_language = Arc::new(
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "JavaScript".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
)
|
||||
.with_brackets_query(
|
||||
r#"
|
||||
("{" @open "}" @close)
|
||||
("(" @open ")" @close)
|
||||
"#,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
cx.set_global(Settings::test(cx));
|
||||
let buffer = cx.add_model(|cx| {
|
||||
let text = "
|
||||
for (const a in b) {
|
||||
// a comment that's longer than the for-loop header
|
||||
}
|
||||
"
|
||||
.unindent();
|
||||
Buffer::new(0, text, cx).with_language(javascript_language, cx)
|
||||
});
|
||||
|
||||
let buffer = buffer.read(cx);
|
||||
assert_eq!(
|
||||
buffer.enclosing_bracket_point_ranges(Point::new(0, 18)..Point::new(0, 18)),
|
||||
Some((
|
||||
Point::new(0, 4)..Point::new(0, 5),
|
||||
Point::new(0, 17)..Point::new(0, 18)
|
||||
))
|
||||
);
|
||||
|
||||
// Regression test: even though the parent node of the parentheses (the for loop) does
|
||||
// intersect the given range, the parentheses themselves do not contain the range, so
|
||||
// they should not be returned. Only the curly braces contain the range.
|
||||
assert_eq!(
|
||||
buffer.enclosing_bracket_point_ranges(Point::new(0, 20)..Point::new(0, 20)),
|
||||
Some((
|
||||
Point::new(0, 19)..Point::new(0, 20),
|
||||
Point::new(2, 0)..Point::new(2, 1)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -1337,6 +1395,7 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
|
||||
(0..entry_count).map(|_| {
|
||||
let range = buffer.random_byte_range(0, &mut rng);
|
||||
let range = range.to_point_utf16(buffer);
|
||||
let range = range.start..range.end;
|
||||
DiagnosticEntry {
|
||||
range,
|
||||
diagnostic: Diagnostic {
|
||||
|
||||
@@ -71,7 +71,7 @@ impl DiagnosticSet {
|
||||
diagnostics: SumTree::from_iter(
|
||||
entries.into_iter().map(|entry| DiagnosticEntry {
|
||||
range: buffer.anchor_before(entry.range.start)
|
||||
..buffer.anchor_after(entry.range.end),
|
||||
..buffer.anchor_before(entry.range.end),
|
||||
diagnostic: entry.diagnostic,
|
||||
}),
|
||||
buffer,
|
||||
|
||||
@@ -1053,8 +1053,8 @@ pub fn point_to_lsp(point: PointUtf16) -> lsp::Position {
|
||||
lsp::Position::new(point.row, point.column)
|
||||
}
|
||||
|
||||
pub fn point_from_lsp(point: lsp::Position) -> PointUtf16 {
|
||||
PointUtf16::new(point.line, point.character)
|
||||
pub fn point_from_lsp(point: lsp::Position) -> Unclipped<PointUtf16> {
|
||||
Unclipped(PointUtf16::new(point.line, point.character))
|
||||
}
|
||||
|
||||
pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range {
|
||||
@@ -1064,7 +1064,7 @@ pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
|
||||
pub fn range_from_lsp(range: lsp::Range) -> Range<Unclipped<PointUtf16>> {
|
||||
let mut start = point_from_lsp(range.start);
|
||||
let mut end = point_from_lsp(range.end);
|
||||
if start > end {
|
||||
|
||||
@@ -128,8 +128,8 @@ impl LspCommand for PrepareRename {
|
||||
) = message
|
||||
{
|
||||
let Range { start, end } = range_from_lsp(range);
|
||||
if buffer.clip_point_utf16(start, Bias::Left) == start
|
||||
&& buffer.clip_point_utf16(end, Bias::Left) == end
|
||||
if buffer.clip_point_utf16(start, Bias::Left) == start.0
|
||||
&& buffer.clip_point_utf16(end, Bias::Left) == end.0
|
||||
{
|
||||
return Ok(Some(buffer.anchor_after(start)..buffer.anchor_before(end)));
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ use language::{
|
||||
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent,
|
||||
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt,
|
||||
Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
|
||||
Unclipped,
|
||||
};
|
||||
use lsp::{
|
||||
DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString,
|
||||
@@ -252,7 +253,7 @@ pub struct Symbol {
|
||||
pub label: CodeLabel,
|
||||
pub name: String,
|
||||
pub kind: lsp::SymbolKind,
|
||||
pub range: Range<PointUtf16>,
|
||||
pub range: Range<Unclipped<PointUtf16>>,
|
||||
pub signature: [u8; 32],
|
||||
}
|
||||
|
||||
@@ -2597,7 +2598,7 @@ impl Project {
|
||||
language_server_id: usize,
|
||||
abs_path: PathBuf,
|
||||
version: Option<i32>,
|
||||
diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
|
||||
diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||
cx: &mut ModelContext<Project>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let (worktree, relative_path) = self
|
||||
@@ -2635,7 +2636,7 @@ impl Project {
|
||||
fn update_buffer_diagnostics(
|
||||
&mut self,
|
||||
buffer: &ModelHandle<Buffer>,
|
||||
mut diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
|
||||
mut diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||
version: Option<i32>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
@@ -2659,7 +2660,7 @@ impl Project {
|
||||
let mut sanitized_diagnostics = Vec::new();
|
||||
let edits_since_save = Patch::new(
|
||||
snapshot
|
||||
.edits_since::<PointUtf16>(buffer.read(cx).saved_version())
|
||||
.edits_since::<Unclipped<PointUtf16>>(buffer.read(cx).saved_version())
|
||||
.collect(),
|
||||
);
|
||||
for entry in diagnostics {
|
||||
@@ -2679,13 +2680,14 @@ impl Project {
|
||||
let mut range = snapshot.clip_point_utf16(start, Bias::Left)
|
||||
..snapshot.clip_point_utf16(end, Bias::Right);
|
||||
|
||||
// Expand empty ranges by one character
|
||||
// Expand empty ranges by one codepoint
|
||||
if range.start == range.end {
|
||||
// This will be go to the next boundary when being clipped
|
||||
range.end.column += 1;
|
||||
range.end = snapshot.clip_point_utf16(range.end, Bias::Right);
|
||||
range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Right);
|
||||
if range.start == range.end && range.end.column > 0 {
|
||||
range.start.column -= 1;
|
||||
range.start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Left);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3288,7 +3290,7 @@ impl Project {
|
||||
return Task::ready(Ok(Default::default()));
|
||||
};
|
||||
|
||||
let position = position.to_point_utf16(source_buffer);
|
||||
let position = Unclipped(position.to_point_utf16(source_buffer));
|
||||
let anchor = source_buffer.anchor_after(position);
|
||||
|
||||
if worktree.read(cx).as_local().is_some() {
|
||||
@@ -3307,7 +3309,7 @@ impl Project {
|
||||
lsp::TextDocumentIdentifier::new(
|
||||
lsp::Url::from_file_path(buffer_abs_path).unwrap(),
|
||||
),
|
||||
point_to_lsp(position),
|
||||
point_to_lsp(position.0),
|
||||
),
|
||||
context: Default::default(),
|
||||
work_done_progress_params: Default::default(),
|
||||
@@ -3350,7 +3352,7 @@ impl Project {
|
||||
let range = range_from_lsp(edit.range);
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start || end != range.end {
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
}
|
||||
@@ -3362,7 +3364,7 @@ impl Project {
|
||||
// If the language server does not provide a range, then infer
|
||||
// the range based on the syntax tree.
|
||||
None => {
|
||||
if position != clipped_position {
|
||||
if position.0 != clipped_position {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
}
|
||||
@@ -5117,22 +5119,30 @@ impl Project {
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::GetCompletionsResponse> {
|
||||
let position = envelope
|
||||
.payload
|
||||
.position
|
||||
.and_then(language::proto::deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid position"))?;
|
||||
let version = deserialize_version(envelope.payload.version);
|
||||
let buffer = this.read_with(&cx, |this, cx| {
|
||||
this.opened_buffers
|
||||
.get(&envelope.payload.buffer_id)
|
||||
.and_then(|buffer| buffer.upgrade(cx))
|
||||
.ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
|
||||
})?;
|
||||
|
||||
let position = envelope
|
||||
.payload
|
||||
.position
|
||||
.and_then(language::proto::deserialize_anchor)
|
||||
.map(|p| {
|
||||
buffer.read_with(&cx, |buffer, _| {
|
||||
buffer.clip_point_utf16(Unclipped(p.to_point_utf16(buffer)), Bias::Left)
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| anyhow!("invalid position"))?;
|
||||
|
||||
let version = deserialize_version(envelope.payload.version);
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| buffer.wait_for_version(version))
|
||||
.await;
|
||||
let version = buffer.read_with(&cx, |buffer, _| buffer.version());
|
||||
|
||||
let completions = this
|
||||
.update(&mut cx, |this, cx| this.completions(&buffer, position, cx))
|
||||
.await?;
|
||||
@@ -5619,8 +5629,8 @@ impl Project {
|
||||
},
|
||||
|
||||
name: serialized_symbol.name,
|
||||
range: PointUtf16::new(start.row, start.column)
|
||||
..PointUtf16::new(end.row, end.column),
|
||||
range: Unclipped(PointUtf16::new(start.row, start.column))
|
||||
..Unclipped(PointUtf16::new(end.row, end.column)),
|
||||
kind,
|
||||
signature: serialized_symbol
|
||||
.signature
|
||||
@@ -5706,10 +5716,10 @@ impl Project {
|
||||
|
||||
let mut lsp_edits = lsp_edits.into_iter().peekable();
|
||||
let mut edits = Vec::new();
|
||||
while let Some((mut range, mut new_text)) = lsp_edits.next() {
|
||||
while let Some((range, mut new_text)) = lsp_edits.next() {
|
||||
// Clip invalid ranges provided by the language server.
|
||||
range.start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
range.end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
let mut range = snapshot.clip_point_utf16(range.start, Bias::Left)
|
||||
..snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
|
||||
// Combine any LSP edits that are adjacent.
|
||||
//
|
||||
@@ -5721,11 +5731,11 @@ impl Project {
|
||||
// In order for the diffing logic below to work properly, any edits that
|
||||
// cancel each other out must be combined into one.
|
||||
while let Some((next_range, next_text)) = lsp_edits.peek() {
|
||||
if next_range.start > range.end {
|
||||
if next_range.start.row > range.end.row + 1
|
||||
|| next_range.start.column > 0
|
||||
if next_range.start.0 > range.end {
|
||||
if next_range.start.0.row > range.end.row + 1
|
||||
|| next_range.start.0.column > 0
|
||||
|| snapshot.clip_point_utf16(
|
||||
PointUtf16::new(range.end.row, u32::MAX),
|
||||
Unclipped(PointUtf16::new(range.end.row, u32::MAX)),
|
||||
Bias::Left,
|
||||
) > range.end
|
||||
{
|
||||
@@ -5733,7 +5743,7 @@ impl Project {
|
||||
}
|
||||
new_text.push('\n');
|
||||
}
|
||||
range.end = next_range.end;
|
||||
range.end = snapshot.clip_point_utf16(next_range.end, Bias::Left);
|
||||
new_text.push_str(next_text);
|
||||
lsp_edits.next();
|
||||
}
|
||||
@@ -6054,13 +6064,13 @@ fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
|
||||
path: symbol.path.path.to_string_lossy().to_string(),
|
||||
name: symbol.name.clone(),
|
||||
kind: unsafe { mem::transmute(symbol.kind) },
|
||||
start: Some(proto::Point {
|
||||
row: symbol.range.start.row,
|
||||
column: symbol.range.start.column,
|
||||
start: Some(proto::PointUtf16 {
|
||||
row: symbol.range.start.0.row,
|
||||
column: symbol.range.start.0.column,
|
||||
}),
|
||||
end: Some(proto::Point {
|
||||
row: symbol.range.end.row,
|
||||
column: symbol.range.end.column,
|
||||
end: Some(proto::PointUtf16 {
|
||||
row: symbol.range.end.0.row,
|
||||
column: symbol.range.end.0.column,
|
||||
}),
|
||||
signature: symbol.signature.to_vec(),
|
||||
}
|
||||
|
||||
@@ -1239,7 +1239,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
|
||||
&buffer,
|
||||
vec![
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(0, 10)..PointUtf16::new(0, 10),
|
||||
range: Unclipped(PointUtf16::new(0, 10))..Unclipped(PointUtf16::new(0, 10)),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "syntax error 1".to_string(),
|
||||
@@ -1247,7 +1247,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
|
||||
},
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: PointUtf16::new(1, 10)..PointUtf16::new(1, 10),
|
||||
range: Unclipped(PointUtf16::new(1, 10))..Unclipped(PointUtf16::new(1, 10)),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "syntax error 2".to_string(),
|
||||
|
||||
@@ -20,6 +20,7 @@ use gpui::{
|
||||
executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
|
||||
Task,
|
||||
};
|
||||
use language::Unclipped;
|
||||
use language::{
|
||||
proto::{deserialize_version, serialize_line_ending, serialize_version},
|
||||
Buffer, DiagnosticEntry, PointUtf16, Rope,
|
||||
@@ -65,7 +66,7 @@ pub struct LocalWorktree {
|
||||
_background_scanner_task: Option<Task<()>>,
|
||||
poll_task: Option<Task<()>>,
|
||||
share: Option<ShareState>,
|
||||
diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<PointUtf16>>>,
|
||||
diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<Unclipped<PointUtf16>>>>,
|
||||
diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
|
||||
client: Arc<Client>,
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -499,7 +500,10 @@ impl LocalWorktree {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn diagnostics_for_path(&self, path: &Path) -> Option<Vec<DiagnosticEntry<PointUtf16>>> {
|
||||
pub fn diagnostics_for_path(
|
||||
&self,
|
||||
path: &Path,
|
||||
) -> Option<Vec<DiagnosticEntry<Unclipped<PointUtf16>>>> {
|
||||
self.diagnostics.get(path).cloned()
|
||||
}
|
||||
|
||||
@@ -507,7 +511,7 @@ impl LocalWorktree {
|
||||
&mut self,
|
||||
language_server_id: usize,
|
||||
worktree_path: Arc<Path>,
|
||||
diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
|
||||
diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||
_: &mut ModelContext<Worktree>,
|
||||
) -> Result<bool> {
|
||||
self.diagnostics.remove(&worktree_path);
|
||||
|
||||
@@ -12,7 +12,7 @@ smallvec = { version = "1.6", features = ["union"] }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
arrayvec = "0.7.1"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
|
||||
util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.3"
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
mod offset_utf16;
|
||||
mod point;
|
||||
mod point_utf16;
|
||||
mod unclipped;
|
||||
|
||||
use arrayvec::ArrayString;
|
||||
use bromberg_sl2::{DigestString, HashMatrix};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp, fmt, io, mem, ops::Range, str};
|
||||
use std::{
|
||||
cmp, fmt, io, mem,
|
||||
ops::{AddAssign, Range},
|
||||
str,
|
||||
};
|
||||
use sum_tree::{Bias, Dimension, SumTree};
|
||||
use util::debug_panic;
|
||||
|
||||
pub use offset_utf16::OffsetUtf16;
|
||||
pub use point::Point;
|
||||
pub use point_utf16::PointUtf16;
|
||||
pub use unclipped::Unclipped;
|
||||
|
||||
#[cfg(test)]
|
||||
const CHUNK_BASE: usize = 6;
|
||||
@@ -260,6 +267,14 @@ impl Rope {
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
|
||||
self.point_utf16_to_offset_impl(point, false)
|
||||
}
|
||||
|
||||
pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped<PointUtf16>) -> usize {
|
||||
self.point_utf16_to_offset_impl(point.0, true)
|
||||
}
|
||||
|
||||
fn point_utf16_to_offset_impl(&self, point: PointUtf16, clip: bool) -> usize {
|
||||
if point >= self.summary().lines_utf16() {
|
||||
return self.summary().len;
|
||||
}
|
||||
@@ -269,20 +284,20 @@ impl Rope {
|
||||
cursor.start().1
|
||||
+ cursor
|
||||
.item()
|
||||
.map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot))
|
||||
.map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot, clip))
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point {
|
||||
if point >= self.summary().lines_utf16() {
|
||||
pub fn unclipped_point_utf16_to_point(&self, point: Unclipped<PointUtf16>) -> Point {
|
||||
if point.0 >= self.summary().lines_utf16() {
|
||||
return self.summary().lines;
|
||||
}
|
||||
let mut cursor = self.chunks.cursor::<(PointUtf16, Point)>();
|
||||
cursor.seek(&point, Bias::Left, &());
|
||||
let overshoot = point - cursor.start().0;
|
||||
cursor.seek(&point.0, Bias::Left, &());
|
||||
let overshoot = Unclipped(point.0 - cursor.start().0);
|
||||
cursor.start().1
|
||||
+ cursor
|
||||
.item()
|
||||
.map_or(Point::zero(), |chunk| chunk.point_utf16_to_point(overshoot))
|
||||
+ cursor.item().map_or(Point::zero(), |chunk| {
|
||||
chunk.unclipped_point_utf16_to_point(overshoot)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize {
|
||||
@@ -330,11 +345,11 @@ impl Rope {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
|
||||
pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
|
||||
let mut cursor = self.chunks.cursor::<PointUtf16>();
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
cursor.seek(&point.0, Bias::Right, &());
|
||||
if let Some(chunk) = cursor.item() {
|
||||
let overshoot = point - cursor.start();
|
||||
let overshoot = Unclipped(point.0 - cursor.start());
|
||||
*cursor.start() + chunk.clip_point_utf16(overshoot, bias)
|
||||
} else {
|
||||
self.summary().lines_utf16()
|
||||
@@ -665,28 +680,33 @@ impl Chunk {
|
||||
fn point_to_offset(&self, target: Point) -> usize {
|
||||
let mut offset = 0;
|
||||
let mut point = Point::new(0, 0);
|
||||
|
||||
for ch in self.0.chars() {
|
||||
if point >= target {
|
||||
if point > target {
|
||||
panic!("point {:?} is inside of character {:?}", target, ch);
|
||||
debug_panic!("point {target:?} is inside of character {ch:?}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ch == '\n' {
|
||||
point.row += 1;
|
||||
if point.row > target.row {
|
||||
panic!(
|
||||
"point {:?} is beyond the end of a line with length {}",
|
||||
target, point.column
|
||||
);
|
||||
}
|
||||
point.column = 0;
|
||||
|
||||
if point.row > target.row {
|
||||
debug_panic!(
|
||||
"point {target:?} is beyond the end of a line with length {}",
|
||||
point.column
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
point.column += ch.len_utf8() as u32;
|
||||
}
|
||||
|
||||
offset += ch.len_utf8();
|
||||
}
|
||||
|
||||
offset
|
||||
}
|
||||
|
||||
@@ -711,45 +731,62 @@ impl Chunk {
|
||||
point_utf16
|
||||
}
|
||||
|
||||
fn point_utf16_to_offset(&self, target: PointUtf16) -> usize {
|
||||
fn point_utf16_to_offset(&self, target: PointUtf16, clip: bool) -> usize {
|
||||
let mut offset = 0;
|
||||
let mut point = PointUtf16::new(0, 0);
|
||||
|
||||
for ch in self.0.chars() {
|
||||
if point >= target {
|
||||
if point > target {
|
||||
panic!("point {:?} is inside of character {:?}", target, ch);
|
||||
}
|
||||
if point == target {
|
||||
break;
|
||||
}
|
||||
|
||||
if ch == '\n' {
|
||||
point.row += 1;
|
||||
if point.row > target.row {
|
||||
panic!(
|
||||
"point {:?} is beyond the end of a line with length {}",
|
||||
target, point.column
|
||||
);
|
||||
}
|
||||
point.column = 0;
|
||||
|
||||
if point.row > target.row {
|
||||
if !clip {
|
||||
debug_panic!(
|
||||
"point {target:?} is beyond the end of a line with length {}",
|
||||
point.column
|
||||
);
|
||||
}
|
||||
// Return the offset of the newline
|
||||
return offset;
|
||||
}
|
||||
} else {
|
||||
point.column += ch.len_utf16() as u32;
|
||||
}
|
||||
|
||||
if point > target {
|
||||
if !clip {
|
||||
debug_panic!("point {target:?} is inside of codepoint {ch:?}");
|
||||
}
|
||||
// Return the offset of the codepoint which we have landed within, bias left
|
||||
return offset;
|
||||
}
|
||||
|
||||
offset += ch.len_utf8();
|
||||
}
|
||||
|
||||
offset
|
||||
}
|
||||
|
||||
fn point_utf16_to_point(&self, target: PointUtf16) -> Point {
|
||||
fn unclipped_point_utf16_to_point(&self, target: Unclipped<PointUtf16>) -> Point {
|
||||
let mut point = Point::zero();
|
||||
let mut point_utf16 = PointUtf16::zero();
|
||||
|
||||
for ch in self.0.chars() {
|
||||
if point_utf16 >= target {
|
||||
if point_utf16 > target {
|
||||
panic!("point {:?} is inside of character {:?}", target, ch);
|
||||
}
|
||||
if point_utf16 == target.0 {
|
||||
break;
|
||||
}
|
||||
|
||||
if point_utf16 > target.0 {
|
||||
// If the point is past the end of a line or inside of a code point,
|
||||
// return the last valid point before the target.
|
||||
return point;
|
||||
}
|
||||
|
||||
if ch == '\n' {
|
||||
point_utf16 += PointUtf16::new(1, 0);
|
||||
point += Point::new(1, 0);
|
||||
@@ -758,6 +795,7 @@ impl Chunk {
|
||||
point += Point::new(0, ch.len_utf8() as u32);
|
||||
}
|
||||
}
|
||||
|
||||
point
|
||||
}
|
||||
|
||||
@@ -777,11 +815,11 @@ impl Chunk {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn clip_point_utf16(&self, target: PointUtf16, bias: Bias) -> PointUtf16 {
|
||||
fn clip_point_utf16(&self, target: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
|
||||
for (row, line) in self.0.split('\n').enumerate() {
|
||||
if row == target.row as usize {
|
||||
if row == target.0.row as usize {
|
||||
let mut code_units = line.encode_utf16();
|
||||
let mut column = code_units.by_ref().take(target.column as usize).count();
|
||||
let mut column = code_units.by_ref().take(target.0.column as usize).count();
|
||||
if char::decode_utf16(code_units).next().transpose().is_err() {
|
||||
match bias {
|
||||
Bias::Left => column -= 1,
|
||||
@@ -917,7 +955,7 @@ impl std::ops::Add<Self> for TextSummary {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, rhs: Self) -> Self::Output {
|
||||
self.add_assign(&rhs);
|
||||
AddAssign::add_assign(&mut self, &rhs);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1114,15 +1152,15 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rope.clip_point_utf16(PointUtf16::new(0, 1), Bias::Left),
|
||||
rope.clip_point_utf16(Unclipped(PointUtf16::new(0, 1)), Bias::Left),
|
||||
PointUtf16::new(0, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
rope.clip_point_utf16(PointUtf16::new(0, 1), Bias::Right),
|
||||
rope.clip_point_utf16(Unclipped(PointUtf16::new(0, 1)), Bias::Right),
|
||||
PointUtf16::new(0, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
rope.clip_point_utf16(PointUtf16::new(0, 3), Bias::Right),
|
||||
rope.clip_point_utf16(Unclipped(PointUtf16::new(0, 3)), Bias::Right),
|
||||
PointUtf16::new(0, 2)
|
||||
);
|
||||
|
||||
@@ -1238,7 +1276,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let mut offset_utf16 = OffsetUtf16(0);
|
||||
let mut point_utf16 = PointUtf16::zero();
|
||||
let mut point_utf16 = Unclipped(PointUtf16::zero());
|
||||
for unit in expected.encode_utf16() {
|
||||
let left_offset = actual.clip_offset_utf16(offset_utf16, Bias::Left);
|
||||
let right_offset = actual.clip_offset_utf16(offset_utf16, Bias::Right);
|
||||
@@ -1250,15 +1288,15 @@ mod tests {
|
||||
let left_point = actual.clip_point_utf16(point_utf16, Bias::Left);
|
||||
let right_point = actual.clip_point_utf16(point_utf16, Bias::Right);
|
||||
assert!(right_point >= left_point);
|
||||
// Ensure translating UTF-16 points to offsets doesn't panic.
|
||||
// Ensure translating valid UTF-16 points to offsets doesn't panic.
|
||||
actual.point_utf16_to_offset(left_point);
|
||||
actual.point_utf16_to_offset(right_point);
|
||||
|
||||
offset_utf16.0 += 1;
|
||||
if unit == b'\n' as u16 {
|
||||
point_utf16 += PointUtf16::new(1, 0);
|
||||
point_utf16.0 += PointUtf16::new(1, 0);
|
||||
} else {
|
||||
point_utf16 += PointUtf16::new(0, 1);
|
||||
point_utf16.0 += PointUtf16::new(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
57
crates/rope/src/unclipped.rs
Normal file
57
crates/rope/src/unclipped.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use crate::{ChunkSummary, TextDimension, TextSummary};
|
||||
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Unclipped<T>(pub T);
|
||||
|
||||
impl<T> From<T> for Unclipped<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Unclipped(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: sum_tree::Dimension<'a, ChunkSummary>> sum_tree::Dimension<'a, ChunkSummary>
|
||||
for Unclipped<T>
|
||||
{
|
||||
fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
|
||||
self.0.add_summary(summary, &());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TextDimension> TextDimension for Unclipped<T> {
|
||||
fn from_text_summary(summary: &TextSummary) -> Self {
|
||||
Unclipped(T::from_text_summary(summary))
|
||||
}
|
||||
|
||||
fn add_assign(&mut self, other: &Self) {
|
||||
TextDimension::add_assign(&mut self.0, &other.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Add<T, Output = T>> Add<Unclipped<T>> for Unclipped<T> {
|
||||
type Output = Unclipped<T>;
|
||||
|
||||
fn add(self, rhs: Unclipped<T>) -> Self::Output {
|
||||
Unclipped(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sub<T, Output = T>> Sub<Unclipped<T>> for Unclipped<T> {
|
||||
type Output = Unclipped<T>;
|
||||
|
||||
fn sub(self, rhs: Unclipped<T>) -> Self::Output {
|
||||
Unclipped(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AddAssign<T>> AddAssign<Unclipped<T>> for Unclipped<T> {
|
||||
fn add_assign(&mut self, rhs: Unclipped<T>) {
|
||||
self.0 += rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SubAssign<T>> SubAssign<Unclipped<T>> for Unclipped<T> {
|
||||
fn sub_assign(&mut self, rhs: Unclipped<T>) {
|
||||
self.0 -= rhs.0;
|
||||
}
|
||||
}
|
||||
@@ -412,8 +412,10 @@ message Symbol {
|
||||
string name = 4;
|
||||
int32 kind = 5;
|
||||
string path = 6;
|
||||
Point start = 7;
|
||||
Point end = 8;
|
||||
// Cannot use generate anchors for unopend files,
|
||||
// so we are forced to use point coords instead
|
||||
PointUtf16 start = 7;
|
||||
PointUtf16 end = 8;
|
||||
bytes signature = 9;
|
||||
}
|
||||
|
||||
@@ -1042,7 +1044,7 @@ message Range {
|
||||
uint64 end = 2;
|
||||
}
|
||||
|
||||
message Point {
|
||||
message PointUtf16 {
|
||||
uint32 row = 1;
|
||||
uint32 column = 2;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ use smallvec::{smallvec, SmallVec};
|
||||
use std::iter;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref MIN: Locator = Locator::min();
|
||||
pub static ref MAX: Locator = Locator::max();
|
||||
static ref MIN: Locator = Locator::min();
|
||||
static ref MAX: Locator = Locator::max();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
@@ -19,6 +19,14 @@ impl Locator {
|
||||
Self(smallvec![u64::MAX])
|
||||
}
|
||||
|
||||
pub fn min_ref() -> &'static Self {
|
||||
&*MIN
|
||||
}
|
||||
|
||||
pub fn max_ref() -> &'static Self {
|
||||
&*MAX
|
||||
}
|
||||
|
||||
pub fn assign(&mut self, other: &Self) {
|
||||
self.0.resize(other.0.len(), 0);
|
||||
self.0.copy_from_slice(&other.0);
|
||||
|
||||
@@ -1594,8 +1594,12 @@ impl BufferSnapshot {
|
||||
self.visible_text.point_utf16_to_offset(point)
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point {
|
||||
self.visible_text.point_utf16_to_point(point)
|
||||
pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped<PointUtf16>) -> usize {
|
||||
self.visible_text.unclipped_point_utf16_to_offset(point)
|
||||
}
|
||||
|
||||
pub fn unclipped_point_utf16_to_point(&self, point: Unclipped<PointUtf16>) -> Point {
|
||||
self.visible_text.unclipped_point_utf16_to_point(point)
|
||||
}
|
||||
|
||||
pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize {
|
||||
@@ -1766,9 +1770,9 @@ impl BufferSnapshot {
|
||||
|
||||
fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator {
|
||||
if *anchor == Anchor::MIN {
|
||||
&locator::MIN
|
||||
Locator::min_ref()
|
||||
} else if *anchor == Anchor::MAX {
|
||||
&locator::MAX
|
||||
Locator::max_ref()
|
||||
} else {
|
||||
let anchor_key = InsertionFragmentKey {
|
||||
timestamp: anchor.timestamp,
|
||||
@@ -1803,7 +1807,10 @@ impl BufferSnapshot {
|
||||
}
|
||||
|
||||
pub fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
|
||||
let offset = position.to_offset(self);
|
||||
self.anchor_at_offset(position.to_offset(self), bias)
|
||||
}
|
||||
|
||||
fn anchor_at_offset(&self, offset: usize, bias: Bias) -> Anchor {
|
||||
if bias == Bias::Left && offset == 0 {
|
||||
Anchor::MIN
|
||||
} else if bias == Bias::Right && offset == self.len() {
|
||||
@@ -1840,7 +1847,7 @@ impl BufferSnapshot {
|
||||
self.visible_text.clip_offset_utf16(offset, bias)
|
||||
}
|
||||
|
||||
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
|
||||
pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
|
||||
self.visible_text.clip_point_utf16(point, bias)
|
||||
}
|
||||
|
||||
@@ -2354,32 +2361,20 @@ pub trait ToOffset {
|
||||
}
|
||||
|
||||
impl ToOffset for Point {
|
||||
fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
snapshot.point_to_offset(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for PointUtf16 {
|
||||
fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
snapshot.point_utf16_to_offset(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for usize {
|
||||
fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
assert!(*self <= snapshot.len(), "offset {self} is out of range");
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for OffsetUtf16 {
|
||||
fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
snapshot.offset_utf16_to_offset(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for Anchor {
|
||||
fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
snapshot.summary_for_anchor(self)
|
||||
}
|
||||
}
|
||||
@@ -2390,31 +2385,43 @@ impl<'a, T: ToOffset> ToOffset for &'a T {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for PointUtf16 {
|
||||
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
snapshot.point_utf16_to_offset(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for Unclipped<PointUtf16> {
|
||||
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
snapshot.unclipped_point_utf16_to_offset(*self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToPoint {
|
||||
fn to_point(&self, snapshot: &BufferSnapshot) -> Point;
|
||||
}
|
||||
|
||||
impl ToPoint for Anchor {
|
||||
fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point {
|
||||
fn to_point(&self, snapshot: &BufferSnapshot) -> Point {
|
||||
snapshot.summary_for_anchor(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPoint for usize {
|
||||
fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point {
|
||||
fn to_point(&self, snapshot: &BufferSnapshot) -> Point {
|
||||
snapshot.offset_to_point(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPoint for PointUtf16 {
|
||||
fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point {
|
||||
snapshot.point_utf16_to_point(*self)
|
||||
impl ToPoint for Point {
|
||||
fn to_point(&self, _: &BufferSnapshot) -> Point {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPoint for Point {
|
||||
fn to_point<'a>(&self, _: &BufferSnapshot) -> Point {
|
||||
*self
|
||||
impl ToPoint for Unclipped<PointUtf16> {
|
||||
fn to_point(&self, snapshot: &BufferSnapshot) -> Point {
|
||||
snapshot.unclipped_point_utf16_to_point(*self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2423,25 +2430,25 @@ pub trait ToPointUtf16 {
|
||||
}
|
||||
|
||||
impl ToPointUtf16 for Anchor {
|
||||
fn to_point_utf16<'a>(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
|
||||
fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
|
||||
snapshot.summary_for_anchor(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPointUtf16 for usize {
|
||||
fn to_point_utf16<'a>(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
|
||||
fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
|
||||
snapshot.offset_to_point_utf16(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPointUtf16 for PointUtf16 {
|
||||
fn to_point_utf16<'a>(&self, _: &BufferSnapshot) -> PointUtf16 {
|
||||
fn to_point_utf16(&self, _: &BufferSnapshot) -> PointUtf16 {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPointUtf16 for Point {
|
||||
fn to_point_utf16<'a>(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
|
||||
fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 {
|
||||
snapshot.point_to_point_utf16(*self)
|
||||
}
|
||||
}
|
||||
@@ -2451,45 +2458,23 @@ pub trait ToOffsetUtf16 {
|
||||
}
|
||||
|
||||
impl ToOffsetUtf16 for Anchor {
|
||||
fn to_offset_utf16<'a>(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 {
|
||||
fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 {
|
||||
snapshot.summary_for_anchor(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffsetUtf16 for usize {
|
||||
fn to_offset_utf16<'a>(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 {
|
||||
fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 {
|
||||
snapshot.offset_to_offset_utf16(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffsetUtf16 for OffsetUtf16 {
|
||||
fn to_offset_utf16<'a>(&self, _snapshot: &BufferSnapshot) -> OffsetUtf16 {
|
||||
fn to_offset_utf16(&self, _snapshot: &BufferSnapshot) -> OffsetUtf16 {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Clip {
|
||||
fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self;
|
||||
}
|
||||
|
||||
impl Clip for usize {
|
||||
fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self {
|
||||
snapshot.clip_offset(*self, bias)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clip for Point {
|
||||
fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self {
|
||||
snapshot.clip_point(*self, bias)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clip for PointUtf16 {
|
||||
fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self {
|
||||
snapshot.clip_point_utf16(*self, bias)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromAnchor {
|
||||
fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ test-support = ["serde_json", "tempdir", "git2"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.38"
|
||||
backtrace = "0.3"
|
||||
futures = "0.3"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test;
|
||||
|
||||
pub use backtrace::Backtrace;
|
||||
use futures::Future;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{
|
||||
@@ -10,6 +11,18 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! debug_panic {
|
||||
( $($fmt_arg:tt)* ) => {
|
||||
if cfg!(debug_assertions) {
|
||||
panic!( $($fmt_arg)* );
|
||||
} else {
|
||||
let backtrace = $crate::Backtrace::new();
|
||||
log::error!("{}\n{:?}", format_args!($($fmt_arg)*), backtrace);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn truncate(s: &str, max_chars: usize) -> &str {
|
||||
match s.char_indices().nth(max_chars) {
|
||||
None => s,
|
||||
|
||||
@@ -114,12 +114,12 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
|
||||
};
|
||||
|
||||
edits.push((expanded_range, "\n"));
|
||||
new_selections.push(selection.map(|_| anchor.clone()));
|
||||
new_selections.push(selection.map(|_| anchor));
|
||||
} else {
|
||||
let range = selection.map(|p| p.to_point(map)).range();
|
||||
let anchor = map.buffer_snapshot.anchor_after(range.end);
|
||||
edits.push((range, ""));
|
||||
new_selections.push(selection.map(|_| anchor.clone()));
|
||||
new_selections.push(selection.map(|_| anchor));
|
||||
}
|
||||
selection.goal = SelectionGoal::None;
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.66.0"
|
||||
version = "0.67.0"
|
||||
|
||||
[lib]
|
||||
name = "zed"
|
||||
|
||||
@@ -1 +1 @@
|
||||
stable
|
||||
dev
|
||||
@@ -213,6 +213,21 @@ fn init_paths() {
|
||||
std::fs::create_dir_all(&*zed::paths::LANGUAGES_DIR).expect("could not create languages path");
|
||||
std::fs::create_dir_all(&*zed::paths::DB_DIR).expect("could not create database path");
|
||||
std::fs::create_dir_all(&*zed::paths::LOGS_DIR).expect("could not create logs path");
|
||||
|
||||
// Copy setting files from legacy locations. TODO: remove this after a few releases.
|
||||
thread::spawn(|| {
|
||||
if std::fs::metadata(&*zed::paths::legacy::SETTINGS).is_ok()
|
||||
&& std::fs::metadata(&*zed::paths::SETTINGS).is_err()
|
||||
{
|
||||
std::fs::copy(&*zed::paths::legacy::SETTINGS, &*zed::paths::SETTINGS).log_err();
|
||||
}
|
||||
|
||||
if std::fs::metadata(&*zed::paths::legacy::KEYMAP).is_ok()
|
||||
&& std::fs::metadata(&*zed::paths::KEYMAP).is_err()
|
||||
{
|
||||
std::fs::copy(&*zed::paths::legacy::KEYMAP, &*zed::paths::KEYMAP).log_err();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
|
||||
Reference in New Issue
Block a user