feat: add live-cycling LSP completion
This commit is contained in:
+473
-1
@@ -68,12 +68,13 @@ initialize_id = nil
|
||||
initialized_sent = false
|
||||
held = []
|
||||
semantic_held = []
|
||||
completion_held = []
|
||||
definition_count = 0
|
||||
source_uri = uri(source)
|
||||
documents = {}
|
||||
control_id = nil
|
||||
loop do
|
||||
if initialize_id && !initialized_sent && (!['gated', 'hover-gated', 'controlled-gated'].include?(mode) || File.exist?(release))
|
||||
if initialize_id && !initialized_sent && (!['gated', 'hover-gated', 'completion-gated', 'controlled-gated'].include?(mode) || File.exist?(release))
|
||||
sync = case mode
|
||||
when 'numeric' then 1
|
||||
when 'none' then {'openClose' => false, 'change' => 0, 'save' => false}
|
||||
@@ -83,6 +84,7 @@ loop do
|
||||
capabilities = {'positionEncoding' => 'utf-16',
|
||||
'textDocumentSync' => sync, 'definitionProvider' => ['unsupported', 'controlled-unsupported'].include?(mode) ? false : {'workDoneProgress' => false},
|
||||
'hoverProvider' => ['hover-unsupported', 'controlled-unsupported'].include?(mode) ? false : {'workDoneProgress' => false}}
|
||||
capabilities['completionProvider'] = {'resolveProvider' => true} unless ['completion-unsupported', 'controlled-unsupported'].include?(mode)
|
||||
if mode.include?('semantic')
|
||||
capabilities['semanticTokensProvider'] = {
|
||||
'legend' => {'tokenTypes' => ['custom', 'variable', 'function', 'comment', 'type'],
|
||||
@@ -115,6 +117,10 @@ loop do
|
||||
request = semantic_held.delete_at(command.fetch('index', 0))
|
||||
raise 'no held semantic request' unless request
|
||||
emit({'id' => request.fetch('id')}.merge(command.fetch('response')))
|
||||
when 'completion-result'
|
||||
request = completion_held.delete_at(command.fetch('index', 0))
|
||||
raise 'no held completion request' unless request
|
||||
emit({'id' => request.fetch('id')}.merge(command.fetch('response')))
|
||||
when 'refresh'
|
||||
emit({'id' => "refresh-#{control_id}", 'method' => 'workspace/semanticTokens/refresh'})
|
||||
when 'die'
|
||||
@@ -223,6 +229,21 @@ loop do
|
||||
hover = contents.nil? ? nil : {'contents' => contents, 'range' => range(mode == 'hover-invalid-range' ? 999 : 0, 2, 4)}
|
||||
result(frame['id'], hover)
|
||||
end
|
||||
when 'textDocument/completion'
|
||||
if mode.include?('completion-controlled')
|
||||
completion_held << frame
|
||||
elsif mode == 'completion-empty'
|
||||
result(frame['id'], nil)
|
||||
else
|
||||
result(frame['id'], [
|
||||
{'label' => 'alps', 'detail' => 'second candidate', 'sortText' => '2'},
|
||||
{'label' => 'alpha', 'detail' => 'first candidate', 'sortText' => '1'},
|
||||
{'label' => 'unrelated', 'sortText' => '0'},
|
||||
{'label' => 'alSnippet', 'insertTextFormat' => 2, 'insertText' => 'al${1:snippet}'},
|
||||
{'label' => 'alCommand', 'command' => {'title' => 'unsupported', 'command' => 'fix'}},
|
||||
{'label' => 'alIndented', 'insertTextMode' => 2}
|
||||
])
|
||||
end
|
||||
when 'textDocument/semanticTokens/full', 'textDocument/semanticTokens/range'
|
||||
if mode.start_with?('controlled')
|
||||
semantic_held << frame
|
||||
@@ -2238,3 +2259,454 @@ fn lsp_semantic_primary_retirement_clears_active_and_parked_without_sibling_take
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn completion_reply(session: &mut EditorSession, fixture: &Fixture, id: u64, response: Value) {
|
||||
fixture.control(
|
||||
0,
|
||||
id,
|
||||
json!({"action": "completion-result", "response": response}),
|
||||
);
|
||||
until(session, fixture, |_| fixture.acknowledged(0, id));
|
||||
// The acknowledgement is sent after the worker queued preceding result updates.
|
||||
session.poll();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_completion_live_cycle_original_and_ordinary_keys_use_one_request() {
|
||||
for (input, expected, mode) in [
|
||||
(Input::Char('!'), "alpha! tail\n", Mode::Insert),
|
||||
(Input::Enter, "alpha\n tail\n", Mode::Insert),
|
||||
(Input::Char('\t'), "alpha\t tail\n", Mode::Insert),
|
||||
(Input::Escape, "alpha tail\n", Mode::Normal),
|
||||
] {
|
||||
let fixture = Fixture::new();
|
||||
fs::write(&fixture.source, "al tail\n").unwrap();
|
||||
let mut session = fixture.open("completion");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().diagnostics().count() == 2
|
||||
});
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Right);
|
||||
session.handle(Input::Right);
|
||||
assert!(session.editor().completion().is_none());
|
||||
assert_eq!(fixture.method_count("textDocument/completion"), 0);
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().completion().is_some()
|
||||
});
|
||||
let popup = session.editor().completion().unwrap();
|
||||
assert_eq!(
|
||||
popup
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| item.label.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
["alpha", "alps"]
|
||||
);
|
||||
assert_eq!(popup.items[0].detail.as_deref(), Some("first candidate"));
|
||||
assert_eq!(popup.selected, 1);
|
||||
assert_eq!(session.editor().context().text, "alpha tail\n");
|
||||
assert_eq!(session.editor().cursor(), Cursor { row: 0, column: 5 });
|
||||
session.handle(Input::Ctrl('n'));
|
||||
assert_eq!(session.editor().context().text, "alps tail\n");
|
||||
session.handle(Input::Ctrl('j'));
|
||||
assert_eq!(session.editor().completion().unwrap().selected, 0);
|
||||
assert_eq!(session.editor().context().text, "al tail\n");
|
||||
session.handle(Input::Ctrl('k'));
|
||||
assert_eq!(session.editor().context().text, "alps tail\n");
|
||||
session.handle(Input::Ctrl('k'));
|
||||
assert_eq!(session.editor().context().text, "alpha tail\n");
|
||||
session.handle(input);
|
||||
assert!(session.editor().completion().is_none());
|
||||
assert_eq!(session.editor().context().text, expected);
|
||||
assert_eq!(session.editor().mode(), mode);
|
||||
assert_eq!(fixture.method_count("textDocument/completion"), 1);
|
||||
assert_eq!(fixture.method_count("completionItem/resolve"), 0);
|
||||
if mode == Mode::Insert {
|
||||
session.handle(Input::Escape);
|
||||
}
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().context().text, "al tail\n");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_completion_groups_typed_prefix_and_only_repeats_the_final_candidate() {
|
||||
let fixture = Fixture::new();
|
||||
fs::write(&fixture.source, " tail\n").unwrap();
|
||||
let mut session = fixture.open("completion");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().diagnostics().count() == 2
|
||||
});
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Char('a'));
|
||||
session.handle(Input::Char('l'));
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().completion().is_some()
|
||||
});
|
||||
session.handle(Input::Ctrl('n'));
|
||||
session.handle(Input::Ctrl('j'));
|
||||
session.handle(Input::Ctrl('k'));
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().context().text, "alps tail\n");
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().context().text, " tail\n");
|
||||
session.handle(Input::Char('.'));
|
||||
assert_eq!(session.editor().context().text, "alps tail\n");
|
||||
assert_eq!(fixture.method_count("textDocument/completion"), 1);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().context().text, " tail\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_completion_primary_only_translates_and_preserves_secondary_carets() {
|
||||
let fixture = Fixture::new();
|
||||
fs::write(&fixture.source, "al tail\nal tail\n").unwrap();
|
||||
let mut session = fixture.open("completion-controlled");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().diagnostics().count() == 2
|
||||
});
|
||||
session.handle(Input::Ctrl('e'));
|
||||
assert_eq!(session.editor().carets().len(), 2);
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Right);
|
||||
session.handle(Input::Right);
|
||||
let original_carets = session.editor().carets();
|
||||
assert_eq!(session.editor().cursor(), Cursor { row: 1, column: 2 });
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.method_count("textDocument/completion") == 1
|
||||
});
|
||||
completion_reply(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
json!({"result": [{
|
||||
"label": "alpha",
|
||||
"textEdit": {"range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 2}}, "newText": "alpha"},
|
||||
"additionalTextEdits": [{"range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}, "newText": "use X;\n"}]
|
||||
}]}),
|
||||
);
|
||||
assert_eq!(
|
||||
session.editor().context().text,
|
||||
"use X;\nal tail\nalpha tail\n"
|
||||
);
|
||||
assert_eq!(
|
||||
session.editor().carets(),
|
||||
[Cursor { row: 1, column: 2 }, Cursor { row: 2, column: 5 }]
|
||||
);
|
||||
session.handle(Input::Ctrl('n'));
|
||||
assert_eq!(session.editor().context().text, "al tail\nal tail\n");
|
||||
assert_eq!(session.editor().carets(), original_carets);
|
||||
session.handle(Input::Ctrl('k'));
|
||||
session.handle(Input::Escape);
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().context().text, "al tail\nal tail\n");
|
||||
let request = fixture
|
||||
.frames()
|
||||
.into_iter()
|
||||
.find(|frame| frame["method"] == "textDocument/completion")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
request["params"]["position"],
|
||||
json!({"line": 1, "character": 2})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_completion_utf16_text_edit_insert_replace_and_defaults_are_atomic() {
|
||||
let fixture = Fixture::new();
|
||||
let mut session = fixture.open("completion-controlled");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().diagnostics().count() == 2
|
||||
});
|
||||
session.handle(Input::Char('i'));
|
||||
for _ in 0..3 {
|
||||
session.handle(Input::Right);
|
||||
}
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.method_count("textDocument/completion") == 1
|
||||
});
|
||||
let range = |start, end| json!({"start": {"line": 0, "character": start}, "end": {"line": 0, "character": end}});
|
||||
completion_reply(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
json!({"result": {
|
||||
"isIncomplete": true,
|
||||
"itemDefaults": {"editRange": {"insert": range(1, 5), "replace": range(1, 6)}, "insertTextFormat": 1, "insertTextMode": 1},
|
||||
"items": [
|
||||
{"label": "first", "sortText": "1", "textEdit": {"range": range(1, 5), "newText": "λ"}},
|
||||
{"label": "second", "sortText": "2", "textEdit": {"insert": range(1, 5), "replace": range(1, 6), "newText": "omega"}, "additionalTextEdits": [
|
||||
{"range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 0}}, "newText": "tail"}
|
||||
]},
|
||||
{"label": "default", "sortText": "3", "textEditText": "δ"},
|
||||
{"label": "filtered", "sortText": "0", "filterText": "does-not-match", "textEditText": "bad"}
|
||||
]
|
||||
}}),
|
||||
);
|
||||
assert_eq!(session.editor().context().text, "AλZ\n");
|
||||
let popup = session.editor().completion().unwrap();
|
||||
assert_eq!(popup.items.len(), 3);
|
||||
assert_eq!(
|
||||
popup.items[0].edits[0].start,
|
||||
TextPosition { row: 0, column: 1 }
|
||||
);
|
||||
assert_eq!(
|
||||
popup.items[0].edits[0].end,
|
||||
TextPosition { row: 0, column: 3 }
|
||||
);
|
||||
session.handle(Input::Ctrl('j'));
|
||||
assert_eq!(session.editor().context().text, "Aomega\ntail");
|
||||
assert_eq!(session.editor().cursor(), Cursor { row: 0, column: 6 });
|
||||
session.handle(Input::Ctrl('j'));
|
||||
assert_eq!(session.editor().context().text, "Aδ\n");
|
||||
session.handle(Input::Ctrl('j'));
|
||||
assert_eq!(session.editor().context().text, "A😀e\u{301}Z\n");
|
||||
session.handle(Input::Escape);
|
||||
assert!(!session.editor().dirty());
|
||||
let frames = fixture.frames();
|
||||
let request = frames
|
||||
.iter()
|
||||
.find(|frame| frame["method"] == "textDocument/completion")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
request["params"]["position"],
|
||||
json!({"line": 0, "character": 5})
|
||||
);
|
||||
assert_eq!(request["params"]["context"], json!({"triggerKind": 1}));
|
||||
let capabilities = &frames
|
||||
.iter()
|
||||
.find(|frame| frame["method"] == "initialize")
|
||||
.unwrap()["params"]["capabilities"]["textDocument"]["completion"];
|
||||
assert_eq!(capabilities["completionItem"]["snippetSupport"], false);
|
||||
assert_eq!(capabilities["completionItem"]["insertReplaceSupport"], true);
|
||||
assert_eq!(
|
||||
capabilities["completionItem"]["resolveSupport"]["properties"],
|
||||
json!([])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_completion_waits_for_initialize_and_stale_payloads_are_not_decoded() {
|
||||
let fixture = Fixture::new();
|
||||
fs::write(&fixture.source, "al\n").unwrap();
|
||||
let mut session = fixture.open("completion-gated");
|
||||
session.handle(Input::Char('A'));
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.method_count("initialize") == 1
|
||||
});
|
||||
assert_eq!(fixture.method_count("textDocument/completion"), 0);
|
||||
fixture.release();
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().completion().is_some()
|
||||
});
|
||||
assert_eq!(session.editor().context().text, "alpha\n");
|
||||
drop(session);
|
||||
|
||||
for input in [Input::Escape, Input::Right, Input::Char('!')] {
|
||||
let fixture = Fixture::new();
|
||||
let mut session = fixture.open("completion-controlled");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().diagnostics().count() == 2
|
||||
});
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.method_count("textDocument/completion") == 1
|
||||
});
|
||||
session.handle(input);
|
||||
let text = session.editor().context().text;
|
||||
completion_reply(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
json!({"result": {"invalid": "stale"}}),
|
||||
);
|
||||
assert!(session.editor().completion().is_none());
|
||||
assert_eq!(session.editor().context().text, text);
|
||||
session.execute("lsp.hover");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().hover().is_some()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_completion_feature_errors_keep_healthy_services() {
|
||||
let fixture = Fixture::new();
|
||||
let mut session = fixture.open("completion-controlled");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().diagnostics().count() == 2
|
||||
});
|
||||
let range = |start, end| json!({"start": {"line": 0, "character": start}, "end": {"line": 0, "character": end}});
|
||||
let invalid = vec![
|
||||
json!({"result": {"items": []}}),
|
||||
json!({"result": [{"detail": "missing label"}]}),
|
||||
json!({"result": [{"label": "x", "textEdit": {"range": range(0, 99), "newText": "x"}}]}),
|
||||
json!({"result": [{"label": "x", "textEdit": {"range": range(0, 2), "newText": "x"}}]}),
|
||||
json!({"result": [{"label": "x", "textEdit": {"range": range(0, 4), "newText": "x"}}]}),
|
||||
json!({"result": [{"label": "x", "textEdit": {"range": range(1, 5), "newText": "x"}}]}),
|
||||
json!({"result": [{"label": "x", "textEdit": {"range": {"start": {"line": 0, "character": 0}, "end": {"line": 1, "character": 0}}, "newText": "x"}}]}),
|
||||
json!({"result": [{"label": "x", "textEdit": {"range": range(0, 1), "newText": "\u{1b}"}}]}),
|
||||
json!({"result": [{"label": "x", "textEdit": {"insert": range(0, 3), "replace": range(0, 1), "newText": "x"}}]}),
|
||||
json!({"result": [{"label": "x", "textEdit": {"range": range(0, 1), "newText": "x"}, "additionalTextEdits": [{"range": range(0, 1), "newText": "bad"}]}]}),
|
||||
json!({"result": [{"label": "x", "textEdit": {"range": range(0, 0), "newText": "x"}, "additionalTextEdits": [{"range": range(0, 0), "newText": "bad"}]}]}),
|
||||
json!({"result": [{"label": "x", "additionalTextEdits": {}}]}),
|
||||
json!({"error": {"code": -32603, "message": "completion unavailable"}}),
|
||||
json!({"error": {"message": "malformed error"}}),
|
||||
];
|
||||
for (index, response) in invalid.into_iter().enumerate() {
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.method_count("textDocument/completion") == index + 1
|
||||
});
|
||||
completion_reply(&mut session, &fixture, index as u64 + 1, response);
|
||||
assert!(session.editor().completion().is_none());
|
||||
assert_eq!(session.editor().context().text, "A😀e\u{301}Z\n");
|
||||
assert_eq!(session.editor().diagnostics().count(), 2);
|
||||
session.handle(Input::Escape);
|
||||
}
|
||||
session.execute("lsp.hover");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().hover().is_some()
|
||||
});
|
||||
session.handle(Input::Escape);
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Char('!'));
|
||||
session.handle(Input::Escape);
|
||||
assert_eq!(session.editor().context().text, "!A😀e\u{301}Z\n");
|
||||
session.handle(Input::Char('u'));
|
||||
assert_eq!(session.editor().context().text, "A😀e\u{301}Z\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_completion_fallback_filter_text_insert_text_and_empty_shapes() {
|
||||
let fixture = Fixture::new();
|
||||
fs::write(&fixture.source, "al suffix\n").unwrap();
|
||||
let mut session = fixture.open("completion-controlled");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().diagnostics().count() == 2
|
||||
});
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Right);
|
||||
session.handle(Input::Right);
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.method_count("textDocument/completion") == 1
|
||||
});
|
||||
completion_reply(
|
||||
&mut session,
|
||||
&fixture,
|
||||
1,
|
||||
json!({"result": [
|
||||
{"label": "display name", "filterText": "alpha", "insertText": "actual", "sortText": "1"},
|
||||
{"label": "alLabel", "sortText": "2"},
|
||||
{"label": "does not match", "filterText": "no", "insertText": "must not insert"}
|
||||
]}),
|
||||
);
|
||||
assert_eq!(session.editor().context().text, "actual suffix\n");
|
||||
assert_eq!(
|
||||
session.editor().completion().unwrap().items[0].label,
|
||||
"display name"
|
||||
);
|
||||
session.handle(Input::Ctrl('j'));
|
||||
assert_eq!(session.editor().context().text, "alLabel suffix\n");
|
||||
session.handle(Input::Ctrl('j'));
|
||||
assert_eq!(session.editor().context().text, "al suffix\n");
|
||||
session.handle(Input::Escape);
|
||||
for (index, result) in [
|
||||
Value::Null,
|
||||
json!([]),
|
||||
json!({"isIncomplete": false, "items": []}),
|
||||
json!([{"label": "alSnippet", "insertTextFormat": 2, "insertText": "${1:al}"}]),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.method_count("textDocument/completion") == index + 2
|
||||
});
|
||||
completion_reply(
|
||||
&mut session,
|
||||
&fixture,
|
||||
index as u64 + 2,
|
||||
json!({"result": result}),
|
||||
);
|
||||
assert!(session.editor().completion().is_none());
|
||||
assert_eq!(session.editor().context().text, "al suffix\n");
|
||||
session.handle(Input::Escape);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_completion_superseded_and_preinitialize_changed_sources_never_replace_text() {
|
||||
let fixture = Fixture::new();
|
||||
fs::write(&fixture.source, "al\n").unwrap();
|
||||
let mut session = fixture.open("completion-controlled");
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().diagnostics().count() == 2
|
||||
});
|
||||
session.handle(Input::Char('A'));
|
||||
session.handle(Input::Ctrl('n'));
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.method_count("textDocument/completion") == 2
|
||||
});
|
||||
fixture.control(0, 1, json!({"action": "completion-result", "index": 1, "response": {"result": [{"label": "alLatest"}]}}));
|
||||
until(&mut session, &fixture, |_| fixture.acknowledged(0, 1));
|
||||
assert_eq!(session.editor().context().text, "alLatest\n");
|
||||
completion_reply(
|
||||
&mut session,
|
||||
&fixture,
|
||||
2,
|
||||
json!({"result": {"malformed": "superseded"}}),
|
||||
);
|
||||
assert_eq!(session.editor().context().text, "alLatest\n");
|
||||
assert!(session.editor().completion().is_some());
|
||||
drop(session);
|
||||
|
||||
let fixture = Fixture::new();
|
||||
fs::write(&fixture.source, "al\n").unwrap();
|
||||
let mut session = fixture.open("completion-gated");
|
||||
session.handle(Input::Char('A'));
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.method_count("initialize") == 1
|
||||
});
|
||||
session.handle(Input::Char('!'));
|
||||
fixture.release();
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.method_count("textDocument/didOpen") == 1
|
||||
});
|
||||
assert!(session.editor().completion().is_none());
|
||||
assert_eq!(session.editor().context().text, "al!\n");
|
||||
assert_eq!(fixture.method_count("textDocument/completion"), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_completion_routes_only_to_first_configured_server_without_fallback() {
|
||||
let fixture = Fixture::new();
|
||||
let mut session = fixture.open_stack(["controlled-unsupported", "controlled"], false);
|
||||
until(&mut session, &fixture, |_| {
|
||||
fixture.worker_count(0, "textDocument/didOpen") == 1
|
||||
&& fixture.worker_count(1, "textDocument/didOpen") == 1
|
||||
});
|
||||
session.handle(Input::Char('i'));
|
||||
let previous_message = session.editor().message().to_owned();
|
||||
session.handle(Input::Ctrl('n'));
|
||||
until(&mut session, &fixture, |session| {
|
||||
session.editor().message() != previous_message
|
||||
});
|
||||
assert!(session.editor().completion().is_none());
|
||||
assert_eq!(fixture.worker_count(0, "textDocument/completion"), 0);
|
||||
assert_eq!(fixture.worker_count(1, "textDocument/completion"), 0);
|
||||
session.handle(Input::Char('!'));
|
||||
assert_eq!(session.editor().context().text, "!A😀e\u{301}Z\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user