on master help sign in
Init lsp through the registry.
955cb81Blaž Hrastnik
helix-lsp/src/client.rs+20−14
@@ -23,13 +23,16 @@ use smol::{
23 Executor,
23 Executor,
24};
24};
25
25
26+fn text_document_identifier(doc: &Document) -> lsp::TextDocumentIdentifier {
27+ lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(doc.path().unwrap()).unwrap())
28+}
29+
26pub struct Client {
30pub struct Client {
27 _process: Child,
31 _process: Child,
28 stderr: BufReader<ChildStderr>,
32 stderr: BufReader<ChildStderr>,
29
33
30 outgoing: Sender<Payload>,
34 outgoing: Sender<Payload>,
31− pub incoming: Receiver<Call>,
35+ // pub incoming: Receiver<Call>,
32−
33 pub request_counter: AtomicU64,
36 pub request_counter: AtomicU64,
34
37
35 capabilities: Option<lsp::ServerCapabilities>,
38 capabilities: Option<lsp::ServerCapabilities>,
@@ -38,7 +41,7 @@ pub struct Client {
38}
41}
39
42
40impl Client {
43impl Client {
41− pub fn start(ex: &Executor, cmd: &str, args: &[String]) -> Self {
44+ pub fn start(ex: &Executor, cmd: &str, args: &[String]) -> (Self, Receiver<Call>) {
42 let mut process = Command::new(cmd)
45 let mut process = Command::new(cmd)
43 .args(args)
46 .args(args)
44 .stdin(Stdio::piped())
47 .stdin(Stdio::piped())
@@ -55,18 +58,22 @@ impl Client {
55
58
56 let (incoming, outgoing) = Transport::start(ex, reader, writer);
59 let (incoming, outgoing) = Transport::start(ex, reader, writer);
57
60
58− Client {
61+ let client = Client {
59 _process: process,
62 _process: process,
60 stderr,
63 stderr,
61
64
62 outgoing,
65 outgoing,
63− incoming,
66+ // incoming,
64−
65 request_counter: AtomicU64::new(0),
67 request_counter: AtomicU64::new(0),
66
68
67 capabilities: None,
69 capabilities: None,
68 // diagnostics: HashMap::new(),
70 // diagnostics: HashMap::new(),
69− }
71+ };
72+
73+ // TODO: async client.initialize()
74+ // maybe use an arc<atomic> flag
75+
76+ (client, incoming)
70 }
77 }
71
78
72 fn next_request_id(&self) -> jsonrpc::Id {
79 fn next_request_id(&self) -> jsonrpc::Id {
@@ -219,7 +226,7 @@ impl Client {
219 // Text document
226 // Text document
220 // -------------------------------------------------------------------------------------------
227 // -------------------------------------------------------------------------------------------
221
228
222− pub async fn text_document_did_open(&mut self, doc: &Document) -> Result<()> {
229+ pub async fn text_document_did_open(&self, doc: &Document) -> Result<()> {
223 self.notify::<lsp::notification::DidOpenTextDocument>(lsp::DidOpenTextDocumentParams {
230 self.notify::<lsp::notification::DidOpenTextDocument>(lsp::DidOpenTextDocumentParams {
224 text_document: lsp::TextDocumentItem {
231 text_document: lsp::TextDocumentItem {
225 uri: lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
232 uri: lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
@@ -295,7 +302,7 @@ impl Client {
295
302
296 // TODO: trigger any time history.commit_revision happens
303 // TODO: trigger any time history.commit_revision happens
297 pub async fn text_document_did_change(
304 pub async fn text_document_did_change(
298− &mut self,
305+ &self,
299 doc: &Document,
306 doc: &Document,
300 transaction: &Transaction,
307 transaction: &Transaction,
301 ) -> Result<()> {
308 ) -> Result<()> {
@@ -328,6 +335,7 @@ impl Client {
328
335
329 self.notify::<lsp::notification::DidChangeTextDocument>(lsp::DidChangeTextDocumentParams {
336 self.notify::<lsp::notification::DidChangeTextDocument>(lsp::DidChangeTextDocumentParams {
330 text_document: lsp::VersionedTextDocumentIdentifier::new(
337 text_document: lsp::VersionedTextDocumentIdentifier::new(
338+ // TODO: doc.into() Url
331 lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
339 lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
332 doc.version,
340 doc.version,
333 ),
341 ),
@@ -338,18 +346,16 @@ impl Client {
338
346
339 // TODO: impl into() TextDocumentIdentifier / VersionedTextDocumentIdentifier for Document.
347 // TODO: impl into() TextDocumentIdentifier / VersionedTextDocumentIdentifier for Document.
340
348
341− pub async fn text_document_did_close(&mut self, doc: &Document) -> Result<()> {
349+ pub async fn text_document_did_close(&self, doc: &Document) -> Result<()> {
342 self.notify::<lsp::notification::DidCloseTextDocument>(lsp::DidCloseTextDocumentParams {
350 self.notify::<lsp::notification::DidCloseTextDocument>(lsp::DidCloseTextDocumentParams {
343− text_document: lsp::TextDocumentIdentifier::new(
351+ text_document: text_document_identifier(doc),
344− lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
345− ),
346 })
352 })
347 .await
353 .await
348 }
354 }
349
355
350 // will_save / will_save_wait_until
356 // will_save / will_save_wait_until
351
357
352− pub async fn text_document_did_save(&mut self) -> anyhow::Result<()> {
358+ pub async fn text_document_did_save(&self) -> anyhow::Result<()> {
353 unimplemented!()
359 unimplemented!()
354 }
360 }
355}
361}
helix-lsp/src/lib.rs+28−5
@@ -1,4 +1,5 @@
1mod client;
1mod client;
2+mod select_all;
2mod transport;
3mod transport;
3
4
4pub use jsonrpc_core as jsonrpc;
5pub use jsonrpc_core as jsonrpc;
@@ -69,16 +70,24 @@ pub use jsonrpc::Call;
69
70
70type LanguageId = String;
71type LanguageId = String;
71
72
72−pub static REGISTRY: Lazy<Registry> = Lazy::new(Registry::init);
73+use crate::select_all::SelectAll;
74+use smol::channel::Receiver;
73
75
74pub struct Registry {
76pub struct Registry {
75 inner: HashMap<LanguageId, OnceCell<Arc<Client>>>,
77 inner: HashMap<LanguageId, OnceCell<Arc<Client>>>,
78+
79+ pub incoming: SelectAll<Receiver<Call>>,
76}
80}
77
81
78impl Registry {
82impl Registry {
79− pub fn init() -> Self {
83+ pub fn new() -> Self {
84+ let mut inner = HashMap::new();
85+
86+ inner.insert("rust".to_string(), OnceCell::new());
87+
80 Self {
88 Self {
81− inner: HashMap::new(),
89+ inner,
90+ incoming: SelectAll::new(),
82 }
91 }
83 }
92 }
84
93
@@ -91,8 +100,12 @@ impl Registry {
91 // TODO: lookup defaults for id (name, args)
100 // TODO: lookup defaults for id (name, args)
92
101
93 // initialize a new client
102 // initialize a new client
94− let client = Client::start(&ex, "rust-analyzer", &[]);
103+ let (mut client, incoming) = Client::start(&ex, "rust-analyzer", &[]);
95− // TODO: also call initialize().await()
104+ // TODO: run this async without blocking
105+ smol::block_on(client.initialize()).unwrap();
106+
107+ self.incoming.push(incoming);
108+
96 Arc::new(client)
109 Arc::new(client)
97 })
110 })
98 })
111 })
@@ -115,3 +128,13 @@ impl Registry {
115// -> PROBLEM: how do you trigger an update on the editor side when data updates?
128// -> PROBLEM: how do you trigger an update on the editor side when data updates?
116//
129//
117// -> The data updates should pull all events until we run out so we don't frequently re-render
130// -> The data updates should pull all events until we run out so we don't frequently re-render
131+//
132+//
133+// v2:
134+//
135+// there should be a registry of lsp clients, one per language type (or workspace).
136+// the clients should lazy init on first access
137+// the client.initialize() should be called async and we buffer any requests until that completes
138+// there needs to be a way to process incoming lsp messages from all clients.
139+// -> notifications need to be dispatched to wherever
140+// -> requests need to generate a reply and travel back to the same lsp!
helix-lsp/src/select_all.rsnew file+143
@@ -0,0 +1,143 @@
1+//! An unbounded set of streams
2+
3+use core::fmt::{self, Debug};
4+use core::iter::FromIterator;
5+use core::pin::Pin;
6+
7+use smol::ready;
8+use smol::stream::Stream;
9+use std::task::{Context, Poll};
10+
11+use futures_util::stream::FusedStream;
12+use futures_util::stream::{FuturesUnordered, StreamExt, StreamFuture};
13+
14+/// An unbounded set of streams
15+///
16+/// This "combinator" provides the ability to maintain a set of streams
17+/// and drive them all to completion.
18+///
19+/// Streams are pushed into this set and their realized values are
20+/// yielded as they become ready. Streams will only be polled when they
21+/// generate notifications. This allows to coordinate a large number of streams.
22+///
23+/// Note that you can create a ready-made `SelectAll` via the
24+/// `select_all` function in the `stream` module, or you can start with an
25+/// empty set with the `SelectAll::new` constructor.
26+#[must_use = "streams do nothing unless polled"]
27+pub struct SelectAll<St> {
28+ inner: FuturesUnordered<StreamFuture<St>>,
29+}
30+
31+impl<St: Debug> Debug for SelectAll<St> {
32+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33+ write!(f, "SelectAll {{ ... }}")
34+ }
35+}
36+
37+impl<St: Stream + Unpin> SelectAll<St> {
38+ /// Constructs a new, empty `SelectAll`
39+ ///
40+ /// The returned `SelectAll` does not contain any streams and, in this
41+ /// state, `SelectAll::poll` will return `Poll::Ready(None)`.
42+ pub fn new() -> Self {
43+ Self {
44+ inner: FuturesUnordered::new(),
45+ }
46+ }
47+
48+ /// Returns the number of streams contained in the set.
49+ ///
50+ /// This represents the total number of in-flight streams.
51+ pub fn len(&self) -> usize {
52+ self.inner.len()
53+ }
54+
55+ /// Returns `true` if the set contains no streams
56+ pub fn is_empty(&self) -> bool {
57+ self.inner.is_empty()
58+ }
59+
60+ /// Push a stream into the set.
61+ ///
62+ /// This function submits the given stream to the set for managing. This
63+ /// function will not call `poll` on the submitted stream. The caller must
64+ /// ensure that `SelectAll::poll` is called in order to receive task
65+ /// notifications.
66+ pub fn push(&self, stream: St) {
67+ self.inner.push(stream.into_future());
68+ }
69+}
70+
71+impl<St: Stream + Unpin> Default for SelectAll<St> {
72+ fn default() -> Self {
73+ Self::new()
74+ }
75+}
76+
77+impl<St: Stream + Unpin> Stream for SelectAll<St> {
78+ type Item = St::Item;
79+
80+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
81+ loop {
82+ match ready!(self.inner.poll_next_unpin(cx)) {
83+ Some((Some(item), remaining)) => {
84+ self.push(remaining);
85+ return Poll::Ready(Some(item));
86+ }
87+ Some((None, _)) => {
88+ // `FuturesUnordered` thinks it isn't terminated
89+ // because it yielded a Some.
90+ // We do not return, but poll `FuturesUnordered`
91+ // in the next loop iteration.
92+ }
93+ None => return Poll::Ready(None),
94+ }
95+ }
96+ }
97+}
98+
99+impl<St: Stream + Unpin> FusedStream for SelectAll<St> {
100+ fn is_terminated(&self) -> bool {
101+ self.inner.is_terminated()
102+ }
103+}
104+
105+/// Convert a list of streams into a `Stream` of results from the streams.
106+///
107+/// This essentially takes a list of streams (e.g. a vector, an iterator, etc.)
108+/// and bundles them together into a single stream.
109+/// The stream will yield items as they become available on the underlying
110+/// streams internally, in the order they become available.
111+///
112+/// Note that the returned set can also be used to dynamically push more
113+/// futures into the set as they become available.
114+///
115+/// This function is only available when the `std` or `alloc` feature of this
116+/// library is activated, and it is activated by default.
117+pub fn select_all<I>(streams: I) -> SelectAll<I::Item>
118+where
119+ I: IntoIterator,
120+ I::Item: Stream + Unpin,
121+{
122+ let mut set = SelectAll::new();
123+
124+ for stream in streams {
125+ set.push(stream);
126+ }
127+
128+ set
129+}
130+
131+impl<St: Stream + Unpin> FromIterator<St> for SelectAll<St> {
132+ fn from_iter<T: IntoIterator<Item = St>>(iter: T) -> Self {
133+ select_all(iter)
134+ }
135+}
136+
137+impl<St: Stream + Unpin> Extend<St> for SelectAll<St> {
138+ fn extend<T: IntoIterator<Item = St>>(&mut self, iter: T) {
139+ for st in iter {
140+ self.push(st)
141+ }
142+ }
143+}
helix-term/src/application.rs+7−6
@@ -10,6 +10,7 @@ use log::{debug, info};
10use std::{
10use std::{
11 io::{self, stdout, Stdout, Write},
11 io::{self, stdout, Stdout, Write},
12 path::PathBuf,
12 path::PathBuf,
13+ sync::Arc,
13 time::Duration,
14 time::Duration,
14};
15};
15
16
@@ -32,7 +33,8 @@ pub struct Application {
32 terminal: Terminal,
33 terminal: Terminal,
33
34
34 executor: &'static smol::Executor<'static>,
35 executor: &'static smol::Executor<'static>,
35− language_server: helix_lsp::Client,
36+ language_server: Arc<helix_lsp::Client>,
37+ language_servers: helix_lsp::Registry,
36}
38}
37
39
38impl Application {
40impl Application {
@@ -49,7 +51,8 @@ impl Application {
49 let mut compositor = Compositor::new();
51 let mut compositor = Compositor::new();
50 compositor.push(Box::new(ui::EditorView::new()));
52 compositor.push(Box::new(ui::EditorView::new()));
51
53
52− let language_server = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
54+ let language_servers = helix_lsp::Registry::new();
55+ let language_server = language_servers.get("rust", &executor).unwrap();
53
56
54 let mut app = Self {
57 let mut app = Self {
55 editor,
58 editor,
@@ -58,6 +61,7 @@ impl Application {
58
61
59 executor,
62 executor,
60 language_server,
63 language_server,
64+ language_servers,
61 };
65 };
62
66
63 Ok(app)
67 Ok(app)
@@ -81,9 +85,6 @@ impl Application {
81 pub async fn event_loop(&mut self) {
85 pub async fn event_loop(&mut self) {
82 let mut reader = EventStream::new();
86 let mut reader = EventStream::new();
83
87
84− // initialize lsp
85− self.language_server.initialize().await.unwrap();
86−
87 self.language_server
88 self.language_server
88 .text_document_did_open(&self.editor.view().unwrap().doc)
89 .text_document_did_open(&self.editor.view().unwrap().doc)
89 .await
90 .await
@@ -101,7 +102,7 @@ impl Application {
101 event = reader.next().fuse() => {
102 event = reader.next().fuse() => {
102 self.handle_terminal_events(event)
103 self.handle_terminal_events(event)
103 }
104 }
104− call = self.language_server.incoming.next().fuse() => {
105+ call = self.language_servers.incoming.next().fuse() => {
105 self.handle_language_server_message(call).await
106 self.handle_language_server_message(call).await
106 }
107 }
107 }
108 }