first pass at lsp extensions

pull/8675/merge^2
Matt Paras 3 months ago
parent 199c9c2cfb
commit 884e9580bc

@ -1549,4 +1549,63 @@ impl Client {
changes, 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("push-component!");
template_function_arity_1("enqueue-thread-local-callback"); 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| { let mut template_function_arity_2 = |name: &str| {
if generate_sources { if generate_sources {
builtin_misc_module.push_str(&format!( builtin_misc_module.push_str(&format!(
@ -2829,3 +2856,95 @@ fn move_window_to_the_right(cx: &mut Context) {
.is_some() .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…
Cancel
Save