Skip to content

Commit b8bd95a

Browse files
committed
Support document sync for bangls
1 parent 7b3828b commit b8bd95a

File tree

4 files changed

+96
-24
lines changed

4 files changed

+96
-24
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/bangls/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
[package]
22
name = "bangls"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
edition = "2024"
55

66
[features]
7+
default = ["lalrpop"]
78
lalrpop = ["parser/lalrpop"]
89

910
[dependencies]

tools/bangls/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ bangls 是一个 bang 语言的语言服务器, 使用语言服务器协议 (LSP
2424
功能支持
2525
===============================================================================
2626
- [x] 基本补全
27-
- [ ] 无需保存分析
27+
- [x] 无需保存分析
2828
- [ ] 启发式片段补全
2929
- [ ] 定义跳转
3030
- [ ] 引用跳转

tools/bangls/src/main.rs

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
use std::{fs, path::Path};
2-
31
use anyhow::{Result, anyhow, bail};
42
use crossbeam_channel::{Receiver, Sender};
53
use linked_hash_map::LinkedHashMap;
64
use lsp_server::{IoThreads, Message};
7-
use lsp_types::{CompletionItem, CompletionOptions, InitializeParams, ServerCapabilities, request::{self, Request}};
5+
use lsp_types::{CompletionItem, CompletionOptions, InitializeParams, MessageType, Position, ServerCapabilities, ShowMessageParams, TextDocumentSyncCapability, TextDocumentSyncKind, Uri, notification::{self, Notification}, request::{self, Request}};
86
use syntax::{Compile, CompileMeta, EmulateInfo, Expand, LSP_DEBUG};
97

108
fn main() {
119
main_loop().unwrap();
1210
}
1311

12+
fn lopos(pos: Position) -> (u32, u32) {
13+
(pos.line + 1, pos.character + 1)
14+
}
15+
1416
struct IoJoiner(pub Option<IoThreads>);
1517
impl std::ops::DerefMut for IoJoiner {
1618
fn deref_mut(&mut self) -> &mut Self::Target {
@@ -45,6 +47,7 @@ fn main_loop() -> Result<()> {
4547
trigger_characters: Some(vec![]),
4648
..Default::default()
4749
}),
50+
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
4851
..Default::default()
4952
};
5053
let server_capabilities = serde_json::to_value(server_capabilities)?;
@@ -55,33 +58,60 @@ fn main_loop() -> Result<()> {
5558
} = serde_json::from_value::<InitializeParams>(init_params)?;
5659
let _workspace_folders = workspace_folders.ok_or(anyhow!("Cannot find workspace folder"))?;
5760
let mut ctx = Ctx {
61+
open_files: Default::default(),
5862
sender: connect.sender,
5963
recver: connect.receiver,
6064
};
6165
ctx.run()
6266
}
6367

6468
struct Ctx {
69+
open_files: LinkedHashMap<Uri, String>,
6570
sender: Sender<Message>,
6671
recver: Receiver<Message>,
6772
}
6873
impl Ctx {
6974
fn run(&mut self) -> Result<(), anyhow::Error> {
7075
while let Ok(msg) = self.recver.recv() {
7176
match msg {
72-
Message::Request(request) => self.handle_request(request)?,
77+
Message::Request(request) => self.handle_requests(request)?,
7378
Message::Response(response) => {
7479
eprintln!("unknown response {response:?}")
7580
},
7681
Message::Notification(notification) => {
82+
let notification = &mut Some(notification);
83+
self.try_handle_notif::<notification::DidOpenTextDocument>(notification)?;
84+
self.try_handle_notif::<notification::DidChangeTextDocument>(notification)?;
85+
self.try_handle_notif::<notification::DidCloseTextDocument>(notification)?;
7786
eprintln!("unknown notification {notification:?}")
7887
},
7988
}
8089
}
8190
Ok(())
8291
}
8392

84-
fn try_handle<T: RequestHandler>(&mut self, request: &mut Option<lsp_server::Request>) -> Option<Result<()>> {
93+
fn handle_requests(&mut self, request: lsp_server::Request) -> Result<()> {
94+
let request = &mut Some(request);
95+
96+
self.try_handle_req::<request::Completion>(request).transpose()?;
97+
98+
if let Some(request) = request {
99+
bail!("unknown request {request:#?}")
100+
}
101+
Ok(())
102+
}
103+
104+
fn try_handle_notif<T: NotificationHandler>(&mut self, notification: &mut Option<lsp_server::Notification>) -> Result<()> {
105+
let Some(lsp_server::Notification { params, .. })
106+
= notification.take_if(|it| it.method == T::METHOD) else { return Ok(()) };
107+
let params = match serde_json::from_value(params) {
108+
Ok(it) => it,
109+
Err(err) => bail!(err),
110+
};
111+
T::handle(self, params)
112+
}
113+
114+
fn try_handle_req<T: RequestHandler>(&mut self, request: &mut Option<lsp_server::Request>) -> Option<Result<()>> {
85115
let lsp_server::Request { id, params, .. }
86116
= request.take_if(|req| req.method == T::METHOD)?;
87117
let params = match serde_json::from_value(params) {
@@ -109,21 +139,15 @@ impl Ctx {
109139
Some(self.sender.send(Message::Response(response)).map_err(Into::into))
110140
}
111141

112-
fn handle_request(&mut self, request: lsp_server::Request) -> Result<()> {
113-
let request = &mut Some(request);
114-
115-
None.or_else(|| self.try_handle::<request::Completion>(request))
116-
.unwrap_or_else(|| bail!("unknown request {request:#?}"))
117-
}
118-
119-
fn read_file(&self, path: impl AsRef<Path>) -> Result<String> {
120-
let file = fs::read(path)?;
121-
let file = String::from_utf8_lossy(&file);
122-
Ok(file.into_owned())
142+
fn read_file(&self, uri: &Uri) -> Result<&str> {
143+
match self.open_files.get(uri) {
144+
Some(s) => Ok(s),
145+
None => bail!("Cannot read no opened file"),
146+
}
123147
}
124148

125149
fn try_parse(&self, (line, col): (u32, u32), file: &str) -> Result<(Expand, String)> {
126-
let index = line_column::index(&file, line+1, col+1);
150+
let index = line_column::index(&file, line, col);
127151
let placeholders = [
128152
format!("{LSP_DEBUG} "),
129153
format!("{LSP_DEBUG} __lsp_arg;"),
@@ -141,18 +165,31 @@ impl Ctx {
141165
}
142166
Err(anyhow!("Fake parse err: {parse_err}"))
143167
}
168+
169+
fn send_window_notif(&self, typ: MessageType, msg: impl std::fmt::Display) -> Result<()> {
170+
let params = ShowMessageParams {
171+
typ,
172+
message: msg.to_string(),
173+
};
174+
let params = serde_json::to_value(params)?;
175+
let msg = Message::Notification(lsp_server::Notification {
176+
method: notification::ShowMessage::METHOD.to_owned(),
177+
params,
178+
});
179+
self.sender.send(msg)?;
180+
Ok(())
181+
}
144182
}
145183

146184
trait RequestHandler: Request {
147185
fn handle(ctx: &Ctx, param: Self::Params) -> Result<Self::Result>;
148186
}
149187
impl RequestHandler for request::Completion {
150188
fn handle(ctx: &Ctx, param: Self::Params) -> Result<Self::Result> {
151-
let path = param.text_document_position.text_document.uri.path();
152-
let line = param.text_document_position.position.line;
153-
let col = param.text_document_position.position.character;
189+
let uri = param.text_document_position.text_document.uri;
190+
let (line, col) = lopos(param.text_document_position.position);
154191

155-
let file = ctx.read_file(path.as_str())?;
192+
let file = ctx.read_file(&uri)?;
156193
let (top, src) = ctx.try_parse((line, col), &file)?;
157194
let mut meta = CompileMeta::with_source(src.into());
158195
meta.is_emulated = true;
@@ -196,3 +233,37 @@ fn generate_completes(infos: &[EmulateInfo]) -> Vec<CompletionItem> {
196233
}
197234
}).collect()
198235
}
236+
237+
trait NotificationHandler: Notification {
238+
fn handle(ctx: &mut Ctx, param: Self::Params) -> Result<()>;
239+
}
240+
impl NotificationHandler for notification::DidOpenTextDocument {
241+
fn handle(ctx: &mut Ctx, param: Self::Params) -> Result<()> {
242+
ctx.send_window_notif(MessageType::LOG, "open file")?;
243+
let file = ctx.open_files.entry(param.text_document.uri).or_default();
244+
*file = param.text_document.text;
245+
Ok(())
246+
}
247+
}
248+
impl NotificationHandler for notification::DidChangeTextDocument {
249+
fn handle(ctx: &mut Ctx, param: Self::Params) -> Result<()> {
250+
let file = ctx.open_files.entry(param.text_document.uri).or_default();
251+
252+
for change in param.content_changes {
253+
if change.range.is_some() {
254+
bail!("unsupported range change sync: {change:#?}")
255+
}
256+
*file = change.text;
257+
}
258+
Ok(())
259+
}
260+
}
261+
impl NotificationHandler for notification::DidCloseTextDocument {
262+
fn handle(ctx: &mut Ctx, param: Self::Params) -> Result<()> {
263+
let uri = param.text_document.uri;
264+
if ctx.open_files.remove(&uri).is_none() {
265+
ctx.send_window_notif(MessageType::WARNING, format_args!("Cannot close unknown file: {uri:?}"))?;
266+
}
267+
Ok(())
268+
}
269+
}

0 commit comments

Comments
 (0)