mirror of https://github.com/helix-editor/helix
first pass at lsp extensions
parent
199c9c2cfb
commit
884e9580bc
|
@ -1549,4 +1549,63 @@ impl Client {
|
|||
changes,
|
||||
})
|
||||
}
|
||||
|
||||
// Everything below is explicitly extensions used for handling non standard lsp commands
|
||||
pub fn non_standard_extension(
|
||||
&self,
|
||||
method_name: String,
|
||||
params: Option<Value>,
|
||||
) -> Option<impl Future<Output = Result<Value>>> {
|
||||
Some(self.call_non_standard(DynamicLspRequest {
|
||||
method_name,
|
||||
params,
|
||||
}))
|
||||
}
|
||||
|
||||
fn call_non_standard(&self, request: DynamicLspRequest) -> impl Future<Output = Result<Value>> {
|
||||
self.call_non_standard_with_timeout(request, self.req_timeout)
|
||||
}
|
||||
|
||||
fn call_non_standard_with_timeout(
|
||||
&self,
|
||||
request: DynamicLspRequest,
|
||||
timeout_secs: u64,
|
||||
) -> impl Future<Output = Result<Value>> {
|
||||
let server_tx = self.server_tx.clone();
|
||||
let id = self.next_request_id();
|
||||
|
||||
let params = serde_json::to_value(&request.params);
|
||||
async move {
|
||||
use std::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
|
||||
let request = jsonrpc::MethodCall {
|
||||
jsonrpc: Some(jsonrpc::Version::V2),
|
||||
id: id.clone(),
|
||||
method: (&request.method_name).to_string(),
|
||||
params: Self::value_into_params(params?),
|
||||
};
|
||||
|
||||
let (tx, mut rx) = channel::<Result<Value>>(1);
|
||||
|
||||
server_tx
|
||||
.send(Payload::Request {
|
||||
chan: tx,
|
||||
value: request,
|
||||
})
|
||||
.map_err(|e| Error::Other(e.into()))?;
|
||||
|
||||
// TODO: delay other calls until initialize success
|
||||
timeout(Duration::from_secs(timeout_secs), rx.recv())
|
||||
.await
|
||||
.map_err(|_| Error::Timeout(id))? // return Timeout
|
||||
.ok_or(Error::StreamClosed)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Deserialize)]
|
||||
pub struct DynamicLspRequest {
|
||||
method_name: String,
|
||||
params: Option<Value>,
|
||||
}
|
||||
|
|
|
@ -1924,6 +1924,33 @@ fn load_misc_api(engine: &mut Engine, generate_sources: bool) {
|
|||
template_function_arity_1("push-component!");
|
||||
template_function_arity_1("enqueue-thread-local-callback");
|
||||
|
||||
module.register_fn("send-lsp-command", send_arbitrary_lsp_command);
|
||||
if generate_sources {
|
||||
builtin_misc_module.push_str(
|
||||
r#"
|
||||
(provide send-lsp-command)
|
||||
;;@doc
|
||||
;; Send an lsp command. The `lsp-name` must correspond to an active lsp.
|
||||
;; The method name corresponds to the method name that you'd expect to see
|
||||
;; with the lsp, and the params can be passed as a hash table. The callback
|
||||
;; provided will be called with whatever result is returned from the LSP,
|
||||
;; deserialized from json to a steel value.
|
||||
;;
|
||||
;; # Example
|
||||
;; ```scheme
|
||||
;; (define (view-crate-graph)
|
||||
;; (send-lsp-command "rust-analyzer"
|
||||
;; "rust-analyzer/viewCrateGraph"
|
||||
;; (hash "full" #f)
|
||||
;; ;; Callback to run with the result
|
||||
;; (lambda (result) (displayln result))))
|
||||
;; ```
|
||||
(define (send-lsp-command lsp-name method-name params callback)
|
||||
(helix.send-lsp-command *helix.cx* lsp-name method-name params callback))
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
let mut template_function_arity_2 = |name: &str| {
|
||||
if generate_sources {
|
||||
builtin_misc_module.push_str(&format!(
|
||||
|
@ -2829,3 +2856,95 @@ fn move_window_to_the_right(cx: &mut Context) {
|
|||
.is_some()
|
||||
{}
|
||||
}
|
||||
|
||||
// TODO: Get LSP id correctly, and then format a json blob (or something)
|
||||
// that can be serialized by serde
|
||||
fn send_arbitrary_lsp_command(
|
||||
cx: &mut Context,
|
||||
name: SteelString,
|
||||
command: SteelString,
|
||||
// Arguments - these will be converted to some json stuff
|
||||
json_argument: Option<SteelVal>,
|
||||
callback_fn: SteelVal,
|
||||
) -> anyhow::Result<()> {
|
||||
let argument = json_argument.map(|x| serde_json::Value::try_from(x).unwrap());
|
||||
|
||||
let (_view, doc) = current!(cx.editor);
|
||||
|
||||
let language_server_id = anyhow::Context::context(
|
||||
doc.language_servers().find(|x| x.name() == name.as_str()),
|
||||
"Unable to find the language server specified!",
|
||||
)?
|
||||
.id();
|
||||
|
||||
let future = match cx
|
||||
.editor
|
||||
.language_server_by_id(language_server_id)
|
||||
.and_then(|language_server| {
|
||||
language_server.non_standard_extension(command.to_string(), argument)
|
||||
}) {
|
||||
Some(future) => future,
|
||||
None => {
|
||||
// TODO: Come up with a better message once we check the capabilities for
|
||||
// the arbitrary thing you're trying to do, since for now the above actually
|
||||
// always returns a `Some`
|
||||
cx.editor.set_error(
|
||||
"Language server does not support whatever command you just tried to do",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let rooted = callback_fn.as_rooted();
|
||||
|
||||
let callback = async move {
|
||||
// Result of the future - this will be whatever we get back
|
||||
// from the lsp call
|
||||
let res = future.await?;
|
||||
|
||||
let call: Box<dyn FnOnce(&mut Editor, &mut Compositor, &mut job::Jobs)> = Box::new(
|
||||
move |editor: &mut Editor, _compositor: &mut Compositor, jobs: &mut job::Jobs| {
|
||||
let mut ctx = Context {
|
||||
register: None,
|
||||
count: None,
|
||||
editor,
|
||||
callback: Vec::new(),
|
||||
on_next_key_callback: None,
|
||||
jobs,
|
||||
};
|
||||
|
||||
let cloned_func = rooted.value();
|
||||
|
||||
ENGINE.with(move |x| {
|
||||
let mut guard = x.borrow_mut();
|
||||
|
||||
match SteelVal::try_from(res) {
|
||||
Ok(result) => {
|
||||
if let Err(e) = guard
|
||||
.with_mut_reference::<Context, Context>(&mut ctx)
|
||||
.consume(move |engine, args| {
|
||||
let context = args[0].clone();
|
||||
engine.update_value("*helix.cx*", context);
|
||||
|
||||
engine.call_function_with_args(
|
||||
cloned_func.clone(),
|
||||
vec![result.clone()],
|
||||
)
|
||||
})
|
||||
{
|
||||
present_error_inside_engine_context(&mut ctx, &mut guard, e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
present_error_inside_engine_context(&mut ctx, &mut guard, e);
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
Ok(call)
|
||||
};
|
||||
cx.jobs.local_callback(callback);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue