Start tracking diffs in ScriptingSession
(#26463)
The diff is not exposed yet, but we'll take care of that next. Release Notes: - N/A
This commit is contained in:
parent
036c123488
commit
30afba50a9
5 changed files with 207 additions and 64 deletions
|
@ -1,5 +1,6 @@
|
|||
use anyhow::anyhow;
|
||||
use collections::HashSet;
|
||||
use buffer_diff::BufferDiff;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
pin_mut, SinkExt, StreamExt,
|
||||
|
@ -18,10 +19,15 @@ use util::{paths::PathMatcher, ResultExt};
|
|||
|
||||
struct ForegroundFn(Box<dyn FnOnce(WeakEntity<ScriptingSession>, AsyncApp) + Send>);
|
||||
|
||||
struct BufferChanges {
|
||||
diff: Entity<BufferDiff>,
|
||||
edit_ids: Vec<clock::Lamport>,
|
||||
}
|
||||
|
||||
pub struct ScriptingSession {
|
||||
project: Entity<Project>,
|
||||
scripts: Vec<Script>,
|
||||
changed_buffers: HashSet<Entity<Buffer>>,
|
||||
changes_by_buffer: HashMap<Entity<Buffer>, BufferChanges>,
|
||||
foreground_fns_tx: mpsc::Sender<ForegroundFn>,
|
||||
_invoke_foreground_fns: Task<()>,
|
||||
}
|
||||
|
@ -32,7 +38,7 @@ impl ScriptingSession {
|
|||
ScriptingSession {
|
||||
project,
|
||||
scripts: Vec::new(),
|
||||
changed_buffers: HashSet::default(),
|
||||
changes_by_buffer: HashMap::default(),
|
||||
foreground_fns_tx,
|
||||
_invoke_foreground_fns: cx.spawn(|this, cx| async move {
|
||||
while let Some(foreground_fn) = foreground_fns_rx.next().await {
|
||||
|
@ -43,7 +49,7 @@ impl ScriptingSession {
|
|||
}
|
||||
|
||||
pub fn changed_buffers(&self) -> impl ExactSizeIterator<Item = &Entity<Buffer>> {
|
||||
self.changed_buffers.iter()
|
||||
self.changes_by_buffer.keys()
|
||||
}
|
||||
|
||||
pub fn run_script(
|
||||
|
@ -188,9 +194,6 @@ impl ScriptingSession {
|
|||
|
||||
lua.load(SANDBOX_PREAMBLE).exec_async().await?;
|
||||
|
||||
// Drop Lua instance to decrement reference count.
|
||||
drop(lua);
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
});
|
||||
|
@ -384,8 +387,12 @@ impl ScriptingSession {
|
|||
.update(&mut cx, |buffer, cx| buffer.diff(text, cx))?
|
||||
.await;
|
||||
|
||||
buffer.update(&mut cx, |buffer, cx| {
|
||||
let edit_ids = buffer.update(&mut cx, |buffer, cx| {
|
||||
buffer.finalize_last_transaction();
|
||||
buffer.apply_diff(diff, cx);
|
||||
let transaction = buffer.finalize_last_transaction();
|
||||
transaction
|
||||
.map_or(Vec::new(), |transaction| transaction.edit_ids.clone())
|
||||
})?;
|
||||
|
||||
session
|
||||
|
@ -400,10 +407,36 @@ impl ScriptingSession {
|
|||
})?
|
||||
.await?;
|
||||
|
||||
let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
|
||||
|
||||
// If we saved successfully, mark buffer as changed
|
||||
session.update(&mut cx, |session, _cx| {
|
||||
session.changed_buffers.insert(buffer);
|
||||
})
|
||||
let buffer_without_changes =
|
||||
buffer.update(&mut cx, |buffer, cx| buffer.branch(cx))?;
|
||||
session
|
||||
.update(&mut cx, |session, cx| {
|
||||
let changed_buffer = session
|
||||
.changes_by_buffer
|
||||
.entry(buffer)
|
||||
.or_insert_with(|| BufferChanges {
|
||||
diff: cx.new(|cx| BufferDiff::new(&snapshot, cx)),
|
||||
edit_ids: Vec::new(),
|
||||
});
|
||||
changed_buffer.edit_ids.extend(edit_ids);
|
||||
let operations_to_undo = changed_buffer
|
||||
.edit_ids
|
||||
.iter()
|
||||
.map(|edit_id| (*edit_id, u32::MAX))
|
||||
.collect::<HashMap<_, _>>();
|
||||
buffer_without_changes.update(cx, |buffer, cx| {
|
||||
buffer.undo_operations(operations_to_undo, cx);
|
||||
});
|
||||
changed_buffer.diff.update(cx, |diff, cx| {
|
||||
diff.set_base_text(buffer_without_changes, snapshot.text, cx)
|
||||
})
|
||||
})?
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}),
|
||||
|
@ -895,6 +928,7 @@ impl Script {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::TestAppContext;
|
||||
|
@ -954,9 +988,7 @@ mod tests {
|
|||
let test_session = TestSession::init(cx).await;
|
||||
let output = test_session.test_success(script, cx).await;
|
||||
assert_eq!(output, "Content:\tHello world!\n");
|
||||
|
||||
// Only read, should not be marked as changed
|
||||
assert!(!test_session.was_marked_changed("file1.txt", cx));
|
||||
assert_eq!(test_session.diff(cx), Vec::new());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -976,7 +1008,16 @@ mod tests {
|
|||
let test_session = TestSession::init(cx).await;
|
||||
let output = test_session.test_success(script, cx).await;
|
||||
assert_eq!(output, "Written content:\tThis is new content\n");
|
||||
assert!(test_session.was_marked_changed("file1.txt", cx));
|
||||
assert_eq!(
|
||||
test_session.diff(cx),
|
||||
vec![(
|
||||
PathBuf::from("file1.txt"),
|
||||
vec![(
|
||||
"Hello world!\n".to_string(),
|
||||
"This is new content".to_string()
|
||||
)]
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -1004,7 +1045,16 @@ mod tests {
|
|||
output,
|
||||
"Full content:\tFirst line\nSecond line\nThird line\n"
|
||||
);
|
||||
assert!(test_session.was_marked_changed("multiwrite.txt", cx));
|
||||
assert_eq!(
|
||||
test_session.diff(cx),
|
||||
vec![(
|
||||
PathBuf::from("multiwrite.txt"),
|
||||
vec![(
|
||||
"".to_string(),
|
||||
"First line\nSecond line\nThird line".to_string()
|
||||
)]
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -1033,29 +1083,33 @@ mod tests {
|
|||
output,
|
||||
"Final content:\tContent written by second handle\n\n"
|
||||
);
|
||||
assert!(test_session.was_marked_changed("multi_open.txt", cx));
|
||||
assert_eq!(
|
||||
test_session.diff(cx),
|
||||
vec![(
|
||||
PathBuf::from("multi_open.txt"),
|
||||
vec![(
|
||||
"".to_string(),
|
||||
"Content written by second handle\n".to_string()
|
||||
)]
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_append_mode(cx: &mut TestAppContext) {
|
||||
let script = r#"
|
||||
-- Test append mode
|
||||
local file = io.open("append.txt", "w")
|
||||
file:write("Initial content\n")
|
||||
file:close()
|
||||
|
||||
-- Append more content
|
||||
file = io.open("append.txt", "a")
|
||||
file = io.open("file1.txt", "a")
|
||||
file:write("Appended content\n")
|
||||
file:close()
|
||||
|
||||
-- Add even more
|
||||
file = io.open("append.txt", "a")
|
||||
file = io.open("file1.txt", "a")
|
||||
file:write("More appended content")
|
||||
file:close()
|
||||
|
||||
-- Read back to verify
|
||||
local read_file = io.open("append.txt", "r")
|
||||
local read_file = io.open("file1.txt", "r")
|
||||
local content = read_file:read("*a")
|
||||
print("Content after appends:", content)
|
||||
read_file:close()
|
||||
|
@ -1065,9 +1119,18 @@ mod tests {
|
|||
let output = test_session.test_success(script, cx).await;
|
||||
assert_eq!(
|
||||
output,
|
||||
"Content after appends:\tInitial content\nAppended content\nMore appended content\n"
|
||||
"Content after appends:\tHello world!\nAppended content\nMore appended content\n"
|
||||
);
|
||||
assert_eq!(
|
||||
test_session.diff(cx),
|
||||
vec![(
|
||||
PathBuf::from("file1.txt"),
|
||||
vec![(
|
||||
"".to_string(),
|
||||
"Appended content\nMore appended content".to_string()
|
||||
)]
|
||||
)]
|
||||
);
|
||||
assert!(test_session.was_marked_changed("append.txt", cx));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -1117,7 +1180,13 @@ mod tests {
|
|||
assert!(output.contains("Line with newline length:\t7"));
|
||||
assert!(output.contains("Last char:\t10")); // LF
|
||||
assert!(output.contains("5 bytes:\tLine "));
|
||||
assert!(test_session.was_marked_changed("multiline.txt", cx));
|
||||
assert_eq!(
|
||||
test_session.diff(cx),
|
||||
vec![(
|
||||
PathBuf::from("multiline.txt"),
|
||||
vec![("".to_string(), "Line 1\nLine 2\nLine 3".to_string())]
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
@ -1137,8 +1206,8 @@ mod tests {
|
|||
fs.insert_tree(
|
||||
path!("/"),
|
||||
json!({
|
||||
"file1.txt": "Hello world!",
|
||||
"file2.txt": "Goodbye moon!"
|
||||
"file1.txt": "Hello world!\n",
|
||||
"file2.txt": "Goodbye moon!\n"
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
@ -1164,17 +1233,30 @@ mod tests {
|
|||
})
|
||||
}
|
||||
|
||||
fn was_marked_changed(&self, path_str: &str, cx: &mut TestAppContext) -> bool {
|
||||
fn diff(&self, cx: &mut TestAppContext) -> Vec<(PathBuf, Vec<(String, String)>)> {
|
||||
self.session.read_with(cx, |session, cx| {
|
||||
let count_changed = session
|
||||
.changed_buffers
|
||||
session
|
||||
.changes_by_buffer
|
||||
.iter()
|
||||
.filter(|buffer| buffer.read(cx).file().unwrap().path().ends_with(path_str))
|
||||
.count();
|
||||
|
||||
assert!(count_changed < 2, "Multiple buffers matched for same path");
|
||||
|
||||
count_changed > 0
|
||||
.map(|(buffer, changes)| {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let diff = changes.diff.read(cx);
|
||||
let hunks = diff.hunks(&snapshot, cx);
|
||||
let path = buffer.read(cx).file().unwrap().path().clone();
|
||||
let diffs = hunks
|
||||
.map(|hunk| {
|
||||
let old_text = diff
|
||||
.base_text()
|
||||
.text_for_range(hunk.diff_base_byte_range)
|
||||
.collect::<String>();
|
||||
let new_text =
|
||||
snapshot.text_for_range(hunk.range).collect::<String>();
|
||||
(old_text, new_text)
|
||||
})
|
||||
.collect();
|
||||
(path.to_path_buf(), diffs)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue