Skip to content

Commit b41d8bf

Browse files
committed
fix(backend/lsp): Send text operations to rust-analyzer
1 parent 5c517f1 commit b41d8bf

File tree

5 files changed

+151
-42
lines changed

5 files changed

+151
-42
lines changed

backend/runner/src/lsp.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub struct LspInput;
1919

2020
impl LspInput {
2121
pub fn notify_value<T: Notification>(
22-
params: T::Params,
22+
params: &T::Params,
2323
) -> Result<serde_json::Value, serde_json::Error> {
2424
Ok(serde_json::json!({
2525
"jsonrpc": JsonRpcVersion,
@@ -28,7 +28,7 @@ impl LspInput {
2828
}))
2929
}
3030

31-
pub fn notify<T: Notification>(params: T::Params) -> Result<String, serde_json::Error> {
31+
pub fn notify<T: Notification>(params: &T::Params) -> Result<String, serde_json::Error> {
3232
serde_json::to_string(&Self::notify_value::<T>(params)?)
3333
}
3434

backend/src/project/project.rs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
use std::collections::{HashMap, HashSet};
2+
use std::str::FromStr;
23
use std::sync::Arc;
34

45
use actix::Addr;
56
use futures::StreamExt;
7+
use rsground_runner::lsp::notification::{DidChangeTextDocument, DidOpenTextDocument};
8+
use rsground_runner::lsp::{
9+
DidChangeTextDocumentParams, DidOpenTextDocumentParams, TextDocumentContentChangeEvent,
10+
TextDocumentItem, Uri, VersionedTextDocumentIdentifier,
11+
};
612
use rsground_runner::Runner;
713
use tokio::sync::broadcast;
814
use uuid::Uuid;
915

1016
use crate::auth::jwt::RgUserData;
1117
use crate::collab::{Document, DocumentInfo};
1218
use crate::http_errors::HttpErrors;
19+
use crate::project::project_lsp::ProjectLspActor;
1320
use crate::utils::{ArcStr, AsyncDefault, AsyncInto, ToStream, EMPTY_STR};
1421
use crate::ws::messages::{InternalMessage, ServerMessage};
1522

@@ -29,6 +36,7 @@ pub struct Project {
2936
runner: Arc<Runner>,
3037
producer: Addr<producer::ProjectProducer>,
3138
lsp: Addr<lsp::ProjectLsp>,
39+
lsp_started: bool,
3240
}
3341

3442
impl AsyncDefault for Project {
@@ -58,6 +66,7 @@ impl AsyncDefault for Project {
5866
runner,
5967
producer,
6068
lsp,
69+
lsp_started: false,
6170
}
6271
}
6372
}
@@ -79,8 +88,33 @@ impl Project {
7988
&self.lsp
8089
}
8190

82-
pub async fn start_lsp(&self) {
91+
pub async fn start_lsp(&mut self) {
92+
if self.lsp_started {
93+
return;
94+
}
95+
96+
self.lsp_started = true;
97+
8398
self.lsp.do_send(lsp::Execute);
99+
100+
for (file, doc) in &self.documents {
101+
let version = doc.revision().await as i32;
102+
let text = doc.text().await;
103+
104+
let Ok(uri) = Uri::from_str(&format!("file:///home/{file}")) else {
105+
continue;
106+
};
107+
108+
self.lsp
109+
.send_notify::<DidOpenTextDocument, true>(&DidOpenTextDocumentParams {
110+
text_document: TextDocumentItem {
111+
uri,
112+
language_id: "rust".to_owned(),
113+
version,
114+
text,
115+
},
116+
});
117+
}
84118
}
85119

86120
pub async fn execute(&self) {
@@ -110,13 +144,26 @@ impl Project {
110144

111145
pub async fn add_file(&mut self, path: impl Into<ArcStr>, document: Document) -> Arc<Document> {
112146
let path: ArcStr = path.into();
147+
let content = document.text().await;
113148

114149
_ = self
115150
.get_runner()
116-
.create_file(&path.to_string(), &document.text().await)
151+
.create_file(&path.to_string(), &content)
117152
.await
118153
.inspect_err(|err| project_log::error!("{err}"));
119154

155+
if let Ok(uri) = Uri::from_str(&format!("file:///home/{path}")) {
156+
self.lsp
157+
.send_notify::<DidOpenTextDocument, true>(&DidOpenTextDocumentParams {
158+
text_document: TextDocumentItem {
159+
uri,
160+
language_id: "rust".to_owned(),
161+
version: 0,
162+
text: content,
163+
},
164+
});
165+
}
166+
120167
self.documents
121168
.entry(path)
122169
.insert_entry(document.into())
@@ -125,9 +172,38 @@ impl Project {
125172
}
126173

127174
pub fn rm_file(&mut self, path: impl AsRef<str>) -> Option<Arc<Document>> {
175+
// TODO: (@Brayan-724) Remove real file and notify lsp
128176
self.documents.remove(path.as_ref())
129177
}
130178

179+
pub async fn file_edit(&mut self, path: ArcStr, content: impl AsRef<str>) {
180+
_ = self
181+
.runner
182+
.create_file(&path, content.as_ref())
183+
.await
184+
.inspect_err(|err| log::error!("{err}"));
185+
186+
if let Ok(uri) = Uri::from_str(&format!("file:///home/{path}")) {
187+
let version = if let Some(doc) = self.documents.get(&path) {
188+
doc.revision().await as i32
189+
} else {
190+
0
191+
};
192+
193+
self.lsp
194+
.send_notify::<DidChangeTextDocument, true>(&DidChangeTextDocumentParams {
195+
text_document: VersionedTextDocumentIdentifier { uri, version },
196+
content_changes: vec![TextDocumentContentChangeEvent {
197+
range: None,
198+
range_length: None,
199+
text: content.as_ref().to_owned(),
200+
}],
201+
});
202+
}
203+
204+
_ = self.internal.send(InternalMessage::FileEdit { path });
205+
}
206+
131207
/// Get all file paths
132208
pub async fn get_files(&self) -> HashMap<ArcStr, DocumentInfo> {
133209
(&self.documents)

backend/src/project/project_lsp.rs

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod client;
22
mod server;
33

4+
use std::collections::VecDeque;
45
use std::io::{self, Write};
56
use std::sync::Arc;
67

@@ -17,7 +18,7 @@ use rsground_runner::{PipeWriter, Runner};
1718
use tokio::sync::broadcast;
1819
use uuid::Uuid;
1920

20-
use crate::utils::{define_local_logger, ArcStr};
21+
use crate::utils::{define_local_logger, ArcStr, Truncate};
2122
use crate::ws::messages::InternalMessage;
2223

2324
use super::project_runner::{start_job, AbortSender};
@@ -36,10 +37,12 @@ define_local_logger!(local_log as "backend::lsp" {
3637
const LSP_INITIALIZATION_ID: &str = "rsground::initialize";
3738

3839
pub struct ProjectLsp {
39-
project_id: Uuid,
4040
broadcast: broadcast::Sender<InternalMessage>,
41-
runner: Arc<Runner>,
4241
instance: Option<(AbortSender, PipeWriter)>,
42+
project_id: Uuid,
43+
queue: VecDeque<Stdin>,
44+
ready: bool,
45+
runner: Arc<Runner>,
4346
}
4447

4548
impl ProjectLsp {
@@ -49,10 +52,12 @@ impl ProjectLsp {
4952
runner: Arc<Runner>,
5053
) -> Addr<Self> {
5154
let project_lsp = Self {
52-
project_id,
5355
broadcast,
54-
runner,
5556
instance: None,
57+
project_id,
58+
queue: VecDeque::new(),
59+
ready: false,
60+
runner,
5661
};
5762

5863
project_lsp.start()
@@ -63,6 +68,39 @@ impl Actor for ProjectLsp {
6368
type Context = Context<ProjectLsp>;
6469
}
6570

71+
pub trait ProjectLspActor {
72+
fn send_request<T: Request, const QUEUE: bool>(
73+
&self,
74+
id: impl serde::Serialize,
75+
params: &T::Params,
76+
);
77+
fn send_notify<T: Notification, const QUEUE: bool>(&self, params: &T::Params);
78+
}
79+
80+
impl ProjectLspActor for Addr<ProjectLsp> {
81+
fn send_request<T: Request, const QUEUE: bool>(
82+
&self,
83+
id: impl serde::Serialize,
84+
params: &T::Params,
85+
) {
86+
match LspInput::request::<T>(id, params) {
87+
Ok(req) => self.do_send(Stdin::<QUEUE>(req)),
88+
Err(err) => {
89+
local_log::stdin::error!("Serialize: {err:?}");
90+
}
91+
}
92+
}
93+
94+
fn send_notify<T: Notification, const QUEUE: bool>(&self, params: &T::Params) {
95+
match LspInput::notify::<T>(params) {
96+
Ok(req) => self.do_send(Stdin::<QUEUE>(req)),
97+
Err(err) => {
98+
local_log::stdin::error!("Serialize: {err:?}");
99+
}
100+
}
101+
}
102+
}
103+
66104
// Client-side messages
67105

68106
#[derive(Message)]
@@ -71,7 +109,7 @@ pub struct Execute;
71109

72110
#[derive(Message)]
73111
#[rtype(result = "()")]
74-
pub struct Stdin(String);
112+
pub struct Stdin<const QUEUE: bool = true>(String);
75113

76114
#[derive(Message)]
77115
#[rtype(result = "()")]
@@ -103,19 +141,10 @@ impl Handler<Execute> for ProjectLsp {
103141
move |abort, this, ctx| {
104142
this.instance.replace((abort, stdin));
105143

106-
match LspInput::request::<Initialize>(
144+
ctx.address().send_request::<Initialize, false>(
107145
LSP_INITIALIZATION_ID,
108146
&*client::LSP_INITIALIZATION,
109-
) {
110-
Ok(req) => {
111-
local_log::initialize::info!(target: this.project_id, "Initialize sended");
112-
ctx.notify(Stdin(req))
113-
}
114-
Err(err) => {
115-
local_log::initialize::error!(target: this.project_id, "{err:?}");
116-
ctx.notify(Kill);
117-
}
118-
}
147+
);
119148
},
120149
async move |abort| child.wait_or_abort(abort).await,
121150
|_, this, _| {
@@ -132,21 +161,26 @@ impl Handler<Kill> for ProjectLsp {
132161
type Result = ();
133162

134163
fn handle(&mut self, _: Kill, _: &mut Self::Context) -> Self::Result {
164+
self.ready = false;
135165
if let Some((abort, _)) = self.instance.take() {
136166
_ = abort.send(());
137167
}
138168
}
139169
}
140170

141-
impl Handler<Stdin> for ProjectLsp {
171+
impl<const QUEUE: bool> Handler<Stdin<QUEUE>> for ProjectLsp {
142172
type Result = ();
143173

144-
fn handle(&mut self, Stdin(msg): Stdin, _: &mut Self::Context) -> Self::Result {
145-
if let Some((_, stdin)) = self.instance.as_mut() {
146-
let msg = format!("Content-Length: {}\r\n\r\n{msg}", msg.len());
147-
if let Err(err) = stdin.write_all(msg.as_bytes()).and_then(|_| stdin.flush()) {
148-
local_log::stdin::error!(target: self.project_id, "Cannot write: {err}")
174+
fn handle(&mut self, Stdin(msg): Stdin<QUEUE>, _: &mut Self::Context) -> Self::Result {
175+
if !QUEUE || self.ready {
176+
if let Some((_, stdin)) = self.instance.as_mut() {
177+
let msg = format!("Content-Length: {}\r\n\r\n{msg}", msg.len());
178+
if let Err(err) = stdin.write_all(msg.as_bytes()).and_then(|_| stdin.flush()) {
179+
local_log::stdin::error!(target: self.project_id, "Cannot write: {err}")
180+
}
149181
}
182+
} else {
183+
self.queue.push_back(Stdin(msg));
150184
}
151185
}
152186
}
@@ -229,7 +263,7 @@ impl Handler<ClientStdin> for ProjectLsp {
229263
return;
230264
};
231265

232-
ctx.notify(Stdin(msg));
266+
ctx.notify(Stdin::<true>(msg));
233267
}
234268
}
235269

@@ -248,9 +282,16 @@ impl StreamHandler<Stdout> for ProjectLsp {
248282
LspOutput::Response(res) if res.id() == LSP_INITIALIZATION_ID => match res {
249283
LspResponse::Ok { result, .. } => {
250284
local_log::initialize::info!(target: self.project_id, "Initialize received");
251-
local_log::initialize::trace!(target: self.project_id, "{result:?}");
252-
if let Ok(noti) = LspInput::notify::<Initialized>(InitializedParams {}) {
253-
ctx.notify(Stdin(noti));
285+
local_log::initialize::trace!(target: self.project_id, "{:?}", result.truncate::<256>());
286+
287+
self.ready = true;
288+
289+
if let Ok(noti) = LspInput::notify::<Initialized>(&InitializedParams {}) {
290+
ctx.notify(Stdin::<false>(noti));
291+
}
292+
293+
while let Some(msg) = self.queue.pop_front() {
294+
ctx.notify(msg);
254295
}
255296
}
256297
LspResponse::Err { error, .. } => {
@@ -260,7 +301,7 @@ impl StreamHandler<Stdout> for ProjectLsp {
260301
},
261302

262303
LspOutput::Response(res) => {
263-
local_log::response::trace!(target: self.project_id, "{res:?}");
304+
local_log::response::trace!(target: self.project_id, "{:?}", (&res).truncate::<256>());
264305
let (client_id, res_id) = res
265306
.id()
266307
.clone()

backend/src/project/project_lsp/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ pub const LSP_INITIALIZATION: LazyLock<InitializeResult> = LazyLock::new(|| Init
111111
}),
112112
text_document_sync: Some(TextDocumentSyncCapability::Options(
113113
TextDocumentSyncOptions {
114-
change: Some(TextDocumentSyncKind::INCREMENTAL),
114+
change: Some(TextDocumentSyncKind::FULL),
115115
open_close: Some(true),
116116
save: Some(TextDocumentSyncSaveOptions::SaveOptions(
117117
SaveOptions::default(),

backend/src/ws/handlers.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,6 @@ impl RgWebsocket {
367367

368368
let project = self.app_state.get_project(self.project_id).await?;
369369
let mut project = project.write().await;
370-
let runner = project.get_runner();
371370

372371
let doc = project.get_file(&file).ok_or_else(|| {
373372
local_log::sync::error!("File {file:?} not found in {:?}", self.project_id);
@@ -383,14 +382,7 @@ impl RgWebsocket {
383382
.send(ServerMessage::Error { message: err })
384383
}
385384

386-
_ = runner
387-
.create_file(&file, &doc.text().await)
388-
.await
389-
.inspect_err(|err| log::error!("{err}"));
390-
391-
_ = project
392-
.internal
393-
.send(InternalMessage::FileEdit { path: file });
385+
project.file_edit(file, doc.text().await).await;
394386

395387
Err(ServerMessageError::None)
396388
}

0 commit comments

Comments
 (0)