use super::*;
use crate::editor::events::AppEvent;
use crate::editor::Editor;
use std::path::PathBuf;
use strop_core::id::LineIndex;
use strop_core::Buffer;
use strop_lsp::{
LspEvent, PositionEncoding, ReplyContext, RequestId, RequestKind, RequestStamp, ServerColumn,
ServerEdit, ServerId, ServerPosition,
};
use strop_workspace::ResourceLocation;
fn at(line: usize, col: usize) -> ServerPosition {
ServerPosition {
line: LineIndex::new(line),
column: ServerColumn::new(col),
}
}
fn edit(sl: usize, sc: usize, el: usize, ec: usize, text: &str) -> ServerEdit {
ServerEdit {
start: at(sl, sc),
end: at(el, ec),
new_text: text.into(),
}
}
fn arm(e: &mut Editor, document: DocumentId, id: u64, kind: RequestKind) -> ReplyContext {
let server = ServerId::new(7);
let (revision, path) = {
let doc = e.docs.get(document).unwrap();
(doc.buf.revision(), doc.buf.path.clone().unwrap())
};
e.lsp_state.bindings.insert(
document,
crate::editor::lsp::state::Binding {
server,
revision,
path,
root: PathBuf::from("/workspace"),
target: strop_workspace::Filesystem::Local,
},
);
let stamp = RequestStamp {
request: RequestId::new(id),
server,
document,
revision,
};
e.lsp_state.navigation = Some(stamp);
ReplyContext {
stamp,
encoding: PositionEncoding::Utf8,
kind,
}
}
fn file_editor(dir: &tempfile::TempDir, name: &str, text: &str) -> (Editor, DocumentId) {
std::fs::write(dir.path().join(name), text).unwrap();
let mut e = Editor::new_in(Buffer::from_text("scratch\n"), dir.path().to_path_buf());
e.open_fixture(&dir.path().join(name)).unwrap();
let document = e.current();
(e, document)
}
#[test]
fn format_applies_edits_and_records_a_receipt() {
let dir = tempfile::tempdir().unwrap();
let (mut e, document) = file_editor(&dir, "a.txt", "hello world\n");
let context = arm(&mut e, document, 1, RequestKind::Format);
e.handle_app_event(AppEvent::Lsp(LspEvent::Edits {
context,
edits: vec![edit(0, 0, 0, 5, "hi")],
}));
assert_eq!(e.buf().text().to_string(), "hi world\n");
assert_eq!(e.message, "format: applied to 1 buffer(s)");
}
#[test]
fn rename_applies_across_open_documents() {
let dir = tempfile::tempdir().unwrap();
let (mut e, first) = file_editor(&dir, "a.txt", "alpha here\n");
let b_path = dir.path().join("b.txt");
std::fs::write(&b_path, "alpha there\n").unwrap();
e.open_fixture(&b_path).unwrap();
let second = e.current();
let _ = arm(&mut e, first, 1, RequestKind::Rename);
let context = arm(&mut e, second, 2, RequestKind::Rename);
let path_a = dir.path().join("a.txt");
e.handle_app_event(AppEvent::Lsp(LspEvent::WorkspaceEdits {
context,
edits: vec![
(
ResourceLocation::local(path_a.clone()),
vec![edit(0, 0, 0, 5, "omega")],
),
(
ResourceLocation::local(b_path.clone()),
vec![edit(0, 0, 0, 5, "omega")],
),
],
}));
assert_eq!(e.buf().text().to_string(), "omega there\n");
assert_eq!(e.message, "rename: applied to 2 buffer(s)");
e.feed_text(":undo-change\r");
assert_eq!(e.buf().text().to_string(), "alpha there\n");
e.switch_to(first);
assert_eq!(e.buf().text().to_string(), "alpha here\n");
}
#[test]
fn unbound_targets_are_named_refusals_never_silent() {
let dir = tempfile::tempdir().unwrap();
let (mut e, document) = file_editor(&dir, "a.txt", "alpha here\n");
let context = arm(&mut e, document, 1, RequestKind::Rename);
e.handle_app_event(AppEvent::Lsp(LspEvent::WorkspaceEdits {
context,
edits: vec![
(
ResourceLocation::local(dir.path().join("a.txt")),
vec![edit(0, 0, 0, 5, "omega")],
),
(
ResourceLocation::local(dir.path().join("ghost.txt")),
vec![edit(0, 0, 0, 3, "x")],
),
],
}));
assert_eq!(e.buf().text().to_string(), "omega here\n");
assert_eq!(
e.message,
"rename: 1 buffer(s) applied, 1 target(s) refused"
);
}
#[test]
fn a_document_edited_since_the_plan_is_refused_by_name() {
let dir = tempfile::tempdir().unwrap();
let (mut e, document) = file_editor(&dir, "a.txt", "alpha here\n");
let _ = arm(&mut e, document, 1, RequestKind::Rename); let location = ResourceLocation::local(dir.path().join("a.txt"));
let plan = e.build_change_plan(
ChangeProducer::Rename,
vec![(location, vec![edit(0, 0, 0, 5, "omega")])],
PositionEncoding::Utf8,
);
e.feed_text("0rx"); e.apply_change_plan(plan);
assert_eq!(e.buf().text().to_string(), "xlpha here\n");
assert!(e.message.contains("refused"), "{}", e.message);
assert!(e.message.contains("0 buffer(s) applied"), "{}", e.message);
}
#[test]
fn stale_edit_replies_never_touch_the_buffer() {
let dir = tempfile::tempdir().unwrap();
let (mut e, document) = file_editor(&dir, "a.txt", "alpha here\n");
let mut context = arm(&mut e, document, 1, RequestKind::Format);
context.stamp.revision = BufferRevision::new(99); e.handle_app_event(AppEvent::Lsp(LspEvent::Edits {
context,
edits: vec![edit(0, 0, 0, 5, "SNEAKY")],
}));
assert_eq!(e.buf().text().to_string(), "alpha here\n");
assert_ne!(e.message, "format: applied to 1 buffer(s)");
}
#[test]
fn grouped_undo_skips_buffers_edited_since_the_receipt() {
let dir = tempfile::tempdir().unwrap();
let (mut e, first) = file_editor(&dir, "a.txt", "alpha here\n");
let b_path = dir.path().join("b.txt");
std::fs::write(&b_path, "alpha there\n").unwrap();
e.open_fixture(&b_path).unwrap();
let second = e.current();
let _ = arm(&mut e, first, 1, RequestKind::Rename);
let context = arm(&mut e, second, 2, RequestKind::Rename);
e.handle_app_event(AppEvent::Lsp(LspEvent::WorkspaceEdits {
context,
edits: vec![
(
ResourceLocation::local(dir.path().join("a.txt")),
vec![edit(0, 0, 0, 5, "omega")],
),
(
ResourceLocation::local(b_path),
vec![edit(0, 0, 0, 5, "omega")],
),
],
}));
e.feed_text("0rZ"); e.feed_text(":undo-change\r");
assert!(e.message.contains("skipped"), "{}", e.message);
assert_eq!(e.buf().text().to_string(), "Zmega there\n");
e.switch_to(first);
assert_eq!(e.buf().text().to_string(), "alpha here\n");
}