Skip to content

Commit 925a765

Browse files
committed
feat: document store
1 parent 313dd43 commit 925a765

File tree

15 files changed

+655
-80
lines changed

15 files changed

+655
-80
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ async-lsp = { version = "0.2", default-features = false, features = [
155155
"client-monitor",
156156
"stdio",
157157
"tokio",
158+
"tracing",
158159
] }
160+
crop = "0.4"
159161
# See <https://github.com/gluon-lang/lsp-types/issues/284>
160162
lsp-types = "0.95.0"
161163
tower = "0.5"
@@ -177,6 +179,7 @@ inturn = "0.1.0"
177179
libc = "0.2"
178180
md-5 = "0.10"
179181
memchr = "2.7"
182+
normalize-path = "0.2.1"
180183
once_map = { version = "0.4.20", default-features = false, features = ["std"] }
181184
paste = "1.0"
182185
petgraph = "0.8"

crates/interface/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ hex.workspace = true
3636
inturn.workspace = true
3737
itertools.workspace = true
3838
itoa.workspace = true
39-
normalize-path = "0.2.1"
39+
normalize-path.workspace = true
4040
once_map.workspace = true
4141
rayon.workspace = true
4242
scoped-tls.workspace = true

crates/lsp/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ solar-config = { workspace = true, features = ["version"] }
2828
solar-interface.workspace = true
2929

3030
async-lsp.workspace = true
31+
crop = { workspace = true, features = ["utf16-metric"] }
3132
lsp-types.workspace = true
33+
normalize-path.workspace = true
3234
tower.workspace = true
35+
tracing.workspace = true
3336

3437
# This is needed because Windows does not support truly asynchronous pipes.
3538
[target.'cfg(not(unix))'.dependencies]

crates/lsp/src/global_state.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use std::{ops::ControlFlow, sync::Arc};
2+
3+
use async_lsp::{ClientSocket, ResponseError};
4+
use lsp_types::{
5+
InitializeParams, InitializeResult, InitializedParams, LogMessageParams, MessageType,
6+
ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind,
7+
TextDocumentSyncOptions, notification as notif,
8+
};
9+
use solar_config::version::SHORT_VERSION;
10+
use solar_interface::data_structures::sync::RwLock;
11+
12+
use crate::{NotifyResult, vfs::Vfs};
13+
14+
pub(crate) struct GlobalState {
15+
client: ClientSocket,
16+
pub(crate) vfs: Arc<RwLock<Vfs>>,
17+
}
18+
19+
impl GlobalState {
20+
pub(crate) fn new(client: ClientSocket) -> Self {
21+
Self { client, vfs: Arc::new(Default::default()) }
22+
}
23+
24+
pub(crate) fn on_initialize(
25+
&mut self,
26+
_: InitializeParams,
27+
) -> impl Future<Output = Result<InitializeResult, ResponseError>> + use<> {
28+
std::future::ready(Ok(InitializeResult {
29+
capabilities: ServerCapabilities {
30+
text_document_sync: Some(TextDocumentSyncCapability::Options(
31+
TextDocumentSyncOptions {
32+
open_close: Some(true),
33+
change: Some(TextDocumentSyncKind::INCREMENTAL),
34+
will_save: None,
35+
will_save_wait_until: None,
36+
..Default::default()
37+
},
38+
)),
39+
..Default::default()
40+
},
41+
server_info: Some(ServerInfo {
42+
name: "solar".into(),
43+
version: Some(SHORT_VERSION.into()),
44+
}),
45+
}))
46+
}
47+
48+
pub(crate) fn on_initialized(&mut self, _: InitializedParams) -> NotifyResult {
49+
self.info_msg("solar initialized");
50+
ControlFlow::Continue(())
51+
}
52+
}
53+
54+
impl GlobalState {
55+
#[expect(unused)]
56+
fn warn_msg(&self, message: impl Into<String>) {
57+
let _ = self.client.notify::<notif::LogMessage>(LogMessageParams {
58+
typ: MessageType::WARNING,
59+
message: message.into(),
60+
});
61+
}
62+
63+
#[expect(unused)]
64+
fn error_msg(&self, message: impl Into<String>) {
65+
let _ = self.client.notify::<notif::LogMessage>(LogMessageParams {
66+
typ: MessageType::ERROR,
67+
message: message.into(),
68+
});
69+
}
70+
71+
fn info_msg(&self, message: impl Into<String>) {
72+
let _ = self.client.notify::<notif::LogMessage>(LogMessageParams {
73+
typ: MessageType::INFO,
74+
message: message.into(),
75+
});
76+
}
77+
}

crates/lsp/src/handlers/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod notifs;
2+
mod reqs;
3+
4+
pub(crate) use notifs::*;
5+
pub(crate) use reqs::*;

crates/lsp/src/handlers/notifs.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use std::ops::ControlFlow;
2+
3+
use crop::Rope;
4+
use lsp_types::{
5+
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
6+
};
7+
use tracing::error;
8+
9+
use crate::{NotifyResult, global_state::GlobalState, proto, utils::apply_document_changes};
10+
11+
pub(crate) fn did_open_text_document(
12+
state: &mut GlobalState,
13+
params: DidOpenTextDocumentParams,
14+
) -> NotifyResult {
15+
if let Some(path) = proto::vfs_path(&params.text_document.uri) {
16+
let already_exists = state.vfs.read().exists(&path);
17+
if already_exists {
18+
error!(?path, "duplicate DidOpenTextDocument");
19+
}
20+
21+
state.vfs.write().set_file_contents(path, Some(Rope::from(params.text_document.text)));
22+
}
23+
24+
ControlFlow::Continue(())
25+
}
26+
27+
pub(crate) fn did_change_text_document(
28+
state: &mut GlobalState,
29+
params: DidChangeTextDocumentParams,
30+
) -> NotifyResult {
31+
if let Some(path) = proto::vfs_path(&params.text_document.uri) {
32+
let (changed, new_contents) = {
33+
let _guard = state.vfs.read();
34+
let Some(contents) = _guard.get_file_contents(&path) else {
35+
error!(?path, "orphan DidChangeTextDocument");
36+
return ControlFlow::Continue(());
37+
};
38+
let new_contents = apply_document_changes(contents, params.content_changes);
39+
40+
(contents != &new_contents, new_contents)
41+
};
42+
43+
if changed {
44+
state.vfs.write().set_file_contents(path, Some(new_contents));
45+
}
46+
}
47+
48+
ControlFlow::Continue(())
49+
}
50+
51+
pub(crate) fn did_close_text_document(
52+
state: &mut GlobalState,
53+
params: DidCloseTextDocumentParams,
54+
) -> NotifyResult {
55+
if let Some(path) = proto::vfs_path(&params.text_document.uri) {
56+
if !state.vfs.read().exists(&path) {
57+
error!(?path, "orphan DidCloseTextDocument");
58+
}
59+
60+
state.vfs.write().set_file_contents(path, None);
61+
}
62+
63+
ControlFlow::Continue(())
64+
}

crates/lsp/src/handlers/reqs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

crates/lsp/src/lib.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,44 @@
55
)]
66
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
77

8+
use std::ops::ControlFlow;
9+
810
use async_lsp::{
9-
client_monitor::ClientProcessMonitorLayer, concurrency::ConcurrencyLayer,
10-
server::LifecycleLayer,
11+
ClientSocket, client_monitor::ClientProcessMonitorLayer, concurrency::ConcurrencyLayer,
12+
router::Router, server::LifecycleLayer, tracing::TracingLayer,
1113
};
14+
use lsp_types::{notification as notif, request as req};
1215
use tower::ServiceBuilder;
1316

14-
use crate::server::Server;
17+
use crate::global_state::GlobalState;
18+
19+
mod global_state;
20+
mod handlers;
21+
mod proto;
22+
mod utils;
23+
mod vfs;
24+
25+
pub(crate) type NotifyResult = ControlFlow<async_lsp::Result<()>>;
26+
27+
fn new_router(client: ClientSocket) -> Router<GlobalState> {
28+
let this = GlobalState::new(client);
29+
let mut router = Router::new(this);
1530

16-
mod server;
31+
// Lifecycle
32+
router
33+
.request::<req::Initialize, _>(GlobalState::on_initialize)
34+
.notification::<notif::Initialized>(GlobalState::on_initialized)
35+
.request::<req::Shutdown, _>(|_, _| std::future::ready(Ok(())))
36+
.notification::<notif::Exit>(|_, _| ControlFlow::Break(Ok(())));
37+
38+
// Notifications
39+
router
40+
.notification::<notif::DidOpenTextDocument>(handlers::did_open_text_document)
41+
.notification::<notif::DidCloseTextDocument>(handlers::did_close_text_document)
42+
.notification::<notif::DidChangeTextDocument>(handlers::did_change_text_document);
43+
44+
router
45+
}
1746

1847
/// Start the LSP server over stdin/stdout.
1948
///
@@ -35,11 +64,12 @@ pub async fn run_server_stdio() -> solar_interface::Result<()> {
3564

3665
let (eloop, _) = async_lsp::MainLoop::new_server(|client| {
3766
ServiceBuilder::new()
67+
.layer(TracingLayer::default())
3868
.layer(LifecycleLayer::default())
3969
// TODO: infer concurrency
4070
.layer(ConcurrencyLayer::new(2.try_into().unwrap()))
4171
.layer(ClientProcessMonitorLayer::new(client.clone()))
42-
.service(Server::new_router(client))
72+
.service(new_router(client))
4373
});
4474

4575
eloop.run_buffered(stdin, stdout).await.unwrap();

crates/lsp/src/proto.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use crop::Rope;
2+
3+
use crate::vfs::{self, VfsPath};
4+
5+
pub(crate) fn vfs_path(url: &lsp_types::Url) -> Option<vfs::VfsPath> {
6+
url.to_file_path().map(VfsPath::from).ok()
7+
}
8+
9+
/// Converts an [`lsp_types::Range`] to a [`Range`].
10+
///
11+
/// This assumes the position encoding in LSP is UTF-16, which is mandatory to support in the LSP
12+
/// spec.
13+
///
14+
/// [`Range`]: std::ops::Range
15+
pub(crate) fn text_range(rope: &Rope, range: lsp_types::Range) -> std::ops::Range<usize> {
16+
let start_line = if range.start.line > rope.line_len() as u32 {
17+
0usize
18+
} else {
19+
rope.byte_of_line(range.start.line as usize)
20+
};
21+
let start = rope.byte_of_utf16_code_unit(start_line + range.start.character as usize);
22+
let end_line = if range.end.line > rope.line_len() as u32 {
23+
0usize
24+
} else {
25+
rope.byte_of_line(range.end.line as usize)
26+
};
27+
let end = rope.byte_of_utf16_code_unit(end_line + range.end.character as usize);
28+
29+
start..end
30+
}

0 commit comments

Comments
 (0)