feat: migrate LSP to opt-in native language servers

This commit is contained in:
Valdemar Lorenzen
2026-10-09 13:12:38 +02:00
parent 92679ecbe1
commit 30aa70672e
12 changed files with 2407 additions and 658 deletions
+55 -3
View File
@@ -5,7 +5,8 @@ use std::{
};
use crate::{
editor::{Editor, Effect, Input, Invocation},
editor::{Cursor, Editor, Effect, Input, Invocation, Mode},
lsp::Lsp,
plugin::{
DocumentEvent, DocumentEventKind, DocumentSnapshot, PluginEvent, PluginUpdate,
TextPosition, UpdateEffect,
@@ -37,6 +38,9 @@ pub struct EditorSession {
plugins: BTreeMap<String, RubyPlugin>,
documents: BTreeMap<usize, TrackedDocument>,
requests: BTreeMap<String, AsyncRequests>,
lsp: Lsp,
lsp_request_id: u64,
lsp_source_cursor: Option<Cursor>,
}
impl EditorSession {
@@ -117,6 +121,9 @@ impl EditorSession {
plugins,
documents: BTreeMap::new(),
requests: BTreeMap::new(),
lsp: Lsp::new(config.lsp),
lsp_request_id: 0,
lsp_source_cursor: None,
};
session.synchronize_documents();
Ok(session)
@@ -145,6 +152,31 @@ impl EditorSession {
}
fn dispatch(&mut self, invocation: Invocation) {
if invocation.plugin == "core" {
let result = match invocation.command.as_str() {
"lsp_definition" => {
if let Some(id) = self.lsp_request_id.checked_add(1) {
self.lsp_request_id = id;
let context = self.editor.context();
let source = InvocationSource {
buffer_id: context.buffer_id,
revision: context.revision,
};
self.requests.entry("core".into()).or_default().latest = Some((id, source));
self.lsp_source_cursor = Some(self.editor.cursor());
self.lsp.definition(&context, id)
} else {
Err("LSP request IDs exhausted".into())
}
}
"lsp_diagnostics" => self.lsp.diagnostics(self.editor.buffer_id()),
_ => unreachable!("session-dispatched core command"),
};
if let Err(error) = result {
self.editor.set_message(format!("LSP: {error}"));
}
return;
}
let context = self.editor.context();
let result = match self.plugins.get_mut(&invocation.plugin) {
Some(plugin) if plugin.is_async() => {
@@ -178,7 +210,7 @@ impl EditorSession {
}
fn synchronize_documents(&mut self) -> bool {
if !self.plugins.values().any(RubyPlugin::is_async) {
if !self.lsp.is_enabled() && !self.plugins.values().any(RubyPlugin::is_async) {
self.documents.clear();
return false;
}
@@ -250,6 +282,13 @@ impl EditorSession {
});
}
let mut failures = Vec::new();
let mut changed = false;
for event in &events {
if let Err(error) = self.lsp.notify_document(event) {
self.editor.set_message(format!("LSP: {error}"));
changed = true;
}
}
for (owner, plugin) in &mut self.plugins {
if !plugin.is_async() {
continue;
@@ -261,7 +300,7 @@ impl EditorSession {
}
}
}
let changed = !failures.is_empty();
changed |= !failures.is_empty();
for (owner, error) in failures {
self.retire(&owner, &error);
}
@@ -282,6 +321,9 @@ impl EditorSession {
) -> bool {
self.editor.buffer_id() == source.buffer_id
&& self.source_is_current(source)
&& (owner != "core"
|| (self.lsp_source_cursor == Some(self.editor.cursor())
&& self.editor.mode() == Mode::Normal))
&& self
.requests
.get(owner)
@@ -406,6 +448,16 @@ impl EditorSession {
/// Drains ready plugin work without waiting for command or server responses.
pub fn poll(&mut self) -> bool {
let mut changed = self.synchronize_documents();
for update in self.lsp.poll() {
match self.apply_update("core", update) {
Ok(applied) => changed |= applied,
Err(error) => {
self.editor.clear_plugin_diagnostics("core");
self.editor.set_message(format!("LSP: {error}"));
changed = true;
}
}
}
let mut ready = Vec::new();
for (owner, plugin) in &mut self.plugins {
if plugin.is_async() {