Skip to content

Commit 692f172

Browse files
committed
initial commit
0 parents  commit 692f172

File tree

405 files changed

+66868
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

405 files changed

+66868
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/target
2+
Cargo.lock
3+
*.local*
4+
yarn-error.log
5+
.vscode

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "rhai"]
2+
path = rhai
3+
url = [email protected]:tamasfe/rhai.git

Cargo.toml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[workspace]
2+
members = ["crates/*"]
3+
4+
[profile.release]
5+
lto = true
6+
codegen-units = 1
7+
opt-level = 3
8+
9+
[profile.bench]
10+
lto = true
11+
codegen-units = 1
12+
opt-level = 3

README.md

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Rhai LSP
2+
3+
Rhai LSP Server and IDE support.
4+
5+
## Requirements
6+
7+
- Stable Rust toolchain (e.g. via [rustup](https://rustup.rs/))
8+
- [vsce](https://www.npmjs.com/package/vsce) for VS Code extensions
9+
10+
**Optionally:**
11+
12+
- [Task](https://taskfile.dev) for running predefined tasks.
13+
14+
## Project Structure
15+
16+
### [`crates/rowan`](crates/rowan)
17+
18+
Rhai syntax and a recursive descent parser based on [Rowan](https://github.com/rust-analyzer/rowan).
19+
20+
The high-level syntax ([ungrammar](https://rust-analyzer.github.io/blog/2020/10/24/introducing-ungrammar.html)) definition is found in [crates/rowan/src/ast/rhai.ungram](crates/rowan/src/ast/rhai.ungram). The parser mimics the structure and produces a fitting CST.
21+
22+
### [`crates/lsp`](crates/lsp)
23+
24+
The LSP server implementation backed up by [lsp-async-stub](https://github.com/tamasfe/taplo/tree/master/lsp-async-stub).
25+
26+
It can be compiled to WASM only right now, but native binaries with stdio or TCP communication can be easily implemented.
27+
28+
### [`crates/sourcegen`](crates/sourcegen)
29+
30+
Crate for source generation.
31+
32+
Currently only some node types and helper macros are generated from the ungrammar definition. Later the AST will also be generated from it.
33+
34+
### [`js/lsp`](js/lsp)
35+
36+
A JavaScript wrapper over the LSP so that it can be used in NodeJS (and browser) environments, it greatly improves portability (e.g. the same JS *"binary"* can be used in a VS Code extension or with coc.nvim).
37+
38+
### [`ide/vscode`](ide/vscode)
39+
40+
VS Code extension that uses the LSP.
41+
42+
If all the tools are available from the [Requirements](#requirements), it can be built and installed with `task vscode:dev`.
43+
44+
### [`ide/web`](ide/web)
45+
46+
A web demo page, nice to have in the future, currently has an editor and does nothing.
47+
48+
## Tests
49+
50+
Run all tests with `cargo test`.
51+
52+
[Parser tests](crates/rowan/tests) are based on scripts found in [`testdata`](testdata), and also in the upstream [rhai submodule](rhai/scripts).
53+
54+
## Benchmarks
55+
56+
Run benchmarks with `cargo bench`.
57+
58+
Current parser results:
59+
60+
![bench](images/bench.png)
61+
62+
We can only go up from here.
63+
64+
## Profiling
65+
66+
To profile the parser, run `cargo bench --bench parse -- --profile-time 5`.
67+
68+
The flame graph outputs will can found in `target/criterion/profile` afterwards.

Taskfile.yml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
version: 3
2+
3+
tasks:
4+
lsp:js:build:
5+
desc: Build the LSP
6+
dir: js/lsp
7+
cmds:
8+
- yarn build
9+
10+
vscode:dev:
11+
desc: Build everything and install the extension
12+
cmds:
13+
- task: lsp:js:build
14+
- task: vscode:clean
15+
- task: vscode:package
16+
- task: vscode:install
17+
18+
vscode:clean:
19+
desc: Clean VS Code extension build artifacts and run Yarn
20+
dir: ide/vscode
21+
cmds:
22+
- rm -rf node_modules
23+
- rm -rf dist
24+
- yarn
25+
vscode:package:
26+
desc: Package the VS Code extension
27+
dir: ide/vscode
28+
cmds:
29+
- vsce package
30+
vscode:install:
31+
desc: Install the VS Code extension
32+
dir: ide/vscode
33+
cmds:
34+
- code --install-extension rhai-*.vsix --force

crates/lsp/Cargo.toml

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[package]
2+
name = "rhai-lsp"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[lib]
9+
crate-type = ["cdylib", "rlib"]
10+
11+
[dependencies]
12+
tracing = "0.1.28"
13+
lsp-async-stub = "0.2.0"
14+
rhai-rowan = { path = "../rowan" }
15+
lsp-types = "0.90.0"
16+
once_cell = "1.8.0"
17+
futures = "0.3.17"
18+
19+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
20+
tokio = { version = "1", features = ["rt-multi-thread"] }
21+
clap = { version = "3.0.0-beta.2", optional = true }
22+
ctrlc = { version = "3", optional = true }
23+
notify = { version = "4", optional = true }
24+
25+
[target.'cfg(target_arch = "wasm32")'.dependencies]
26+
wasm-bindgen = { version = "0.2", features = [
27+
"serde-serialize",
28+
] }
29+
wasm-bindgen-futures = { version = "0.4" }
30+
console_error_panic_hook = { version = "0.1" }
31+
js-sys = { version = "0.3" }
32+
tracing-wasm = { version = "0.2.0" }
33+
serde_json = "1.0.68"
34+
35+
[features]
36+
default = []
37+
cli = ["clap", "ctrlc", "notify"]
38+
wasm-export = []

crates/lsp/src/diagnostics.rs

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use lsp_async_stub::{Context, RequestWriter};
2+
use lsp_types::{notification, Diagnostic, DiagnosticSeverity, PublishDiagnosticsParams, Url};
3+
use rhai_rowan::parser::Parse;
4+
use tracing::error;
5+
6+
use crate::{
7+
external::spawn,
8+
mapper::{LspExt, Mapper},
9+
World,
10+
};
11+
12+
pub async fn publish_diagnostics(mut context: Context<World>, uri: Url) {
13+
let w = context.world().lock().unwrap();
14+
15+
let doc = match w.documents.get(&uri) {
16+
Some(d) => d.clone(),
17+
None => {
18+
// Doesn't exist anymore
19+
return;
20+
}
21+
};
22+
23+
let mut diagnostics = Vec::new();
24+
25+
syntax_diagnostics(&mut diagnostics, &doc.parse, &doc.mapper);
26+
drop(w);
27+
28+
// FIXME: why is another `spawn` required here?
29+
// the compiler complains about the future not being Sync otherwise,
30+
// but I don't see why.
31+
spawn(async move {
32+
context
33+
.write_notification::<notification::PublishDiagnostics, _>(Some(
34+
PublishDiagnosticsParams {
35+
uri: uri.clone(),
36+
diagnostics,
37+
version: None,
38+
},
39+
))
40+
.await
41+
.unwrap_or_else(|error| error!(%error));
42+
});
43+
}
44+
45+
pub async fn clear_diagnostics(mut context: Context<World>, uri: Url) {
46+
context
47+
.write_notification::<notification::PublishDiagnostics, _>(Some(PublishDiagnosticsParams {
48+
uri,
49+
diagnostics: Vec::new(),
50+
version: None,
51+
}))
52+
.await
53+
.unwrap_or_else(|error| error!(%error));
54+
}
55+
56+
fn syntax_diagnostics(diagnostics: &mut Vec<Diagnostic>, parse: &Parse, mapper: &Mapper) {
57+
diagnostics.extend(parse.errors.iter().map(|e| {
58+
let range = mapper.range(e.range).unwrap_or_default().into_lsp();
59+
Diagnostic {
60+
range,
61+
severity: Some(DiagnosticSeverity::Error),
62+
code: None,
63+
code_description: None,
64+
source: Some("Rhai".into()),
65+
message: format!("{}", e),
66+
related_information: None,
67+
tags: None,
68+
data: None,
69+
}
70+
}));
71+
}

crates/lsp/src/external/native/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#![allow(dead_code)]
2+
3+
use futures::Future;
4+
5+
pub(crate) fn spawn<F: Future<Output = ()> + Send + 'static>(fut: F) {
6+
tokio::spawn(fut);
7+
}

crates/lsp/src/external/wasm32/mod.rs

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#![allow(dead_code)]
2+
3+
use crate::{create_server, create_world, World};
4+
use futures::{Future, Sink};
5+
use lsp_async_stub::{rpc::Message, Server};
6+
use once_cell::sync::Lazy;
7+
use std::{io, task::Poll};
8+
use tracing::trace;
9+
use wasm_bindgen::prelude::*;
10+
use wasm_bindgen_futures::spawn_local;
11+
12+
static SERVER: Lazy<Server<World>> = Lazy::new(|| create_server());
13+
static WORLD: Lazy<World> = Lazy::new(|| create_world());
14+
15+
#[wasm_bindgen]
16+
extern {
17+
#[wasm_bindgen(js_namespace = __rhai__, js_name = onMessage)]
18+
fn js_send_message(message: JsValue);
19+
}
20+
21+
#[cfg(feature = "wasm-export")]
22+
#[wasm_bindgen]
23+
pub async fn initialize() {
24+
console_error_panic_hook::set_once();
25+
tracing_wasm::set_as_global_default();
26+
}
27+
28+
#[cfg(feature = "wasm-export")]
29+
#[wasm_bindgen]
30+
pub fn message(message: JsValue) {
31+
trace!(?message, "received message");
32+
let msg = message.into_serde().unwrap();
33+
spawn(async move {
34+
SERVER
35+
.handle_message(WORLD.clone(), msg, MessageWriter)
36+
.await
37+
.unwrap();
38+
});
39+
}
40+
41+
pub(crate) fn spawn<F: Future<Output = ()> + 'static>(fut: F) {
42+
spawn_local(fut)
43+
}
44+
45+
#[derive(Clone)]
46+
struct MessageWriter;
47+
48+
impl Sink<Message> for MessageWriter {
49+
type Error = io::Error;
50+
51+
fn poll_ready(
52+
self: std::pin::Pin<&mut Self>,
53+
_cx: &mut std::task::Context<'_>,
54+
) -> Poll<Result<(), Self::Error>> {
55+
Poll::Ready(Ok(()))
56+
}
57+
58+
fn start_send(self: std::pin::Pin<&mut Self>, item: Message) -> Result<(), Self::Error> {
59+
let js_msg = JsValue::from_serde(&item).unwrap();
60+
trace!(message = ?js_msg, "sending message");
61+
js_send_message(js_msg);
62+
Ok(())
63+
}
64+
65+
fn poll_flush(
66+
self: std::pin::Pin<&mut Self>,
67+
_cx: &mut std::task::Context<'_>,
68+
) -> std::task::Poll<Result<(), Self::Error>> {
69+
Poll::Ready(Ok(()))
70+
}
71+
72+
fn poll_close(
73+
self: std::pin::Pin<&mut Self>,
74+
_cx: &mut std::task::Context<'_>,
75+
) -> std::task::Poll<Result<(), Self::Error>> {
76+
Poll::Ready(Ok(()))
77+
}
78+
}

0 commit comments

Comments
 (0)