mirror of https://github.com/helix-editor/helix
set up integration to listen to lsp notifications that aren't handled by helix
parent
1eecadb050
commit
2fe135cf7d
|
@ -512,6 +512,8 @@ pub enum Notification {
|
|||
ShowMessage(lsp::ShowMessageParams),
|
||||
LogMessage(lsp::LogMessageParams),
|
||||
ProgressMessage(lsp::ProgressParams),
|
||||
// Other kind specifically for extensions
|
||||
Other(String, jsonrpc::Params),
|
||||
}
|
||||
|
||||
impl Notification {
|
||||
|
@ -538,9 +540,7 @@ impl Notification {
|
|||
let params: lsp::ProgressParams = params.parse()?;
|
||||
Self::ProgressMessage(params)
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::Unhandled);
|
||||
}
|
||||
_ => Self::Other(method.to_owned(), params),
|
||||
};
|
||||
|
||||
Ok(notification)
|
||||
|
|
|
@ -21,6 +21,7 @@ use tui::backend::Backend;
|
|||
|
||||
use crate::{
|
||||
args::Args,
|
||||
commands::ScriptingEngine,
|
||||
compositor::{Compositor, Event},
|
||||
config::Config,
|
||||
handlers,
|
||||
|
@ -881,6 +882,19 @@ impl Application {
|
|||
// Remove the language server from the registry.
|
||||
self.editor.language_servers.remove_by_id(server_id);
|
||||
}
|
||||
Notification::Other(event_name, params) => {
|
||||
let server_id = server_id;
|
||||
|
||||
let mut cx = crate::compositor::Context {
|
||||
editor: &mut self.editor,
|
||||
scroll: None,
|
||||
jobs: &mut self.jobs,
|
||||
};
|
||||
|
||||
ScriptingEngine::handle_lsp_notification(
|
||||
&mut cx, server_id, event_name, params,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use arc_swap::{ArcSwap, ArcSwapAny};
|
||||
use helix_core::syntax;
|
||||
use helix_lsp::{jsonrpc, LanguageServerId};
|
||||
use helix_view::{document::Mode, input::KeyEvent};
|
||||
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
@ -140,6 +141,23 @@ impl ScriptingEngine {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn handle_lsp_notification(
|
||||
cx: &mut compositor::Context,
|
||||
server_id: LanguageServerId,
|
||||
event_name: String,
|
||||
params: jsonrpc::Params,
|
||||
) {
|
||||
for kind in PLUGIN_PRECEDENCE {
|
||||
if manual_dispatch!(
|
||||
kind,
|
||||
// TODO: Get rid of these clones!
|
||||
handle_lsp_notification(cx, server_id, event_name.clone(), params.clone())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_sources() {
|
||||
for kind in PLUGIN_PRECEDENCE {
|
||||
manual_dispatch!(kind, generate_sources())
|
||||
|
@ -160,6 +178,7 @@ pub trait PluginSystem {
|
|||
/// this is done here. This is run before the context is available.
|
||||
fn initialize(&self) {}
|
||||
|
||||
#[allow(unused)]
|
||||
fn engine_name(&self) -> PluginSystemKind;
|
||||
|
||||
/// Post initialization, once the context is available. This means you should be able to
|
||||
|
@ -207,6 +226,19 @@ pub trait PluginSystem {
|
|||
false
|
||||
}
|
||||
|
||||
/// Call into the scripting engine to handle an unhandled LSP notification, sent from the server
|
||||
/// to the client.
|
||||
#[inline(always)]
|
||||
fn handle_lsp_notification(
|
||||
&self,
|
||||
_cx: &mut compositor::Context,
|
||||
_server_id: LanguageServerId,
|
||||
_event_name: String,
|
||||
_params: jsonrpc::Params,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Given an identifier, extract the documentation from the engine.
|
||||
#[inline(always)]
|
||||
fn get_doc_for_identifier(&self, _ident: &str) -> Option<String> {
|
||||
|
|
|
@ -6,8 +6,8 @@ use helix_core::{
|
|||
extensions::steel_implementations::{rope_module, SteelRopeSlice},
|
||||
find_workspace, graphemes,
|
||||
syntax::config::{
|
||||
default_timeout, AutoPairConfig, IndentationConfiguration, LanguageConfiguration,
|
||||
LanguageServerConfiguration, SoftWrap,
|
||||
default_timeout, AutoPairConfig, LanguageConfiguration, LanguageServerConfiguration,
|
||||
SoftWrap,
|
||||
},
|
||||
syntax::{self},
|
||||
text_annotations::InlineAnnotation,
|
||||
|
@ -33,11 +33,12 @@ use once_cell::sync::{Lazy, OnceCell};
|
|||
use steel::{
|
||||
compiler::modules::steel_home,
|
||||
gc::{unsafe_erased_pointers::CustomReference, ShareableMut},
|
||||
rerrs::ErrorKind,
|
||||
rvals::{as_underlying_type, AsRefMutSteelVal, FromSteelVal, IntoSteelVal, SteelString},
|
||||
steel_vm::{
|
||||
engine::Engine, mutex_lock, mutex_unlock, register_fn::RegisterFn, ThreadStateController,
|
||||
},
|
||||
steelerr, SteelErr, SteelVal,
|
||||
steelerr, RootedSteelVal, SteelErr, SteelVal,
|
||||
};
|
||||
|
||||
use std::{
|
||||
|
@ -194,6 +195,18 @@ pub static BUFFER_EXTENSION_KEYMAP: Lazy<RwLock<BufferExtensionKeyMap>> = Lazy::
|
|||
})
|
||||
});
|
||||
|
||||
pub static LSP_NOTIFICATION_REGISTRY: Lazy<RwLock<HashMap<(String, String), RootedSteelVal>>> =
|
||||
Lazy::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
fn register_lsp_notification_callback(lsp: String, kind: String, function: SteelVal) {
|
||||
let rooted = function.as_rooted();
|
||||
|
||||
LSP_NOTIFICATION_REGISTRY
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert((lsp, kind), rooted);
|
||||
}
|
||||
|
||||
pub struct BufferExtensionKeyMap {
|
||||
map: HashMap<String, EmbeddedKeyMap>,
|
||||
reverse: HashMap<usize, String>,
|
||||
|
@ -676,6 +689,11 @@ fn dynamic_set_option(
|
|||
fn load_configuration_api(engine: &mut Engine, generate_sources: bool) {
|
||||
let mut module = BuiltInModule::new("helix/core/configuration");
|
||||
|
||||
module.register_fn(
|
||||
"register-lsp-notification-handler",
|
||||
register_lsp_notification_callback,
|
||||
);
|
||||
|
||||
module.register_fn("update-configuration!", |ctx: &mut Context| {
|
||||
ctx.editor
|
||||
.config_events
|
||||
|
@ -852,6 +870,28 @@ fn load_configuration_api(engine: &mut Engine, generate_sources: bool) {
|
|||
let mut builtin_configuration_module =
|
||||
r#"(require-builtin helix/core/configuration as helix.)
|
||||
|
||||
(provide register-lsp-notification-handler)
|
||||
|
||||
;;@doc
|
||||
;; Register a callback to be called on LSP notifications sent from the server -> client
|
||||
;; that aren't currently handled by Helix as a built in.
|
||||
;;
|
||||
;; ```scheme
|
||||
;; (register-lsp-notification-handler lsp-name event-name handler)
|
||||
;; ```
|
||||
;;
|
||||
;; * lsp-name : string?
|
||||
;; * event-name : string?
|
||||
;; * function : (-> hash? any?) ;; Function where the first argument is the parameters
|
||||
;;
|
||||
;; # Examples
|
||||
;; ```
|
||||
;; (register-lsp-notification-handler "dart"
|
||||
;; "dart/textDocument/publishClosingLabels"
|
||||
;; (lambda (args) (displayln args)))
|
||||
;; ```
|
||||
(define register-lsp-notification-handler helix.register-lsp-notification-handler)
|
||||
|
||||
(provide set-option!)
|
||||
(define (set-option! key value)
|
||||
(helix.set-option! *helix.config* key value))
|
||||
|
@ -1817,7 +1857,7 @@ impl super::PluginSystem for SteelScriptingEngine {
|
|||
|
||||
fn call_function_by_name(&self, cx: &mut Context, name: &str, args: &[Cow<str>]) -> bool {
|
||||
if enter_engine(|x| x.global_exists(name)) {
|
||||
let args = args
|
||||
let mut args = args
|
||||
.iter()
|
||||
.map(|x| x.clone().into_steelval().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -1831,9 +1871,8 @@ impl super::PluginSystem for SteelScriptingEngine {
|
|||
move |engine, arguments| {
|
||||
let context = arguments[0].clone();
|
||||
engine.update_value("*helix.cx*", context);
|
||||
|
||||
// TODO: Get rid of this clone
|
||||
engine.call_function_by_name_with_args(name, args.clone())
|
||||
engine
|
||||
.call_function_by_name_with_args_from_mut_slice(name, &mut args)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
@ -1947,6 +1986,74 @@ impl super::PluginSystem for SteelScriptingEngine {
|
|||
steel_doc::walk_dir(&mut writer, target, &mut engine).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Should this just be a hook / event instead of a function like this?
|
||||
// Handle an LSP notification, assuming its been sent through
|
||||
fn handle_lsp_notification(
|
||||
&self,
|
||||
cx: &mut compositor::Context,
|
||||
server_id: helix_lsp::LanguageServerId,
|
||||
event_name: String,
|
||||
params: helix_lsp::jsonrpc::Params,
|
||||
) -> bool {
|
||||
if let Err(e) = enter_engine(|guard| {
|
||||
{
|
||||
let mut ctx = Context {
|
||||
register: None,
|
||||
count: None,
|
||||
editor: &mut cx.editor,
|
||||
callback: Vec::new(),
|
||||
on_next_key_callback: None,
|
||||
jobs: &mut cx.jobs,
|
||||
};
|
||||
|
||||
let language_server_name = ctx
|
||||
.editor
|
||||
.language_servers
|
||||
.get_by_id(server_id)
|
||||
.map(|x| x.name().to_owned());
|
||||
|
||||
if language_server_name.is_none() {
|
||||
ctx.editor.set_error("Unable to find language server");
|
||||
}
|
||||
|
||||
let language_server_name = language_server_name.unwrap();
|
||||
|
||||
let function = LSP_NOTIFICATION_REGISTRY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&(language_server_name, event_name))
|
||||
.map(|x| x.value())
|
||||
.cloned();
|
||||
|
||||
if let Some(function) = function {
|
||||
// Install the interrupt handler, in the event this thing
|
||||
// is blocking for too long.
|
||||
with_interrupt_handler(|| {
|
||||
guard
|
||||
.with_mut_reference::<Context, Context>(&mut ctx)
|
||||
.consume(move |engine, arguments| {
|
||||
let context = arguments[0].clone();
|
||||
engine.update_value("*helix.cx*", context);
|
||||
|
||||
let params = serde_json::to_value(¶ms)
|
||||
.map_err(|e| SteelErr::new(ErrorKind::Generic, e.to_string()))
|
||||
.and_then(|x| x.into_steelval())?;
|
||||
|
||||
let args = vec![params];
|
||||
|
||||
engine.call_function_with_args(function.clone(), args)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
Ok(SteelVal::Void)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
cx.editor.set_error(format!("{}", e));
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl SteelScriptingEngine {
|
||||
|
@ -2959,7 +3066,6 @@ fn register_hook(event_kind: String, callback_fn: SteelVal) -> steel::UnRecovera
|
|||
let context = args[0].clone();
|
||||
engine.update_value("*helix.cx*", context);
|
||||
let mut args = [minimized_event.into_steelval().unwrap()];
|
||||
// engine.call_function_by_name_with_args(&function_name, args)
|
||||
engine.call_function_with_args_from_mut_slice(
|
||||
rooted.value().clone(),
|
||||
&mut args,
|
||||
|
|
Loading…
Reference in New Issue