feat: configure chrome segments and anchor groups
This commit is contained in:
+178
-28
@@ -220,7 +220,11 @@ impl EditorSession {
|
||||
));
|
||||
}
|
||||
editor.set_diff_context_options(config.diff_context_lines, config.diff_expand_lines);
|
||||
editor.set_tabline_label(config.tabline_label);
|
||||
if let Err(error) =
|
||||
editor.set_chrome_config(config.tabline, config.statusline, config.labels)
|
||||
{
|
||||
notices.push(format!("chrome: {error}"));
|
||||
}
|
||||
let mut plugins = BTreeMap::new();
|
||||
for configured in config.plugins {
|
||||
let path = configured.path;
|
||||
@@ -658,7 +662,7 @@ impl EditorSession {
|
||||
&& self.editor.startup_diagnostic().is_none()
|
||||
}
|
||||
|
||||
fn apply_update(&mut self, owner: &str, update: PluginUpdate) -> Result<bool, String> {
|
||||
fn apply_update(&mut self, owner: &str, mut update: PluginUpdate) -> Result<bool, String> {
|
||||
if owner == "core"
|
||||
&& let Some(id) = update.request_id
|
||||
&& self
|
||||
@@ -668,7 +672,9 @@ impl EditorSession {
|
||||
.map(|latest| latest.0)
|
||||
!= Some(id)
|
||||
{
|
||||
return Ok(false);
|
||||
update
|
||||
.effects
|
||||
.retain(|effect| matches!(effect, UpdateEffect::Label { .. }));
|
||||
}
|
||||
for effect in &update.effects {
|
||||
if let UpdateEffect::Completion {
|
||||
@@ -697,6 +703,7 @@ impl EditorSession {
|
||||
}
|
||||
// Validate the whole applicable annotation batch before changing any buffer.
|
||||
let validation = (|| -> Result<(), String> {
|
||||
let mut label_bytes = 0usize;
|
||||
for effect in &update.effects {
|
||||
match effect {
|
||||
UpdateEffect::Diagnostics {
|
||||
@@ -757,9 +764,14 @@ impl EditorSession {
|
||||
}
|
||||
UpdateEffect::Completion { .. } => {}
|
||||
UpdateEffect::Message { .. } => {}
|
||||
UpdateEffect::TablineLabel { text } => {
|
||||
if text.len() > 4 * 1024 * 1024 {
|
||||
return Err("Tabline label exceeds 4 MiB".into());
|
||||
UpdateEffect::Label { name, text } => {
|
||||
crate::editor::validate_label(name, text)?;
|
||||
label_bytes = label_bytes
|
||||
.checked_add(name.len())
|
||||
.and_then(|bytes| bytes.checked_add(text.len()))
|
||||
.ok_or("Labels exceed 4 MiB")?;
|
||||
if label_bytes > 4 * 1024 * 1024 {
|
||||
return Err("Labels exceed 4 MiB".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -857,8 +869,8 @@ impl EditorSession {
|
||||
changed |= self.editor.message() != text;
|
||||
self.editor.set_message(text);
|
||||
}
|
||||
UpdateEffect::TablineLabel { text } => {
|
||||
changed |= self.editor.set_tabline_label(text);
|
||||
UpdateEffect::Label { name, text } => {
|
||||
changed |= self.editor.set_label(name, text)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -870,7 +882,7 @@ impl EditorSession {
|
||||
&mut self,
|
||||
owner: &str,
|
||||
id: u64,
|
||||
effects: Vec<Effect>,
|
||||
mut effects: Vec<Effect>,
|
||||
) -> Result<bool, String> {
|
||||
let source = self
|
||||
.requests
|
||||
@@ -879,16 +891,18 @@ impl EditorSession {
|
||||
let Some(source) = source else {
|
||||
return Err("plugin response has an unknown invocation ID".into());
|
||||
};
|
||||
if !self.source_is_current(source)
|
||||
|| self.editor.buffer_id() != source.buffer_id
|
||||
|| self.editor.pane_id() != source.pane_id
|
||||
{
|
||||
return Ok(false);
|
||||
// Document effects retain source freshness; labels belong to the whole session.
|
||||
let current = self.source_is_current(source)
|
||||
&& self.editor.buffer_id() == source.buffer_id
|
||||
&& self.editor.pane_id() == source.pane_id;
|
||||
if !current {
|
||||
effects.retain(|effect| matches!(effect, Effect::Label { .. }));
|
||||
}
|
||||
if effects
|
||||
.iter()
|
||||
.all(|effect| matches!(effect, Effect::Message(_) | Effect::TablineLabel(_)))
|
||||
.all(|effect| matches!(effect, Effect::Message(_) | Effect::Label { .. }))
|
||||
{
|
||||
crate::editor::validate_effects(&effects)?;
|
||||
let mut changed = false;
|
||||
for effect in effects {
|
||||
match effect {
|
||||
@@ -896,8 +910,8 @@ impl EditorSession {
|
||||
changed |= self.editor.message() != text;
|
||||
self.editor.set_message(text);
|
||||
}
|
||||
Effect::TablineLabel(text) => {
|
||||
changed |= self.editor.set_tabline_label(text);
|
||||
Effect::Label { name, text } => {
|
||||
changed |= self.editor.set_label(name, text)?;
|
||||
}
|
||||
_ => unreachable!("presentation-only effects checked above"),
|
||||
}
|
||||
@@ -2996,35 +3010,129 @@ end
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tabline_updates_survive_tab_switches_and_remain_atomic_in_readonly_views() {
|
||||
fn startup_chrome_installs_named_values_without_editing_the_document() {
|
||||
let fixture = Fixture::new();
|
||||
fixture.file("buffer.txt", "source");
|
||||
let session = fixture.open(
|
||||
r#"
|
||||
Vamp.configure do |config|
|
||||
config.tabline = {right: [:battery]}
|
||||
config.statusline = {center: [:workspace]}
|
||||
config.labels = {battery: "bat 38%", workspace: "tools\n"}
|
||||
end
|
||||
"#,
|
||||
);
|
||||
assert_eq!(session.editor.label("battery"), "bat 38%");
|
||||
assert_eq!(session.editor.label("workspace"), "tools�");
|
||||
assert_eq!(session.editor.tabline_config().right, ["battery"]);
|
||||
assert_eq!(session.editor.statusline_config().center, ["workspace"]);
|
||||
assert_eq!(session.editor.lines(), ["source"]);
|
||||
assert_eq!(session.editor.text_revision(), 0);
|
||||
assert!(!session.editor.dirty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asynchronous_result_labels_outlive_the_source_without_applying_stale_edits() {
|
||||
let fixture = Fixture::new();
|
||||
let target = fixture.file("other.txt", "");
|
||||
let mut session = EditorSession::open(None, vec![], None).unwrap();
|
||||
let source = InvocationSource {
|
||||
buffer_id: session.editor.buffer_id(),
|
||||
pane_id: session.editor.pane_id(),
|
||||
revision: session.editor.text_revision(),
|
||||
};
|
||||
session.editor.execute("tab.new");
|
||||
session
|
||||
.editor
|
||||
.execute(&format!("edit {}", target.display()));
|
||||
assert_ne!(session.editor.buffer_id(), source.buffer_id);
|
||||
session.editor.handle(Input::Char('i'));
|
||||
let message = session.editor.message().to_owned();
|
||||
for (id, effects) in [
|
||||
(
|
||||
1,
|
||||
vec![
|
||||
Effect::Insert("stale".into()),
|
||||
Effect::Message("stale message".into()),
|
||||
Effect::Label {
|
||||
name: "battery".into(),
|
||||
text: "bat 38%".into(),
|
||||
},
|
||||
],
|
||||
),
|
||||
(
|
||||
2,
|
||||
vec![
|
||||
Effect::Label {
|
||||
name: "battery".into(),
|
||||
text: "must not leak".into(),
|
||||
},
|
||||
Effect::Label {
|
||||
name: "bad-name".into(),
|
||||
text: "invalid".into(),
|
||||
},
|
||||
],
|
||||
),
|
||||
] {
|
||||
session
|
||||
.requests
|
||||
.entry("fixture".into())
|
||||
.or_default()
|
||||
.pending
|
||||
.insert(id, source);
|
||||
let result = session.apply_response("fixture", id, effects);
|
||||
if id == 1 {
|
||||
assert!(result.unwrap());
|
||||
} else {
|
||||
assert!(result.is_err());
|
||||
}
|
||||
assert_eq!(session.editor.label("battery"), "bat 38%");
|
||||
assert_eq!(session.editor.lines(), [""]);
|
||||
assert_eq!(session.editor.message(), message);
|
||||
assert_eq!(session.editor.mode(), Mode::Insert);
|
||||
assert_eq!(session.editor.text_revision(), 0);
|
||||
assert!(!session.editor.dirty());
|
||||
}
|
||||
session.editor.handle(Input::Char('x'));
|
||||
session.editor.handle(Input::Escape);
|
||||
session.editor.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor.lines(), [""]);
|
||||
assert_eq!(session.editor.label("battery"), "bat 38%");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_labels_survive_tab_switches_and_remain_atomic_in_readonly_views() {
|
||||
let fixture = Fixture::new();
|
||||
let mut session = fixture.asynchronous(None);
|
||||
publish(
|
||||
&mut session,
|
||||
vec![serde_json::json!({
|
||||
"type": "tabline_label", "text": "bat 37%"
|
||||
"type": "label", "name": "battery", "text": "bat 37%"
|
||||
})],
|
||||
);
|
||||
drain_until(&mut session, |session| {
|
||||
session.editor().tabline_label() == "bat 37%"
|
||||
session.editor().label("battery") == "bat 37%"
|
||||
});
|
||||
let gate = fixture.0.join("label.gate");
|
||||
hold(
|
||||
&mut session,
|
||||
&gate,
|
||||
vec![serde_json::json!({
|
||||
"type": "tabline_label", "text": "bat 38%"
|
||||
"type": "label", "name": "battery", "text": "bat 38%"
|
||||
})],
|
||||
);
|
||||
barrier(&mut session, "label request received");
|
||||
session.execute("tab.new");
|
||||
release(&mut session, &gate);
|
||||
assert_eq!(session.editor().tabline_label(), "bat 38%");
|
||||
assert_eq!(session.editor().label("battery"), "bat 38%");
|
||||
session.execute("tab.previous");
|
||||
assert_eq!(session.editor().tabline_label(), "bat 38%");
|
||||
assert_eq!(session.editor().label("battery"), "bat 38%");
|
||||
let update = |text: &str| PluginUpdate {
|
||||
request_id: None,
|
||||
effects: vec![UpdateEffect::TablineLabel { text: text.into() }],
|
||||
effects: vec![UpdateEffect::Label {
|
||||
name: "battery".into(),
|
||||
text: text.into(),
|
||||
}],
|
||||
};
|
||||
assert!(
|
||||
!session
|
||||
@@ -3034,7 +3142,8 @@ end
|
||||
let rejected = PluginUpdate {
|
||||
request_id: None,
|
||||
effects: vec![
|
||||
UpdateEffect::TablineLabel {
|
||||
UpdateEffect::Label {
|
||||
name: "battery".into(),
|
||||
text: "must not leak".into(),
|
||||
},
|
||||
UpdateEffect::SemanticTokens {
|
||||
@@ -3045,7 +3154,48 @@ end
|
||||
],
|
||||
};
|
||||
assert!(session.apply_update("generic_fixture", rejected).is_err());
|
||||
assert_eq!(session.editor().tabline_label(), "bat 38%");
|
||||
assert_eq!(session.editor().label("battery"), "bat 38%");
|
||||
let message = session.editor.message().to_owned();
|
||||
for effects in [
|
||||
vec![
|
||||
UpdateEffect::Message {
|
||||
text: "must not leak".into(),
|
||||
},
|
||||
UpdateEffect::Label {
|
||||
name: "battery".into(),
|
||||
text: "changed".into(),
|
||||
},
|
||||
UpdateEffect::Label {
|
||||
name: "bad-name".into(),
|
||||
text: "invalid".into(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
UpdateEffect::Label {
|
||||
name: "battery".into(),
|
||||
text: "x".repeat(2 * 1024 * 1024),
|
||||
},
|
||||
UpdateEffect::Label {
|
||||
name: "workspace".into(),
|
||||
text: "x".repeat(2 * 1024 * 1024),
|
||||
},
|
||||
],
|
||||
] {
|
||||
assert!(
|
||||
session
|
||||
.apply_update(
|
||||
"generic_fixture",
|
||||
PluginUpdate {
|
||||
request_id: None,
|
||||
effects
|
||||
}
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(session.editor.label("battery"), "bat 38%");
|
||||
assert_eq!(session.editor.label("workspace"), "");
|
||||
assert_eq!(session.editor.message(), message);
|
||||
}
|
||||
session.editor.execute("diff");
|
||||
assert!(session.editor.mutation_blocked());
|
||||
assert!(
|
||||
@@ -3053,10 +3203,10 @@ end
|
||||
.apply_update("generic_fixture", update("bat\t39%\n"))
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(session.editor().tabline_label(), "bat�39%�");
|
||||
assert_eq!(session.editor().label("battery"), "bat�39%�");
|
||||
assert_eq!(session.editor().lines(), [""]);
|
||||
assert!(session.apply_update("generic_fixture", update("")).unwrap());
|
||||
assert_eq!(session.editor().tabline_label(), "");
|
||||
assert_eq!(session.editor().label("battery"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user