mirror of https://github.com/helix-editor/helix
first pass at lsp extensions
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…
Reference in New Issue