feat: preconfigure Python with Astral ty and Ruff
This commit is contained in:
+667
-5
@@ -69,8 +69,10 @@ initialized_sent = false
|
||||
held = []
|
||||
definition_count = 0
|
||||
source_uri = uri(source)
|
||||
documents = {}
|
||||
control_id = nil
|
||||
loop do
|
||||
if initialize_id && !initialized_sent && (!['gated', 'hover-gated'].include?(mode) || File.exist?(release))
|
||||
if initialize_id && !initialized_sent && (!['gated', 'hover-gated', 'controlled-gated'].include?(mode) || File.exist?(release))
|
||||
sync = case mode
|
||||
when 'numeric' then 1
|
||||
when 'none' then {'openClose' => false, 'change' => 0, 'save' => false}
|
||||
@@ -78,8 +80,8 @@ loop do
|
||||
else {'openClose' => true, 'change' => 1, 'save' => {'includeText' => true}}
|
||||
end
|
||||
result(initialize_id, {'capabilities' => {'positionEncoding' => 'utf-16',
|
||||
'textDocumentSync' => sync, 'definitionProvider' => mode == 'unsupported' ? false : {'workDoneProgress' => false},
|
||||
'hoverProvider' => mode == 'hover-unsupported' ? false : {'workDoneProgress' => false}}})
|
||||
'textDocumentSync' => sync, 'definitionProvider' => ['unsupported', 'controlled-unsupported'].include?(mode) ? false : {'workDoneProgress' => false},
|
||||
'hoverProvider' => ['hover-unsupported', 'controlled-unsupported'].include?(mode) ? false : {'workDoneProgress' => false}}})
|
||||
initialized_sent = true
|
||||
end
|
||||
if !held.empty? && File.exist?(release) && ['stale', 'hover-stale'].include?(mode)
|
||||
@@ -89,6 +91,30 @@ loop do
|
||||
held.clear
|
||||
emit({'method' => 'window/showMessage', 'params' => {'type' => 3, 'message' => 'stale response complete'}})
|
||||
end
|
||||
control = release + '.control'
|
||||
if File.exist?(control)
|
||||
command = JSON.parse(File.read(control))
|
||||
if command['id'] != control_id
|
||||
control_id = command.fetch('id')
|
||||
case command.fetch('action')
|
||||
when 'publish', 'clear'
|
||||
document = documents.fetch(command.fetch('uri', source_uri))
|
||||
params = {'uri' => document['uri'], 'version' => command.fetch('version', document['version']),
|
||||
'diagnostics' => command['action'] == 'clear' ? [] :
|
||||
[{'range' => range(0, 2, 4), 'severity' => 2, 'message' => command.fetch('message')}]}
|
||||
emit({'method' => 'textDocument/publishDiagnostics', 'params' => params})
|
||||
when 'die'
|
||||
exit! 7
|
||||
when 'malformed'
|
||||
STDOUT.write("Content-Length: nope\r\n\r\n")
|
||||
else
|
||||
raise 'unknown fixture action'
|
||||
end
|
||||
# The response proves the worker consumed every preceding publication.
|
||||
emit({'id' => "control-#{control_id}", 'method' => 'workspace/configuration',
|
||||
'params' => {'items' => []}})
|
||||
end
|
||||
end
|
||||
next unless IO.select([STDIN], nil, nil, 0.01)
|
||||
headers = {}
|
||||
loop do
|
||||
@@ -114,7 +140,8 @@ loop do
|
||||
emit({'id' => 0, 'method' => '$/unsupported', 'params' => {}})
|
||||
when 'textDocument/didOpen'
|
||||
document = frame.fetch('params').fetch('textDocument')
|
||||
if document['uri'] == source_uri
|
||||
documents[document['uri']] = document
|
||||
if document['uri'] == source_uri && !mode.start_with?('controlled')
|
||||
if mode == 'malformed'
|
||||
STDOUT.write("Content-Length: nope\r\n\r\n")
|
||||
elsif mode == 'dead'
|
||||
@@ -125,7 +152,8 @@ loop do
|
||||
end
|
||||
when 'textDocument/didChange'
|
||||
document = frame.fetch('params').fetch('textDocument')
|
||||
if document['uri'] == source_uri && !['malformed', 'dead'].include?(mode)
|
||||
documents[document['uri']] = document
|
||||
if document['uri'] == source_uri && !['malformed', 'dead'].include?(mode) && !mode.start_with?('controlled')
|
||||
diagnostics(document, 'obsolete must drop', old: true)
|
||||
diagnostics(document, "revision #{document['version']}")
|
||||
end
|
||||
@@ -258,6 +286,103 @@ impl Fixture {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn worker_log(&self, worker: usize) -> PathBuf {
|
||||
if worker == 0 {
|
||||
self.log.clone()
|
||||
} else {
|
||||
self.root.join(format!("protocol-{worker}.jsonl"))
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_release(&self, worker: usize) -> PathBuf {
|
||||
if worker == 0 {
|
||||
self.release.clone()
|
||||
} else {
|
||||
self.root.join(format!("release-{worker}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_frames(&self, worker: usize) -> Vec<Value> {
|
||||
fs::read_to_string(self.worker_log(worker))
|
||||
.unwrap_or_default()
|
||||
.lines()
|
||||
.filter_map(|line| serde_json::from_str(line).ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn worker_count(&self, worker: usize, method: &str) -> usize {
|
||||
self.worker_frames(worker)
|
||||
.iter()
|
||||
.filter(|frame| frame["method"] == method)
|
||||
.count()
|
||||
}
|
||||
|
||||
fn open_stack(&self, modes: [&str; 2], overlap: bool) -> EditorSession {
|
||||
self.open_stack_at(modes, overlap, &self.source)
|
||||
}
|
||||
|
||||
fn open_stack_at(
|
||||
&self,
|
||||
modes: [&str; 2],
|
||||
overlap: bool,
|
||||
path: &std::path::Path,
|
||||
) -> EditorSession {
|
||||
let server = self.root.join("server.rb");
|
||||
fs::write(&server, SERVER).unwrap();
|
||||
let command = |worker: usize, mode: &str| {
|
||||
if mode == "spawn" {
|
||||
json!([self.root.join("unavailable-language-server")])
|
||||
} else {
|
||||
json!([
|
||||
"ruby",
|
||||
server,
|
||||
mode,
|
||||
self.worker_log(worker),
|
||||
self.worker_release(worker),
|
||||
self.source,
|
||||
self.target,
|
||||
self.alternate
|
||||
])
|
||||
}
|
||||
};
|
||||
let servers: Vec<_> = modes
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(worker, mode)| json!({"command": command(worker, mode)}))
|
||||
.collect();
|
||||
let mut languages = json!({"python": {"extensions": ["rs"], "servers": servers}});
|
||||
if overlap {
|
||||
languages["zzz-other"] =
|
||||
json!({"extensions": ["rs"], "command": command(2, "controlled")});
|
||||
}
|
||||
let config = format!(
|
||||
"Vamp.configure do |config|\n config.clipboard_mode = 'separate'\n JSON.parse({}).each {{ |language, options| config.lsp[language] = options }}\n config.bind 'K', config.core.lsp.hover\nend\n",
|
||||
serde_json::to_string(&languages.to_string()).unwrap(),
|
||||
);
|
||||
let config_path = self.root.join("config.rb");
|
||||
fs::write(&config_path, config).unwrap();
|
||||
EditorSession::open(
|
||||
Some(path.to_path_buf()),
|
||||
vec!["text".into(), "diagnostics".into()],
|
||||
Some(&config_path),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn control(&self, worker: usize, id: u64, mut command: Value) {
|
||||
command["id"] = json!(id);
|
||||
let path = self.worker_release(worker).with_extension("control");
|
||||
let temporary = path.with_extension("pending");
|
||||
fs::write(&temporary, command.to_string()).unwrap();
|
||||
fs::rename(temporary, path).unwrap();
|
||||
}
|
||||
|
||||
fn acknowledged(&self, worker: usize, id: u64) -> bool {
|
||||
self.worker_frames(worker)
|
||||
.iter()
|
||||
.any(|frame| frame["id"] == format!("control-{id}") && frame.get("result").is_some())
|
||||
}
|
||||
|
||||
fn frames(&self) -> Vec<Value> {
|
||||
fs::read_to_string(&self.log)
|
||||
.unwrap_or_default()
|
||||
@@ -959,3 +1084,540 @@ fn lsp_hover_absent_unsupported_and_invalid_results_preserve_native_editing() {
|
||||
assert_eq!(fixture.method_count("textDocument/definition"), 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn diagnostic_messages(session: &EditorSession) -> Vec<&str> {
|
||||
session
|
||||
.editor()
|
||||
.diagnostics()
|
||||
.map(|diagnostic| diagnostic.message.as_str())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn wait_protocol(fixture: &Fixture, predicate: impl Fn() -> bool) {
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
while !predicate() {
|
||||
assert!(
|
||||
Instant::now() < deadline,
|
||||
"fixture protocol gate did not complete: {:?}",
|
||||
fixture.frames()
|
||||
);
|
||||
thread::sleep(Duration::from_millis(2));
|
||||
}
|
||||
// Deliberately no session.poll(): outputs may be queued across a rename/close.
|
||||
}
|
||||
|
||||
fn publish(
|
||||
session: &mut EditorSession,
|
||||
fixture: &Fixture,
|
||||
worker: usize,
|
||||
id: u64,
|
||||
command: Value,
|
||||
expected: &[&str],
|
||||
) {
|
||||
fixture.control(worker, id, command);
|
||||
until(session, fixture, |session| {
|
||||
fixture.acknowledged(worker, id) && diagnostic_messages(session) == expected
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_python_stack_merges_in_assignment_order_and_routes_positions_only_to_ty() {
|
||||
let fixture = Fixture::new();
|
||||
let mut session = fixture.open_stack(["controlled-gated", "controlled"], true);
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.worker_count(0, "initialize") == 1
|
||||
&& fixture.worker_count(1, "textDocument/didOpen") == 1
|
||||
});
|
||||
assert_eq!(fixture.worker_count(0, "textDocument/didOpen"), 0);
|
||||
session.handle(Input::Char('A'));
|
||||
session.handle(Input::Char('!'));
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().lines(), &["A😀e\u{301}Z!", ""]);
|
||||
session.execute("lsp.hover");
|
||||
fixture.release();
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().hover().is_some()
|
||||
});
|
||||
assert_eq!(fixture.worker_count(0, "textDocument/hover"), 1);
|
||||
assert_eq!(fixture.worker_count(1, "textDocument/hover"), 0);
|
||||
for worker in 0..2 {
|
||||
let frames = fixture.worker_frames(worker);
|
||||
let initialize = frames
|
||||
.iter()
|
||||
.find(|frame| frame["method"] == "initialize")
|
||||
.unwrap();
|
||||
assert!(
|
||||
initialize["params"]["capabilities"]["textDocument"]
|
||||
.get("diagnostic")
|
||||
.is_none()
|
||||
);
|
||||
let open = frames
|
||||
.iter()
|
||||
.find(|frame| frame["method"] == "textDocument/didOpen")
|
||||
.unwrap();
|
||||
assert_eq!(open["params"]["textDocument"]["languageId"], "python");
|
||||
}
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
1,
|
||||
json!({"action": "publish", "message": "ruff"}),
|
||||
&["ruff"],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
0,
|
||||
1,
|
||||
json!({"action": "publish", "message": "ty"}),
|
||||
&["ty", "ruff"],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
0,
|
||||
2,
|
||||
json!({"action": "publish", "message": "ty replaced"}),
|
||||
&["ty replaced", "ruff"],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
2,
|
||||
json!({"action": "clear"}),
|
||||
&["ty replaced"],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
3,
|
||||
json!({"action": "publish", "message": "duplicate"}),
|
||||
&["ty replaced", "duplicate"],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
0,
|
||||
3,
|
||||
json!({"action": "publish", "message": "duplicate"}),
|
||||
&["duplicate", "duplicate"],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
0,
|
||||
4,
|
||||
json!({"action": "clear"}),
|
||||
&["duplicate"],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
4,
|
||||
json!({"action": "clear"}),
|
||||
&[],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
0,
|
||||
5,
|
||||
json!({"action": "publish", "message": "ty first"}),
|
||||
&["ty first"],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
5,
|
||||
json!({"action": "publish", "message": "ruff second"}),
|
||||
&["ty first", "ruff second"],
|
||||
);
|
||||
|
||||
let target_id = edit_target(&mut session, &fixture);
|
||||
session.execute("lsp.definition");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().buffer_id() == target_id
|
||||
});
|
||||
assert_eq!(session.editor().lines(), &["😀e\u{301}disk", ""]);
|
||||
assert_eq!(session.editor().cursor(), Cursor { row: 0, column: 1 });
|
||||
assert_eq!(fixture.worker_count(0, "textDocument/definition"), 1);
|
||||
assert_eq!(fixture.worker_count(1, "textDocument/definition"), 0);
|
||||
session.execute("buffer 1");
|
||||
assert_eq!(diagnostic_messages(&session), ["ty first", "ruff second"]);
|
||||
session.handle(Input::Char('A'));
|
||||
session.handle(Input::Char('?'));
|
||||
session.handle(Input::Escape);
|
||||
assert!(diagnostic_messages(&session).is_empty());
|
||||
until(&mut session, &fixture, |_| {
|
||||
(0..2).all(|worker| {
|
||||
fixture.worker_frames(worker).iter().any(|frame| {
|
||||
frame["method"] == "textDocument/didChange"
|
||||
&& frame["params"]["contentChanges"][0]["text"] == "A😀e\u{301}Z!?\n"
|
||||
})
|
||||
})
|
||||
});
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
6,
|
||||
json!({"action": "publish", "version": 1, "message": "obsolete"}),
|
||||
&[],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
7,
|
||||
json!({"action": "publish", "message": "new lint"}),
|
||||
&["new lint"],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
0,
|
||||
6,
|
||||
json!({"action": "clear"}),
|
||||
&["new lint"],
|
||||
);
|
||||
session.execute("w");
|
||||
until(&mut session, &fixture, |_| {
|
||||
(0..2).all(|worker| fixture.worker_count(worker, "textDocument/didSave") == 1)
|
||||
});
|
||||
session.execute("bdelete 1");
|
||||
until(&mut session, &fixture, |_| {
|
||||
(0..2).all(|worker| fixture.worker_count(worker, "textDocument/didClose") == 1)
|
||||
});
|
||||
assert_eq!(session.editor().buffer_id(), target_id);
|
||||
assert!(
|
||||
!fixture.worker_log(2).exists(),
|
||||
"overlapping losing language must never start"
|
||||
);
|
||||
drop(session);
|
||||
for worker in 0..2 {
|
||||
assert_eq!(fixture.worker_count(worker, "shutdown"), 1);
|
||||
assert_eq!(fixture.worker_count(worker, "exit"), 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_python_stack_failures_retire_only_the_offender_in_active_and_parked_buffers() {
|
||||
for failing in 0..2 {
|
||||
for failure in ["spawn", "malformed", "die"] {
|
||||
let fixture = Fixture::new();
|
||||
fs::write(&fixture.target, "A😀e\u{301}Z\n").unwrap();
|
||||
let mut modes = ["controlled"; 2];
|
||||
if failure == "spawn" {
|
||||
modes[failing] = "spawn";
|
||||
}
|
||||
let mut session = fixture.open_stack(modes, true);
|
||||
let survivor = 1 - failing;
|
||||
until(&mut session, &fixture, |session| {
|
||||
fixture.worker_count(survivor, "textDocument/didOpen") == 1
|
||||
&& if failure == "spawn" {
|
||||
session.editor().message().contains("failed to start")
|
||||
} else {
|
||||
fixture.worker_count(failing, "textDocument/didOpen") == 1
|
||||
}
|
||||
});
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
survivor,
|
||||
1,
|
||||
json!({"action": "publish", "message": "healthy source"}),
|
||||
&["healthy source"],
|
||||
);
|
||||
if failure != "spawn" {
|
||||
let expected = if failing == 0 {
|
||||
["failed source", "healthy source"]
|
||||
} else {
|
||||
["healthy source", "failed source"]
|
||||
};
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
failing,
|
||||
1,
|
||||
json!({"action": "publish", "message": "failed source"}),
|
||||
&expected,
|
||||
);
|
||||
}
|
||||
session.execute(&format!("edit {}", fixture.target.display()));
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.worker_count(survivor, "textDocument/didOpen") == 2
|
||||
&& (failure == "spawn"
|
||||
|| fixture.worker_count(failing, "textDocument/didOpen") == 2)
|
||||
});
|
||||
let target_uri = fixture
|
||||
.worker_frames(survivor)
|
||||
.into_iter()
|
||||
.filter(|frame| frame["method"] == "textDocument/didOpen")
|
||||
.nth(1)
|
||||
.unwrap()["params"]["textDocument"]["uri"]
|
||||
.clone();
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
survivor,
|
||||
2,
|
||||
json!({"action": "publish", "uri": target_uri, "message": "healthy target"}),
|
||||
&["healthy target"],
|
||||
);
|
||||
if failure != "spawn" {
|
||||
let expected = if failing == 0 {
|
||||
["failed target", "healthy target"]
|
||||
} else {
|
||||
["healthy target", "failed target"]
|
||||
};
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
failing,
|
||||
2,
|
||||
json!({"action": "publish", "uri": target_uri, "message": "failed target"}),
|
||||
&expected,
|
||||
);
|
||||
fixture.control(failing, 3, json!({"action": failure}));
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().message().contains("server stopped")
|
||||
&& diagnostic_messages(session) == ["healthy target"]
|
||||
});
|
||||
}
|
||||
assert_eq!(diagnostic_messages(&session), ["healthy target"]);
|
||||
session.execute("buffer 1");
|
||||
assert_eq!(diagnostic_messages(&session), ["healthy source"]);
|
||||
session.handle(Input::Char('A'));
|
||||
session.handle(Input::Char('!'));
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().lines(), &["A😀e\u{301}Z!", ""]);
|
||||
assert!(diagnostic_messages(&session).is_empty());
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.worker_count(survivor, "textDocument/didChange") == 1
|
||||
});
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
survivor,
|
||||
3,
|
||||
json!({"action": "publish", "version": 1, "message": "stale healthy"}),
|
||||
&[],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
survivor,
|
||||
4,
|
||||
json!({"action": "publish", "message": "healthy edit"}),
|
||||
&["healthy edit"],
|
||||
);
|
||||
if failing == 0 {
|
||||
session.execute("lsp.definition");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().message().contains("no running server")
|
||||
});
|
||||
assert_eq!(fixture.worker_count(1, "textDocument/definition"), 0);
|
||||
} else {
|
||||
session.execute("lsp.hover");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().hover().is_some()
|
||||
});
|
||||
}
|
||||
assert!(!fixture.worker_log(2).exists());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_python_stack_discards_queued_same_revision_diagnostics_after_rename_and_close() {
|
||||
let fixture = Fixture::new();
|
||||
let mut session = fixture.open_stack(["controlled", "controlled"], false);
|
||||
until(&mut session, &fixture, |_| {
|
||||
(0..2).all(|worker| fixture.worker_count(worker, "textDocument/didOpen") == 1)
|
||||
});
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
0,
|
||||
1,
|
||||
json!({"action": "publish", "message": "before rename"}),
|
||||
&["before rename"],
|
||||
);
|
||||
let revision = session.editor().context().revision;
|
||||
for worker in 0..2 {
|
||||
fixture.control(
|
||||
worker,
|
||||
2,
|
||||
json!({"action": "publish", "message": "queued old path"}),
|
||||
);
|
||||
}
|
||||
wait_protocol(&fixture, || {
|
||||
(0..2).all(|worker| fixture.acknowledged(worker, 2))
|
||||
});
|
||||
let renamed = fixture.root.join("renamed.rs");
|
||||
session.execute(&format!("w {}", renamed.display()));
|
||||
assert_eq!(session.editor().context().revision, revision);
|
||||
until(&mut session, &fixture, |_| {
|
||||
(0..2).all(|worker| {
|
||||
fixture.worker_count(worker, "textDocument/didClose") == 1
|
||||
&& fixture.worker_count(worker, "textDocument/didOpen") == 2
|
||||
})
|
||||
});
|
||||
assert!(diagnostic_messages(&session).is_empty());
|
||||
let old_uri = fixture
|
||||
.worker_frames(0)
|
||||
.into_iter()
|
||||
.find(|frame| frame["method"] == "textDocument/didOpen")
|
||||
.unwrap()["params"]["textDocument"]["uri"]
|
||||
.clone();
|
||||
let new_uri = fixture
|
||||
.worker_frames(0)
|
||||
.into_iter()
|
||||
.filter(|frame| frame["method"] == "textDocument/didOpen")
|
||||
.nth(1)
|
||||
.unwrap()["params"]["textDocument"]["uri"]
|
||||
.clone();
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
0,
|
||||
3,
|
||||
json!({"action": "publish", "uri": old_uri, "message": "closed old path"}),
|
||||
&[],
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
3,
|
||||
json!({"action": "publish", "uri": new_uri, "message": "new path"}),
|
||||
&["new path"],
|
||||
);
|
||||
fixture.control(
|
||||
1,
|
||||
4,
|
||||
json!({"action": "publish", "uri": new_uri, "message": "queued closed buffer"}),
|
||||
);
|
||||
wait_protocol(&fixture, || fixture.acknowledged(1, 4));
|
||||
session.execute("bdelete 1");
|
||||
until(&mut session, &fixture, |_| {
|
||||
(0..2).all(|worker| fixture.worker_count(worker, "textDocument/didClose") == 2)
|
||||
});
|
||||
assert!(!session.editor().has_buffer(1));
|
||||
assert!(diagnostic_messages(&session).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_python_stack_aggregate_message_limit_preserves_the_healthy_contribution() {
|
||||
let fixture = Fixture::new();
|
||||
let mut session = fixture.open_stack(["controlled", "controlled"], false);
|
||||
until(&mut session, &fixture, |_| {
|
||||
(0..2).all(|worker| fixture.worker_count(worker, "textDocument/didOpen") == 1)
|
||||
});
|
||||
let large = "x".repeat(2 * 1024 * 1024 + 1);
|
||||
fixture.control(0, 1, json!({"action": "publish", "message": large}));
|
||||
until(&mut session, &fixture, |session| {
|
||||
fixture.acknowledged(0, 1) && session.editor().diagnostics().count() == 1
|
||||
});
|
||||
assert_eq!(
|
||||
session.editor().diagnostics().next().unwrap().message.len(),
|
||||
large.len()
|
||||
);
|
||||
fixture.control(1, 1, json!({"action": "publish", "message": large}));
|
||||
until(&mut session, &fixture, |session| {
|
||||
session
|
||||
.editor()
|
||||
.message()
|
||||
.contains("combined diagnostic messages exceed 4 MiB")
|
||||
});
|
||||
assert_eq!(session.editor().diagnostics().count(), 1);
|
||||
assert_eq!(
|
||||
session.editor().diagnostics().next().unwrap().message.len(),
|
||||
large.len()
|
||||
);
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
0,
|
||||
2,
|
||||
json!({"action": "publish", "message": "healthy replacement"}),
|
||||
&["healthy replacement"],
|
||||
);
|
||||
session.handle(Input::Char('A'));
|
||||
session.handle(Input::Char('!'));
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().lines(), &["A😀e\u{301}Z!", ""]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_python_stack_seeds_dirty_unserved_documents_before_lazy_startup() {
|
||||
let mut fixture = Fixture::new();
|
||||
fixture.target = fixture.root.join("target.txt");
|
||||
fs::write(&fixture.target, "disk\n").unwrap();
|
||||
let mut session = fixture.open_stack_at(["controlled", "controlled"], false, &fixture.target);
|
||||
session.execute("lsp.diagnostics");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().message().contains("no running server")
|
||||
});
|
||||
assert!(!fixture.worker_log(0).exists());
|
||||
assert!(!fixture.worker_log(1).exists());
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Paste("😀e\u{301}".into()));
|
||||
session.handle(Input::Escape);
|
||||
session.execute(&format!("edit {}", fixture.source.display()));
|
||||
until(&mut session, &fixture, |_| {
|
||||
(0..2).all(|worker| fixture.worker_count(worker, "textDocument/didOpen") == 1)
|
||||
});
|
||||
session.execute("lsp.definition");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().buffer_id() == 1
|
||||
});
|
||||
assert_eq!(session.editor().lines(), &["😀e\u{301}disk", ""]);
|
||||
assert_eq!(session.editor().cursor(), Cursor { row: 0, column: 1 });
|
||||
assert!(session.editor().dirty());
|
||||
assert_eq!(fs::read_to_string(&fixture.target).unwrap(), "disk\n");
|
||||
assert_eq!(fixture.worker_count(0, "textDocument/definition"), 1);
|
||||
assert_eq!(fixture.worker_count(1, "textDocument/definition"), 0);
|
||||
for worker in 0..2 {
|
||||
assert_eq!(
|
||||
fixture.worker_count(worker, "textDocument/didOpen"),
|
||||
1,
|
||||
"the unserved target is cached, not opened on the wire"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_python_stack_does_not_fall_back_to_ruff_when_ty_lacks_position_capabilities() {
|
||||
let fixture = Fixture::new();
|
||||
let mut session = fixture.open_stack(["controlled-unsupported", "controlled"], false);
|
||||
until(&mut session, &fixture, |_| {
|
||||
(0..2).all(|worker| fixture.worker_count(worker, "textDocument/didOpen") == 1)
|
||||
});
|
||||
publish(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
1,
|
||||
json!({"action": "publish", "message": "lint remains available"}),
|
||||
&["lint remains available"],
|
||||
);
|
||||
for command in ["lsp.definition", "lsp.hover"] {
|
||||
let previous_message = session.editor().message().to_owned();
|
||||
session.execute(command);
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().message() != previous_message
|
||||
});
|
||||
assert_eq!(session.editor().buffer_id(), 1);
|
||||
assert_eq!(diagnostic_messages(&session), ["lint remains available"]);
|
||||
}
|
||||
for worker in 0..2 {
|
||||
assert_eq!(fixture.worker_count(worker, "textDocument/definition"), 0);
|
||||
assert_eq!(fixture.worker_count(worker, "textDocument/hover"), 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user