Skip to content

Commit 5e87c38

Browse files
committed
feat: document store
1 parent 1275fd0 commit 5e87c38

File tree

15 files changed

+632
-80
lines changed

15 files changed

+632
-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
@@ -154,7 +154,9 @@ async-lsp = { version = "0.2", default-features = false, features = [
154154
"client-monitor",
155155
"stdio",
156156
"tokio",
157+
"tracing",
157158
] }
159+
crop = "0.4"
158160
# See <https://github.com/gluon-lang/lsp-types/issues/284>
159161
lsp-types = "0.95.0"
160162
tower = "0.5"
@@ -176,6 +178,7 @@ inturn = "0.1.0"
176178
libc = "0.2"
177179
md-5 = "0.10"
178180
memchr = "2.7"
181+
normalize-path = "0.2.1"
179182
once_map = { version = "0.4.20", default-features = false, features = ["std"] }
180183
paste = "1.0"
181184
petgraph = "0.8"

crates/interface/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ hex.workspace = true
3737
itertools.workspace = true
3838
itoa.workspace = true
3939
inturn.workspace = true
40-
normalize-path = "0.2.1"
40+
normalize-path.workspace = true
4141
rayon.workspace = true
4242
scoped-tls.workspace = true
4343
tracing.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: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
ServerInfo, notification as notif,
7+
};
8+
use solar_config::version::SHORT_VERSION;
9+
use solar_interface::data_structures::sync::RwLock;
10+
11+
use crate::{NotifyResult, vfs::Vfs};
12+
13+
pub(crate) struct GlobalState {
14+
client: ClientSocket,
15+
pub(crate) vfs: Arc<RwLock<Vfs>>,
16+
}
17+
18+
impl GlobalState {
19+
pub(crate) fn new(client: ClientSocket) -> Self {
20+
Self { client, vfs: Arc::new(Default::default()) }
21+
}
22+
23+
pub(crate) fn on_initialize(
24+
&mut self,
25+
_: InitializeParams,
26+
) -> impl Future<Output = Result<InitializeResult, ResponseError>> + use<> {
27+
std::future::ready(Ok(InitializeResult {
28+
server_info: Some(ServerInfo {
29+
name: "solar".into(),
30+
version: Some(SHORT_VERSION.into()),
31+
}),
32+
..Default::default()
33+
}))
34+
}
35+
36+
pub(crate) fn on_initialized(&mut self, _: InitializedParams) -> NotifyResult {
37+
self.info_msg("solar initialized".into());
38+
ControlFlow::Continue(())
39+
}
40+
}
41+
42+
impl GlobalState {
43+
fn warn_msg(&self, message: String) {
44+
let _ = self
45+
.client
46+
.notify::<notif::LogMessage>(LogMessageParams { typ: MessageType::WARNING, message });
47+
}
48+
49+
fn error_msg(&self, message: String) {
50+
let _ = self
51+
.client
52+
.notify::<notif::LogMessage>(LogMessageParams { typ: MessageType::ERROR, message });
53+
}
54+
55+
fn info_msg(&self, message: String) {
56+
let _ = self
57+
.client
58+
.notify::<notif::LogMessage>(LogMessageParams { typ: MessageType::INFO, message });
59+
}
60+
}

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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
// todo: unwrap is not safe
35+
let contents = _guard.get_file_contents(&path).unwrap();
36+
let new_contents = apply_document_changes(contents, params.content_changes);
37+
38+
(contents != &new_contents, new_contents)
39+
};
40+
41+
if changed {
42+
state.vfs.write().set_file_contents(path, Some(new_contents));
43+
}
44+
}
45+
46+
ControlFlow::Continue(())
47+
}
48+
49+
pub(crate) fn did_close_text_document(
50+
state: &mut GlobalState,
51+
params: DidCloseTextDocumentParams,
52+
) -> NotifyResult {
53+
if let Some(path) = proto::vfs_path(&params.text_document.uri) {
54+
if !state.vfs.read().exists(&path) {
55+
error!(?path, "orphan DidCloseTextDocument");
56+
}
57+
58+
state.vfs.write().set_file_contents(path, None);
59+
}
60+
61+
ControlFlow::Continue(())
62+
}

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+
std::ops::Range { start, end }
30+
}

0 commit comments

Comments
 (0)