Implement monaco editor and lsp server with features

This commit is contained in:
silvanshade 2022-06-07 20:14:38 -06:00
parent d34b074c47
commit 24506849a5
No known key found for this signature in database
52 changed files with 12355 additions and 1033 deletions

3
.cargo/config Normal file
View file

@ -0,0 +1,3 @@
[build]
rustflags = "--cfg=web_sys_unstable_apis"
target = "wasm32-unknown-unknown"

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
dist

23
.eslintrc.yaml Normal file
View file

@ -0,0 +1,23 @@
---
parser: "@typescript-eslint/parser"
plugins:
- "@typescript-eslint"
extends:
- eslint:recommended
- plugin:prettier/recommended
- prettier
overrides:
- files: "webpack.config.js"
env:
"node": true
- files: ["*.ts", "*.tsx"]
extends:
- plugin:@typescript-eslint/eslint-recommended
- plugin:@typescript-eslint/recommended-requiring-type-checking
- plugin:@typescript-eslint/recommended
rules:
"@typescript-eslint/no-inferrable-types": ["off"]
parserOptions:
project:
- "./tsconfig.json"
- "./packages/app/tsconfig.json"

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
packages/app/assets/wasm
### Generated by gibo (https://github.com/simonwhitaker/gibo)
### https://raw.github.com/github/gitignore/e5323759e387ba347a9d50f8b0ddd16502eb71d4/Global/Archives.gitignore

11
.prettierrc.yaml Normal file
View file

@ -0,0 +1,11 @@
bracketSpacing: true
printWidth: 120
semi: true
singleQuote: false
tabWidth: 2
trailingComma: all
useTabs: false
overrides:
- files: "*.ts"
options:
parser: typescript

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"livePreview.defaultPreviewPath": "/packages/app/assets/index.html"
}

11
Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[workspace]
members = [
"crates/browser",
"crates/language",
"crates/server",
]
default-members = ["crates/browser"]
[patch.crates-io]
tree-sitter-facade = { path = "../tree-sitter-facade" }
web-tree-sitter-sys = { path = "../web-tree-sitter-sys" }

47
Makefile.toml Normal file
View file

@ -0,0 +1,47 @@
[config]
default_to_workspace = false
skip_core_tasks = true
[tasks.deps]
script = '''
cargo install wasm-bindgen-cli --version 0.2.80
npm install
'''
[tasks.build-server]
script = '''
cargo build
wasm-bindgen --keep-debug --out-dir ./packages/app/assets/wasm --target web --typescript ./target/wasm32-unknown-unknown/debug/demo_lsp_browser.wasm
'''
[tasks.build-app]
script = '''
npm run build --workspace=packages/app
'''
[tasks.build]
dependencies = ["build-server", "build-app"]
[tasks.clean-server]
script = '''
cargo clean
'''
[tasks.clean-app]
script = '''
rm -rf packages/app/dist
rm -rf packages/app/assets/wasm
'''
[tasks.clean]
dependencies = ["clean-server", "clean-app"]
[tasks.format]
script = '''
cargo +nightly fmt --all
'''
[tasks.run]
script = '''
npm run app --workspace=packages/app
'''

View file

@ -1,28 +1,20 @@
<div align="center">
<h1><code>tower-lsp-wasm-example</code></h1>
<h1><code>tower-lsp-web-demo</code></h1>
<p>
<strong>A minimal WASM target example for tower-lsp</strong>
<strong>A minimal browser-hosted WASM demo for tower-lsp</strong>
</p>
</div>
## Building
```sh
cargo install wasm-bindgen-cli --version 0.2.80
cd server
RUSTFLAGS=--cfg=web_sys_unstable_apis cargo build --release --target wasm32-unknown-unknown
wasm-bindgen --out-dir ../app/dist --target web --typescript ./target/wasm32-unknown-unknown/release/server.wasm
cd ..
cd app
npm i
npm run build
cargo install cargo-make
cargo make deps
cargo make build
```
## Running
```sh
cd app
npm run app
cargo make run
```
After the browser window opens, you can try copying and pasting the listed messages into the `stdin` textarea and hitting the `send` button.

View file

@ -1,55 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
.container {
box-sizing: border-box;
display: grid;
grid-template-rows: 2fr 4fr 1fr 2fr 4fr;
align-items: center;
width: 50vh;
height: 50vh;
margin-left: auto;
margin-right: auto;
margin-top: 25vh;
}
.container textarea {
width: 100%;
height: 100%;
resize: none;
}
</style>
<title>tower-lsp-wasm-example</title>
</head>
<body>
<pre>
<!-- NOTE: included for easy copy and pasting into stdin textarea -->
{"jsonrpc": "2.0", "method": "initialize", "params": { "capabilities": {}}, "id": 1}
{"jsonrpc": "2.0", "method": "shutdown", "id": 2}
{"jsonrpc": "2.0", "method": "exit"}
</pre>
<div class="container">
<h1>stdin</h1>
<textarea id="stdin" autocomplete="off" spellcheck="off" wrap="off"></textarea>
<button id="send-button">send</button>
<h1>stdout</h1>
<textarea id="stdout" autocomplete="off" spellcheck="off" wrap="off" readonly></textarea>
</div>
<script type="module">
import init, { serve } from "./dist/server.js"
import { LspStdin, LspStdout } from "./dist/index.js"
const stdin = LspStdin.create(document.getElementById("stdin"), document.getElementById("send-button"));
const stdout = LspStdout.create(document.getElementById("stdout"));
await init();
await serve(stdin, stdout);
</script>
</body>
</html>

697
app/package-lock.json generated
View file

@ -1,697 +0,0 @@
{
"name": "tower-lsp-wasm-app",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "tower-lsp-wasm-app",
"version": "0.0.0",
"license": "Apache-2.0 WITH LLVM-exception",
"dependencies": {
"http-server": "^14.1.0",
"typescript": "^4.7.2"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/async": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dependencies": {
"lodash": "^4.17.14"
}
},
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/corser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"node_modules/follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"bin": {
"he": "bin/he"
}
},
"node_modules/html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
"dependencies": {
"whatwg-encoding": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dependencies": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/http-server": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.0.tgz",
"integrity": "sha512-5lYsIcZtf6pdR8tCtzAHTWrAveo4liUlJdWc7YafwK/maPgYHs+VNP6KpCClmUnSorJrARVMXqtT055zBv11Yg==",
"dependencies": {
"basic-auth": "^2.0.1",
"chalk": "^4.1.2",
"corser": "^2.0.1",
"he": "^1.2.0",
"html-encoding-sniffer": "^3.0.0",
"http-proxy": "^1.18.1",
"mime": "^1.6.0",
"minimist": "^1.2.5",
"opener": "^1.5.1",
"portfinder": "^1.0.28",
"secure-compare": "3.0.1",
"union": "~0.5.0",
"url-join": "^4.0.1"
},
"bin": {
"http-server": "bin/http-server"
},
"engines": {
"node": ">=12"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"bin": {
"opener": "bin/opener-bin.js"
}
},
"node_modules/portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
"integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==",
"dependencies": {
"async": "^2.6.2",
"debug": "^3.1.1",
"mkdirp": "^0.5.5"
},
"engines": {
"node": ">= 0.12.0"
}
},
"node_modules/qs": {
"version": "6.10.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
"integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/secure-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
"integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM="
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/typescript": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/union": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
"dependencies": {
"qs": "^6.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
},
"node_modules/whatwg-encoding": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=12"
}
}
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"async": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"requires": {
"lodash": "^4.17.14"
}
},
"basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"corser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ=="
},
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"requires": {
"ms": "^2.1.1"
}
},
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
"requires": {
"whatwg-encoding": "^2.0.0"
}
},
"http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"requires": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
}
},
"http-server": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.0.tgz",
"integrity": "sha512-5lYsIcZtf6pdR8tCtzAHTWrAveo4liUlJdWc7YafwK/maPgYHs+VNP6KpCClmUnSorJrARVMXqtT055zBv11Yg==",
"requires": {
"basic-auth": "^2.0.1",
"chalk": "^4.1.2",
"corser": "^2.0.1",
"he": "^1.2.0",
"html-encoding-sniffer": "^3.0.0",
"http-proxy": "^1.18.1",
"mime": "^1.6.0",
"minimist": "^1.2.5",
"opener": "^1.5.1",
"portfinder": "^1.0.28",
"secure-compare": "3.0.1",
"union": "~0.5.0",
"url-join": "^4.0.1"
}
},
"iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"requires": {
"minimist": "^1.2.6"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
},
"opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="
},
"portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
"integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==",
"requires": {
"async": "^2.6.2",
"debug": "^3.1.1",
"mkdirp": "^0.5.5"
}
},
"qs": {
"version": "6.10.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
"integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
"requires": {
"side-channel": "^1.0.4"
}
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"secure-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
"integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM="
},
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"requires": {
"has-flag": "^4.0.0"
}
},
"typescript": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A=="
},
"union": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
"requires": {
"qs": "^6.4.0"
}
},
"url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
},
"whatwg-encoding": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
"requires": {
"iconv-lite": "0.6.3"
}
}
}
}

View file

@ -1,15 +0,0 @@
{
"name": "tower-lsp-wasm-app",
"version": "0.0.0",
"main": "index.js",
"author": "silvanshade <silvanshade@users.noreply.github.com>",
"license": "Apache-2.0 WITH LLVM-exception",
"scripts": {
"build": "tsc",
"app": "http-server -o"
},
"dependencies": {
"http-server": "^14.1.0",
"typescript": "^4.7.2"
}
}

View file

@ -1,68 +0,0 @@
class LspStdin {
static async *create(
stdin: HTMLTextAreaElement,
sendButton: HTMLButtonElement
): AsyncGenerator<Uint8Array, never, void> {
const encoder = new TextEncoder();
while (true) {
const bytes = await new Promise<Uint8Array>((resolve) => {
sendButton.addEventListener(
"click",
() => {
const payload = stdin.value;
const message = `Content-Length: ${payload.length}\r\n\r\n${payload}`;
stdin.value = "";
resolve(encoder.encode(message));
},
{ once: true }
);
});
yield bytes;
}
}
}
// NOTE: unused ReadableByteStream based implementation. See comments in server/src/lib.rs.
//
// class LspStdin {
// static create(stdin: HTMLTextAreaElement, sendButton: HTMLButtonElement): ReadableStream {
// const encoder = new TextEncoder();
// return new ReadableStream({
// type: "bytes" as any,
// async start(controller) {
// while (true) {
// await new Promise<void>((resolve) => {
// sendButton.addEventListener(
// "click",
// () => {
// const payload = stdin.value;
// const message = `Content-Length: ${payload.length}\r\n\r\n${payload}`;
// const bytes = encoder.encode(message);
// controller.enqueue(bytes);
// stdin.value = "";
// resolve();
// },
// { once: true }
// );
// });
// }
// },
// });
// }
// }
class LspStdout {
static create(stdout: HTMLTextAreaElement): WritableStream {
const decoder = new TextDecoder();
return new WritableStream({
async write(bytes) {
const message = decoder.decode(bytes);
const payload = message.replace(/^Content-Length:\s*\d+\s*/, "");
stdout.value += payload;
stdout.value += "\n";
},
});
}
}
export { LspStdin, LspStdout };

View file

@ -1,105 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
"incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"lib": ["DOM", "ES2022"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "ES2022", /* Specify what module code is generated. */
"rootDir": "src", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
"newLine": "lf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
"isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"exclude": ["dist"],
"include": ["src/**/*.ts"],
}

33
crates/browser/Cargo.toml Normal file
View file

@ -0,0 +1,33 @@
[package]
publish = false
edition = "2021"
name = "demo-lsp-browser"
version = "0.0.0"
[features]
default = ["tower-lsp/runtime-agnostic"]
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
demo-lsp-language = { version = "0.0", path = "../language" }
demo-lsp-server = { version = "0.0", path = "../server", default-features = false }
futures = "0.3.21"
js-sys = "0.3.57"
tower-lsp = { version = "0.17.0", default-features = false }
tree-sitter = { version = "*", package = "tree-sitter-facade" }
wasm-bindgen = "0.2.80"
wasm-bindgen-futures = { version = "0.4.30", features = ["futures-core-03-stream"] }
wasm-streams = "0.2.3"
web-tree-sitter-sys = "*"
[dependencies.web-sys]
version = "0.3.57"
features = [
"console",
"HtmlTextAreaElement",
"ReadableStream",
"WritableStream",
]

66
crates/browser/src/lib.rs Normal file
View file

@ -0,0 +1,66 @@
#![deny(clippy::all)]
#![deny(unsafe_code)]
use futures::stream::TryStreamExt;
use tower_lsp::{LspService, Server};
use wasm_bindgen::{prelude::*, JsCast};
use wasm_bindgen_futures::{stream::JsStream, JsFuture};
#[wasm_bindgen]
pub struct ServerConfig {
into_server: js_sys::AsyncIterator,
from_server: web_sys::WritableStream,
}
#[wasm_bindgen]
impl ServerConfig {
#[wasm_bindgen(constructor)]
pub fn new(into_server: js_sys::AsyncIterator, from_server: web_sys::WritableStream) -> Self {
Self {
into_server,
from_server,
}
}
}
// NOTE: we don't use web_sys::ReadableStream for input here because on the
// browser side we need to use a ReadableByteStreamController to construct it
// and so far only Chromium-based browsers support that functionality.
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
#[wasm_bindgen]
pub async fn serve(config: ServerConfig) -> Result<(), JsValue> {
console_error_panic_hook::set_once();
web_sys::console::log_1(&"server::serve".into());
let ServerConfig {
into_server,
from_server,
} = config;
let language = demo_lsp_language::language::javascript().await.unwrap();
JsFuture::from(web_tree_sitter_sys::Parser::init())
.await
.expect("failed to initialize tree-sitter");
let input = JsStream::from(into_server);
let input = input
.map_ok(|value| {
value
.dyn_into::<js_sys::Uint8Array>()
.expect("could not cast stream item to Uint8Array")
.to_vec()
})
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
.into_async_read();
let output = JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
let output = wasm_streams::WritableStream::from_raw(output);
let output = output.try_into_async_write().map_err(|err| err.0)?;
let (service, messages) = LspService::new(|client| demo_lsp_server::Server::new(client, language));
Server::new(input, output, messages).serve(service).await;
Ok(())
}

View file

@ -0,0 +1,16 @@
[package]
publish = false
edition = "2021"
name = "demo-lsp-language"
version = "0.0.0"
[dependencies]
anyhow = "1.0"
futures = "0.3"
thiserror = "1.0"
js-sys = "0.3.57"
tree-sitter = { version = "*", package = "tree-sitter-facade" }
wasm-bindgen = { version = "=0.2.80", features = ["strict-macro"] }
wasm-bindgen-futures = "0.4"
web-sys = "0.3.57"
web-tree-sitter-sys = "*"

View file

@ -0,0 +1,16 @@
use anyhow::anyhow;
pub async fn javascript() -> anyhow::Result<tree_sitter::Language> {
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
let bytes: &[u8] = include_bytes!("../../../node_modules/tree-sitter-javascript/tree-sitter-javascript.wasm");
let promise = web_tree_sitter_sys::Language::load_bytes(&bytes.into());
let future = JsFuture::from(promise);
let value = future
.await
.map_err(|_| anyhow!("failed to load tree-sitter-javascript.wasm"))?;
let inner = value.unchecked_into::<web_tree_sitter_sys::Language>();
let result = inner.into();
Ok(result)
}
pub static ID: &'static str = "javascript";

View file

@ -0,0 +1,5 @@
#![deny(clippy::all)]
#![deny(unsafe_code)]
pub mod language;
pub mod parser;

View file

@ -0,0 +1,5 @@
pub fn javascript(language: &tree_sitter::Language) -> anyhow::Result<tree_sitter::Parser> {
let mut parser = tree_sitter::Parser::new()?;
parser.set_language(language)?;
Ok(parser)
}

42
crates/server/Cargo.toml Normal file
View file

@ -0,0 +1,42 @@
[package]
publish = false
edition = "2021"
name = "demo-lsp-server"
version = "0.0.0"
[features]
default = ["tower-lsp/runtime-agnostic"]
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1.0.57"
async-lock = "2.5.0"
console_error_panic_hook = "0.1.7"
dashmap = "5.3.4"
demo-lsp-language = { version = "0.0", path = "../language" }
futures = "0.3.21"
js-sys = "0.3.57"
log = "0.4"
lsp = { version = "0.93", package = "lsp-types" }
lsp-text = { version = "0.5", features = ["tree-sitter"] }
ropey = "1.5.0"
serde_json = "1.0"
thiserror = "1.0"
tower-lsp = { version = "0.17.0", default-features = false }
tree-sitter = { package = "tree-sitter-facade", version = "0.4" }
wasm-bindgen = "0.2.80"
wasm-bindgen-futures = { version = "0.4.30", features = ["futures-core-03-stream"] }
wasm-streams = "0.2.3"
[dependencies.web-sys]
version = "0.3.57"
features = [
"console",
"CssStyleDeclaration",
"Document",
"ReadableStream",
"Window",
"WritableStream",
]

11
crates/server/src/core.rs Normal file
View file

@ -0,0 +1,11 @@
pub mod document;
pub mod error;
pub mod session;
pub mod syntax;
pub mod text;
pub use demo_lsp_language::{language, parser};
pub use document::*;
pub use error::*;
pub use session::*;
pub use text::*;

View file

@ -0,0 +1,64 @@
use async_lock::Mutex;
use lsp_text::RopeExt;
use std::sync::Arc;
pub struct Document {
pub content: ropey::Rope,
pub parser: tree_sitter::Parser,
pub tree: tree_sitter::Tree,
}
impl Document {
pub async fn open(
session: Arc<crate::core::Session>,
params: lsp::DidOpenTextDocumentParams,
) -> anyhow::Result<Option<Self>> {
let mut parser = crate::core::parser::javascript(&session.language)?;
let content = ropey::Rope::from(params.text_document.text);
let result = {
let content = content.clone();
let byte_idx = 0;
let callback = content.chunk_walker(byte_idx).callback_adapter_for_tree_sitter();
let old_tree = None;
parser.parse_with(callback, old_tree)?
};
crate::core::syntax::update_channel(result.as_ref());
Ok(result.map(|tree| crate::core::Document { content, parser, tree }))
}
pub async fn change<'changes>(
session: Arc<crate::core::Session>,
uri: &lsp::Url,
content: &ropey::Rope,
) -> anyhow::Result<Option<tree_sitter::Tree>> {
let result = {
let parser = session.get_mut_parser(uri).await?;
let mut parser = parser.lock().await;
let text = content.chunks().collect::<String>();
parser.parse(text, None)?
};
crate::core::syntax::update_channel(result.as_ref());
if let Some(tree) = result {
{
let tree = tree.clone();
*session.get_mut_tree(uri).await?.value_mut() = Mutex::new(tree);
}
Ok(Some(tree))
} else {
Ok(None)
}
}
/// Return the language-id and textual content portion of the [`Document`].
pub fn text(&self) -> crate::core::Text {
crate::core::Text {
content: self.content.clone(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum DocumentState {
Closed,
Opened,
}

View file

@ -0,0 +1,42 @@
use crate::core;
use thiserror::Error;
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Error, PartialEq)]
pub enum Error {
#[error("ClientNotInitialzed")]
ClientNotInitialized,
#[error("core::SessionResourceNotFound: kind={kind:?}, uri={uri:?}")]
SessionResourceNotFound {
kind: core::session::SessionResourceKind,
uri: lsp::Url,
},
}
pub struct IntoJsonRpcError(pub anyhow::Error);
impl From<IntoJsonRpcError> for tower_lsp::jsonrpc::Error {
fn from(error: IntoJsonRpcError) -> Self {
let mut rpc_error = tower_lsp::jsonrpc::Error::internal_error();
rpc_error.data = Some(serde_json::to_value(format!("{}", error.0)).unwrap());
rpc_error
}
}
#[cfg(test)]
mod tests {
use super::{Error, IntoJsonRpcError};
#[test]
fn from() {
let error = Error::ClientNotInitialized;
let error = error.into();
let mut expected = tower_lsp::jsonrpc::Error::internal_error();
expected.data = Some(serde_json::to_value(format!("{}", error)).unwrap());
let actual: tower_lsp::jsonrpc::Error = IntoJsonRpcError(error).into();
assert_eq!(expected, actual);
}
}

View file

@ -0,0 +1,142 @@
use anyhow::anyhow;
use async_lock::{Mutex, RwLock};
use dashmap::{
mapref::one::{Ref, RefMut},
DashMap,
};
use std::sync::Arc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SessionResourceKind {
Document,
Parser,
Tree,
}
pub struct Session {
pub server_capabilities: RwLock<lsp::ServerCapabilities>,
pub client_capabilities: RwLock<Option<lsp::ClientCapabilities>>,
client: Option<tower_lsp::Client>,
pub language: tree_sitter::Language,
pub document_states: DashMap<lsp::Url, crate::core::DocumentState>,
document_texts: DashMap<lsp::Url, crate::core::Text>,
document_parsers: DashMap<lsp::Url, Mutex<tree_sitter::Parser>>,
document_trees: DashMap<lsp::Url, Mutex<tree_sitter::Tree>>,
}
impl Session {
pub fn new(client: Option<tower_lsp::Client>, language: tree_sitter::Language) -> Arc<Self> {
let server_capabilities = RwLock::new(crate::server::capabilities());
let client_capabilities = Default::default();
let document_states = Default::default();
let document_texts = Default::default();
let document_parsers = Default::default();
let document_trees = Default::default();
Arc::new(Session {
server_capabilities,
client_capabilities,
client,
language,
document_states,
document_texts,
document_parsers,
document_trees,
})
}
pub fn client(&self) -> anyhow::Result<&tower_lsp::Client> {
self.client
.as_ref()
.ok_or_else(|| crate::core::Error::ClientNotInitialized.into())
}
pub fn insert_document(&self, uri: lsp::Url, document: crate::core::Document) -> anyhow::Result<()> {
let result = self.document_texts.insert(uri.clone(), document.text());
debug_assert!(result.is_none());
let result = self.document_parsers.insert(uri.clone(), Mutex::new(document.parser));
debug_assert!(result.is_none());
let result = self.document_trees.insert(uri, Mutex::new(document.tree));
debug_assert!(result.is_none());
Ok(())
}
pub fn remove_document(&self, uri: &lsp::Url) -> anyhow::Result<()> {
let result = self.document_texts.remove(uri);
debug_assert!(result.is_some());
let result = self.document_parsers.remove(uri);
debug_assert!(result.is_some());
let result = self.document_trees.remove(uri);
debug_assert!(result.is_some());
Ok(())
}
pub async fn semantic_tokens_legend(&self) -> Option<lsp::SemanticTokensLegend> {
let capabilities = self.server_capabilities.read().await;
if let Some(capabilities) = &capabilities.semantic_tokens_provider {
match capabilities {
lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(options) => Some(options.legend.clone()),
lsp::SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(options) => {
Some(options.semantic_tokens_options.legend.clone())
},
}
} else {
None
}
}
pub async fn get_text(&self, uri: &lsp::Url) -> anyhow::Result<Ref<'_, lsp::Url, crate::core::Text>> {
self.document_texts.get(uri).ok_or_else(|| {
let kind = SessionResourceKind::Document;
let uri = uri.clone();
crate::core::Error::SessionResourceNotFound { kind, uri }.into()
})
}
pub async fn get_mut_text(&self, uri: &lsp::Url) -> anyhow::Result<RefMut<'_, lsp::Url, crate::core::Text>> {
self.document_texts.get_mut(uri).ok_or_else(|| {
let kind = SessionResourceKind::Document;
let uri = uri.clone();
crate::core::Error::SessionResourceNotFound { kind, uri }.into()
})
}
pub async fn get_mut_parser(
&self,
uri: &lsp::Url,
) -> anyhow::Result<RefMut<'_, lsp::Url, Mutex<tree_sitter::Parser>>> {
self.document_parsers.get_mut(uri).ok_or_else(|| {
let kind = SessionResourceKind::Parser;
let uri = uri.clone();
crate::core::Error::SessionResourceNotFound { kind, uri }.into()
})
}
pub async fn get_tree(&self, uri: &lsp::Url) -> anyhow::Result<Ref<'_, lsp::Url, Mutex<tree_sitter::Tree>>> {
self.document_trees.get(uri).ok_or_else(|| {
let kind = SessionResourceKind::Tree;
let uri = uri.clone();
crate::core::Error::SessionResourceNotFound { kind, uri }.into()
})
}
pub async fn get_mut_tree(&self, uri: &lsp::Url) -> anyhow::Result<RefMut<'_, lsp::Url, Mutex<tree_sitter::Tree>>> {
self.document_trees.get_mut(uri).ok_or_else(|| {
let kind = SessionResourceKind::Tree;
let uri = uri.clone();
crate::core::Error::SessionResourceNotFound { kind, uri }.into()
})
}
pub fn get_channel_syntax() -> anyhow::Result<web_sys::HtmlTextAreaElement> {
use wasm_bindgen::JsCast;
let element_id = "channel-syntax";
let channel_syntax = web_sys::window()
.ok_or(anyhow!("failed to get window"))?
.document()
.ok_or(anyhow!("failed to get document"))?
.get_element_by_id(element_id)
.ok_or(anyhow!("failed to get channel-syntax element"))?
.unchecked_into();
Ok(channel_syntax)
}
}

View file

@ -0,0 +1,20 @@
use crate::core::session::Session;
pub(crate) fn update_channel(tree: Option<&tree_sitter::Tree>) {
// assume errors; use red
let mut color = "rgb(255, 87, 51)";
if let Ok(channel_syntax) = Session::get_channel_syntax() {
if let Some(tree) = tree {
let sexp = crate::format_sexp(tree.root_node().to_sexp());
channel_syntax.set_value(&sexp);
if !tree.root_node().has_error() {
// no errors; use green
color = "rgb(218, 247, 166)";
}
}
channel_syntax
.style()
.set_property("background-color", color)
.expect("failed to set style");
}
}

View file

@ -0,0 +1,17 @@
pub struct Text {
pub content: ropey::Rope,
}
impl Text {
pub fn new(text: impl AsRef<str>) -> anyhow::Result<Self> {
let text = text.as_ref();
let content = ropey::Rope::from_str(text);
Ok(Text { content })
}
}
impl From<crate::core::Document> for Text {
fn from(value: crate::core::Document) -> Self {
value.text()
}
}

View file

@ -0,0 +1,41 @@
pub mod text_document {
use std::sync::Arc;
pub async fn did_open(
session: Arc<crate::core::Session>,
params: lsp::DidOpenTextDocumentParams,
) -> anyhow::Result<()> {
let uri = params.text_document.uri.clone();
if let Some(document) = crate::core::Document::open(session.clone(), params).await? {
session.insert_document(uri.clone(), document)?;
} else {
log::warn!("'textDocument/didOpen' failed :: uri: {:#?}", uri);
}
Ok(())
}
pub async fn did_change(
session: Arc<crate::core::Session>,
params: lsp::DidChangeTextDocumentParams,
) -> anyhow::Result<()> {
let uri = &params.text_document.uri;
let mut text = session.get_mut_text(uri).await?;
*text = crate::core::Text::new(params.content_changes[0].text.clone())?;
crate::core::Document::change(session.clone(), uri, &text.content).await?;
Ok(())
}
pub async fn did_close(
session: Arc<crate::core::Session>,
params: lsp::DidCloseTextDocumentParams,
) -> anyhow::Result<()> {
let uri = params.text_document.uri;
session.remove_document(&uri)?;
let diagnostics = Default::default();
let version = Default::default();
session.client()?.publish_diagnostics(uri, diagnostics, version).await;
Ok(())
}
}

66
crates/server/src/lib.rs Normal file
View file

@ -0,0 +1,66 @@
#![deny(clippy::all)]
#![deny(unsafe_code)]
mod core;
pub mod handler;
mod server;
pub use server::*;
pub(crate) fn format_sexp(sexp: impl AsRef<str>) -> String {
format_sexp_indented(sexp, 0)
}
fn format_sexp_indented(sexp: impl AsRef<str>, initial_indent_level: u32) -> String {
use std::fmt::Write;
let sexp = sexp.as_ref();
let mut formatted = String::new();
let mut indent_level = initial_indent_level;
let mut has_field = false;
let mut s_iter = sexp.split(|c| c == ' ' || c == ')');
while let Some(s) = s_iter.next() {
if s.is_empty() {
// ")"
indent_level -= 1;
write!(formatted, ")").unwrap();
} else if s.starts_with('(') {
if has_field {
has_field = false;
} else {
if indent_level > 0 {
writeln!(formatted, "").unwrap();
for _ in 0 .. indent_level {
write!(formatted, " ").unwrap();
}
}
indent_level += 1;
}
// "(node_name"
write!(formatted, "{}", s).unwrap();
let mut c_iter = s.chars();
c_iter.next();
match c_iter.next() {
Some('M') | Some('U') => {
// "(MISSING node_name" or "(UNEXPECTED 'x'"
let s = s_iter.next().unwrap();
write!(formatted, " {}", s).unwrap();
},
Some(_) | None => {},
}
} else if s.ends_with(':') {
// "field:"
writeln!(formatted, "").unwrap();
for _ in 0 .. indent_level {
write!(formatted, " ").unwrap();
}
write!(formatted, "{} ", s).unwrap();
has_field = true;
indent_level += 1;
}
}
formatted
}

126
crates/server/src/server.rs Normal file
View file

@ -0,0 +1,126 @@
use std::sync::Arc;
use tower_lsp::{jsonrpc, lsp_types::*, LanguageServer};
pub fn capabilities() -> lsp::ServerCapabilities {
let document_symbol_provider = Some(lsp::OneOf::Left(true));
let text_document_sync = {
let options = lsp::TextDocumentSyncOptions {
open_close: Some(true),
change: Some(lsp::TextDocumentSyncKind::FULL),
..Default::default()
};
Some(lsp::TextDocumentSyncCapability::Options(options))
};
lsp::ServerCapabilities {
text_document_sync,
document_symbol_provider,
..Default::default()
}
}
pub struct Server {
pub client: tower_lsp::Client,
pub session: Arc<crate::core::Session>,
}
impl Server {
pub fn new(client: tower_lsp::Client, language: tree_sitter::Language) -> Self {
let session = crate::core::Session::new(Some(client.clone()), language);
Server { client, session }
}
}
#[tower_lsp::async_trait]
impl LanguageServer for Server {
async fn initialize(&self, params: InitializeParams) -> jsonrpc::Result<InitializeResult> {
web_sys::console::log_1(&"server::initialize".into());
*self.session.client_capabilities.write().await = Some(params.capabilities);
let capabilities = capabilities();
Ok(InitializeResult {
capabilities,
..InitializeResult::default()
})
}
async fn initialized(&self, _: lsp::InitializedParams) {
web_sys::console::log_1(&"server::initialized".into());
let typ = lsp::MessageType::INFO;
let message = "demo language server initialized!";
self.client.log_message(typ, message).await;
}
async fn shutdown(&self) -> jsonrpc::Result<()> {
web_sys::console::log_1(&"server::shutdown".into());
Ok(())
}
// FIXME: for some reason this doesn't trigger
async fn did_open(&self, params: lsp::DidOpenTextDocumentParams) {
web_sys::console::log_1(&"server::did_open".into());
let typ = lsp::MessageType::INFO;
let message = format!("opened document: {}", params.text_document.uri.as_str());
self.client.log_message(typ, message).await;
let session = self.session.clone();
crate::handler::text_document::did_open(session, params).await.unwrap();
}
async fn did_change(&self, params: lsp::DidChangeTextDocumentParams) {
web_sys::console::log_1(&"server::did_change".into());
let session = self.session.clone();
crate::handler::text_document::did_change(session, params)
.await
.unwrap();
}
async fn document_symbol(
&self,
params: lsp::DocumentSymbolParams,
) -> jsonrpc::Result<Option<lsp::DocumentSymbolResponse>> {
web_sys::console::log_1(&"server::document_symbol".into());
let _params = params;
let _session = self.session.clone();
let uri = lsp::Url::parse("inmemory://model.fs").expect("failed to parse url");
Ok(Some(lsp::DocumentSymbolResponse::Flat(vec![
#[allow(deprecated)]
lsp::SymbolInformation {
name: "foo".into(),
kind: lsp::SymbolKind::FUNCTION,
tags: Default::default(),
deprecated: Default::default(),
location: lsp::Location {
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(1, 1)),
uri: uri.clone(),
},
container_name: Default::default(),
},
#[allow(deprecated)]
lsp::SymbolInformation {
name: "bar".into(),
kind: lsp::SymbolKind::FUNCTION,
tags: Default::default(),
deprecated: Default::default(),
location: lsp::Location {
range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(3, 1)),
uri: uri.clone(),
},
container_name: Default::default(),
},
#[allow(deprecated)]
lsp::SymbolInformation {
name: "baz".into(),
kind: lsp::SymbolKind::FUNCTION,
tags: Default::default(),
deprecated: Default::default(),
location: lsp::Location {
range: lsp::Range::new(lsp::Position::new(4, 0), lsp::Position::new(5, 1)),
uri: uri.clone(),
},
container_name: Default::default(),
},
])))
}
}

10613
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

10
package.json Normal file
View file

@ -0,0 +1,10 @@
{
"private": true,
"workspaces": [
"packages/app"
],
"dependencies": {
"tree-sitter-javascript": "^0.19.0",
"web-tree-sitter": "https://gitpkg.now.sh/silvanshade/tree-sitter/lib/binding_web?web-tree-sitter-sys@v0.20.6"
}
}

View file

@ -0,0 +1 @@
dist

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<div id="container">
<h1 id="title">browser-hosted editor and language server</h1>
<p id="synopsis">
This app demos an editor with language smarts (for JavaScript) by hosting the <a
href="https://microsoft.github.io/monaco-editor/">Monaco</a> editor widget with a simple <a
href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/">LSP</a>
server implemented via <a href="https://github.com/ebkalderon/tower-lsp">tower-lsp</a> and <a
href="https://github.com/tree-sitter/tree-sitter">tree-sitter</a>. Everything is compiled to WASM and run
client-side directly in the browser; there are no separate sever-side processes or web workers used by the app
code. (Monaco itself does use web workers, however).
</p>
<p id="features">
features: ⇧⌘O (macos) or ⇧⌃O (windows) opens the symbol view; the <strong>syntax</strong> area shows the JavaScript syntax tree (green for valid; red for errors) parsed from the text <strong>editor</strong> area
</p>
<div id="cell-editor">
<label for="editor">editor</label>
<div id="editor"></div>
</div>
<div id="cell-syntax">
<label for="channel-syntax">syntax</label>
<textarea id="channel-syntax" autocomplete="off" spellcheck="off" wrap="off" readonly></textarea>
</div>
<div id="cell-console">
<label for="channel-console">console</label>
<textarea id="channel-console" autocomplete="off" spellcheck="off" wrap="off" readonly></textarea>
</div>
<div id="cell-client">
<label for="channel-client">message trace (client ⇒ server)</label>
<textarea id="channel-client" autocomplete="off" spellcheck="off" wrap="off" readonly></textarea>
</div>
<div id="cell-server">
<label for="channel-server">message trace (client ⇐ server)</label>
<textarea id="channel-server" autocomplete="off" spellcheck="off" wrap="off" readonly></textarea>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,106 @@
a {
color: mediumslateblue;
}
a:visited {
color: silver;
}
body {
display: flex;
height: 100vh;
background-color: black;
color: white;
}
div[id=container] {
display: grid;
height: 90%;
width: 75%;
grid-template-rows: auto auto auto repeat(5, minmax(0, 1fr));
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 2em;
margin: auto;
margin-top: 0;
padding: 0;
}
h1[id=title] {
grid-column: 1 / 3;
grid-row: 1;
font-family: monospace;
font-size: 2em;
margin-bottom: 0;
padding: 0;
}
p[id=synopsis] {
color: lightgrey;
grid-column: 1 / 3;
grid-row: 2;
font-family: sans-serif;
font-style: italic;
font-size: 11pt;
line-height: 1.5em;
margin: 0;
padding: 0;
}
p[id=features] {
color: lightgrey;
grid-column: 1 / 3;
grid-row: 3;
font-family: sans-serif;
font-style: italic;
font-size: 11pt;
line-height: 1.5em;
margin: 0;
padding: 0;
}
div[id=cell-editor] {
grid-column: 1 / 2;
grid-row: 4 / 6;
}
[id=cell-editor] div[id=editor] {
border: 1px solid black;
height: 100%;
overflow: auto;
}
div[id=cell-syntax] {
grid-column: 2 / 3;
grid-row: 4 / 6;
}
[id=container] label {
display: block;
font-family: monospace;
font-size: 14pt;
}
div[id=cell-console] {
grid-column: 1 / 3;
grid-row: 6;
}
div[id=cell-client] {
grid-column: 1 / 3;
grid-row: 7;
}
div[id=cell-server] {
grid-column: 1 / 3;
grid-row: 8;
}
div[id=container] textarea {
display: block;
margin: 0;
padding: 0;
box-sizing: borcder-box;
height: 100%;
width: 100%;
resize: none;
}

1
packages/app/declarations.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare module "*.module.css";

58
packages/app/package.json Normal file
View file

@ -0,0 +1,58 @@
{
"private": true,
"name": "monaco-lsp-streams",
"description": "",
"version": "0.0.0",
"license": "Apache-2.0 WITH LLVM-exception",
"author": {
"name": "silvanshade",
"email": "silvanshade@users.noreply.github.com",
"url": "https://github.com/silvanshade"
},
"homepage": "https://github.com/silvanshade/monaco-lsp-streams#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/silvanshade/monaco-lsp-streams.git"
},
"bugs": {
"url": "https://github.com/silvanshade/monaco-lsp-streams/issues"
},
"main": "index.js",
"scripts": {
"build": "webpack",
"format": "prettier --write '**/*.{js,json,ts,tsx,yml,yaml}'",
"lint": "eslint 'src/**/*.{js,ts,tsx}' && prettier --check '**/*.{json,yml,yaml}'",
"app": "webpack serve --open"
},
"devDependencies": {
"@types/debounce": "^1.2.1",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"esbuild-loader": "^2.19.0",
"eslint": "^8.17.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"path-browserify": "^1.0.1",
"prettier": "^2.6.2",
"source-map-loader": "^4.0.0",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.3",
"ts-node": "^10.8.1",
"typescript": "^4.7.3",
"webpack": "^5.73.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.1"
},
"dependencies": {
"debounce": "^1.2.1",
"json-rpc-2.0": "^1.3.0",
"monaco-editor-core": "^0.33.0",
"monaco-languageclient": "^1.0.1",
"vscode-languageserver-protocol": "^3.17.1"
}
}

97
packages/app/src/app.ts Normal file
View file

@ -0,0 +1,97 @@
import debounce from "debounce";
import * as monaco from "monaco-editor-core";
import { MonacoToProtocolConverter } from "monaco-languageclient";
import * as proto from "vscode-languageserver-protocol";
import Client from "./client";
import { FromServer, IntoServer } from "./codec";
import Language from "./language";
import Server from "./server";
class Environment implements monaco.Environment {
getWorkerUrl(moduleId: string, label: string) {
if (label === "editorWorkerService") {
return "./editor.worker.bundle.js";
}
throw new Error(`getWorkerUrl: unexpected ${JSON.stringify({ moduleId, label })}`);
}
}
const monacoToProtocol = new MonacoToProtocolConverter(monaco);
export default class App {
readonly #window: Window & monaco.Window & typeof globalThis = self;
readonly #intoServer: IntoServer = new IntoServer();
readonly #fromServer: FromServer = FromServer.create();
initializeMonaco(): void {
this.#window.MonacoEnvironment = new Environment();
}
createModel(client: Client): monaco.editor.ITextModel {
const language = Language.initialize(client);
const value = `
function foo() {
}
function bar() {
}
function baz() {
}
`.replace(/^\s*\n/gm, "");
const id = language.id;
const uri = monaco.Uri.parse("inmemory://demo.js");
const model = monaco.editor.createModel(value, id, uri);
model.onDidChangeContent(
debounce(() => {
const text = model.getValue();
client.notify(proto.DidChangeTextDocumentNotification.type.method, {
textDocument: {
version: 0,
uri: model.uri.toString(),
},
contentChanges: [
{
range: monacoToProtocol.asRange(model.getFullModelRange()),
text,
},
],
} as proto.DidChangeTextDocumentParams);
}, 400),
);
// eslint-disable-next-line @typescript-eslint/require-await
client.pushAfterInitializeHook(async () => {
client.notify(proto.DidOpenTextDocumentNotification.type.method, {
textDocument: {
uri: model.uri.toString(),
languageId: language.id,
version: 0,
text: model.getValue(),
},
} as proto.DidOpenTextDocumentParams);
});
return model;
}
createEditor(client: Client): void {
const container = document.getElementById("editor")!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
this.initializeMonaco();
const model = this.createModel(client);
monaco.editor.create(container, {
model,
automaticLayout: true,
});
}
async run(): Promise<void> {
const client = new Client(this.#fromServer, this.#intoServer);
const server = await Server.initialize(this.#intoServer, this.#fromServer);
this.createEditor(client);
await Promise.all([server.start(), client.start()]);
}
}

102
packages/app/src/client.ts Normal file
View file

@ -0,0 +1,102 @@
import * as jsrpc from "json-rpc-2.0";
import * as proto from "vscode-languageserver-protocol";
import { Bytes, FromServer, Headers, IntoServer } from "./codec";
const consoleChannel = document.getElementById("channel-console") as HTMLTextAreaElement;
class Codec {
static encode(json: jsrpc.JSONRPCRequest | jsrpc.JSONRPCResponse): Uint8Array {
const message = JSON.stringify(json);
const delimited = Headers.add(message);
return Bytes.encode(delimited);
}
static decode<T>(data: Uint8Array): T {
const delimited = Bytes.decode(data);
const message = Headers.remove(delimited);
return JSON.parse(message) as T;
}
}
export default class Client extends jsrpc.JSONRPCServerAndClient {
afterInitializedHooks: (() => Promise<void>)[] = [];
#fromServer: FromServer;
constructor(fromServer: FromServer, intoServer: IntoServer) {
super(
new jsrpc.JSONRPCServer(),
new jsrpc.JSONRPCClient(async (json: jsrpc.JSONRPCRequest) => {
const encoded = Codec.encode(json);
intoServer.enqueue(encoded);
if (null != json.id) {
const response = await fromServer.responses.get(json.id);
this.client.receive(response as jsrpc.JSONRPCResponse);
}
}),
);
this.#fromServer = fromServer;
}
// eslint-disable-next-line @typescript-eslint/require-await
async start(): Promise<void> {
// process "window/logMessage": client <- server
this.addMethod(proto.LogMessageNotification.type.method, (params) => {
const { type, message } = params as { type: proto.MessageType; message: string };
switch (type) {
case proto.MessageType.Error: {
consoleChannel.value += "[error] ";
break;
}
case proto.MessageType.Warning: {
consoleChannel.value += " [warn] ";
break;
}
case proto.MessageType.Info: {
consoleChannel.value += " [info] ";
break;
}
case proto.MessageType.Log: {
consoleChannel.value += " [log] ";
break;
}
}
consoleChannel.value += message;
consoleChannel.value += "\n";
return;
});
// request "initialize": client <-> server
await (this.request(proto.InitializeRequest.type.method, {
processId: null,
clientInfo: {
name: "demo-language-client",
},
capabilities: {},
rootUri: null,
} as proto.InitializeParams) as Promise<jsrpc.JSONRPCResponse>);
// notify "initialized": client --> server
this.notify(proto.InitializedNotification.type.method, {});
await Promise.allSettled(this.afterInitializedHooks.map((f: () => Promise<void>) => f()));
await Promise.allSettled([this.processNotifications(), this.processRequests()]);
}
// eslint-disable-next-line @typescript-eslint/require-await
async processNotifications(): Promise<void> {
for await (const notification of this.#fromServer.notifications) {
await this.receiveAndSend(notification);
}
}
async processRequests(): Promise<void> {
for await (const request of this.#fromServer.requests) {
await this.receiveAndSend(request);
}
}
pushAfterInitializeHook(...hooks: (() => Promise<void>)[]): void {
this.afterInitializedHooks.push(...hooks);
}
}

136
packages/app/src/codec.ts Normal file
View file

@ -0,0 +1,136 @@
import * as vsrpc from "vscode-jsonrpc";
import Queue from "./queue";
import Tracer from "./tracer";
const encoder = new TextEncoder();
const decoder = new TextDecoder();
export class Bytes {
static encode(input: string): Uint8Array {
return encoder.encode(input);
}
static decode(input: Uint8Array): string {
return decoder.decode(input);
}
}
export class Headers {
static add(message: string): string {
return `Content-Length: ${message.length}\r\n\r\n${message}`;
}
static remove(delimited: string): string {
return delimited.replace(/^Content-Length:\s*\d+\s*/, "");
}
}
export class PromiseMap<K, V extends { toString(): string }> {
#map: Map<K, PromiseMap.type<V>> = new Map();
async get(key: K & { toString(): string }): Promise<V> {
let initialized: PromiseMap.type<V>;
if (!this.#map.has(key)) {
initialized = this.#set(key);
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
initialized = this.#map.get(key)!;
}
return initialized.promise;
}
#set(key: K, value?: V): PromiseMap.type<V> {
if (this.#map.has(key)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.#map.get(key)!;
}
// eslint-disable-next-line prefer-const
let partial: PromiseMap.partial<V> = {};
partial.promise = new Promise<V>((resolve) => {
partial.resolve = resolve;
});
const initialized = partial as PromiseMap.type<V>;
if (null != value) {
initialized.resolve(value);
}
this.#map.set(key, initialized);
return initialized;
}
has(key: K): boolean {
return this.#map.has(key);
}
set(key: K & { toString(): string }, value: V): this {
const initialized = this.#set(key, value);
initialized.resolve(value);
return this;
}
get size(): number {
return this.#map.size;
}
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace PromiseMap {
export type type<V> = { resolve: (item: V) => void; promise: Promise<V> };
export type partial<V> = { resolve?: (item: V) => void; promise?: Promise<V> };
}
// FIXME: tracing effiency
export class IntoServer extends Queue<Uint8Array> implements AsyncGenerator<Uint8Array, never, void> {
enqueue(item: Uint8Array): void {
const delimited = decoder.decode(item);
const message = Headers.remove(delimited);
Tracer.client(message);
super.enqueue(item);
}
}
export interface FromServer extends WritableStream<Uint8Array> {
readonly responses: { get(key: number | string): Promise<vsrpc.ResponseMessage> };
readonly notifications: AsyncGenerator<vsrpc.NotificationMessage, never, void>;
readonly requests: AsyncGenerator<vsrpc.RequestMessage, never, void>;
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace FromServer {
export function create(): FromServer {
return new StreamDemuxer();
}
}
export class StreamDemuxer extends Queue<Uint8Array> {
readonly responses: PromiseMap<number | string, vsrpc.ResponseMessage> = new PromiseMap();
readonly notifications: Queue<vsrpc.NotificationMessage> = new Queue<vsrpc.NotificationMessage>();
readonly requests: Queue<vsrpc.RequestMessage> = new Queue<vsrpc.RequestMessage>();
readonly #start: Promise<void>;
constructor() {
super();
this.#start = this.start();
}
private async start(): Promise<void> {
for await (const bytes of this) {
const delimited = Bytes.decode(bytes);
const message = JSON.parse(Headers.remove(delimited)) as vsrpc.Message;
Tracer.server(message);
if (vsrpc.Message.isResponse(message) && null != message.id) {
this.responses.set(message.id, message);
continue;
}
if (vsrpc.Message.isNotification(message)) {
this.notifications.enqueue(message);
continue;
}
if (vsrpc.Message.isRequest(message)) {
this.requests.enqueue(message);
continue;
}
}
}
}

View file

@ -0,0 +1,6 @@
import "../assets/index.module.css";
import App from "./app";
const app = new App();
app.run().catch(console.error);

View file

@ -0,0 +1,69 @@
// import * as jsrpc from "json-rpc-2.0";
import { MonacoToProtocolConverter, ProtocolToMonacoConverter } from "monaco-languageclient";
import * as monaco from "monaco-editor-core";
import * as proto from "vscode-languageserver-protocol";
import Client from "./client";
const monacoToProtocol = new MonacoToProtocolConverter(monaco);
const protocolToMonaco = new ProtocolToMonacoConverter(monaco);
let language: null | Language;
export default class Language implements monaco.languages.ILanguageExtensionPoint {
readonly id: string;
readonly aliases: string[];
readonly extensions: string[];
readonly mimetypes: string[];
private constructor(client: Client) {
const { id, aliases, extensions, mimetypes } = Language.extensionPoint();
this.id = id;
this.aliases = aliases;
this.extensions = extensions;
this.mimetypes = mimetypes;
this.registerLanguage(client);
}
static extensionPoint(): monaco.languages.ILanguageExtensionPoint & {
aliases: string[];
extensions: string[];
mimetypes: string[];
} {
const id = "javascript";
const aliases = ["JavaScript", "javascript", "js"];
const extensions = [".js", ".es6", ".mjs", ".cjs", ".pac"];
const mimetypes = ["text/javascript"];
return { id, extensions, aliases, mimetypes };
}
private registerLanguage(client: Client): void {
void client;
monaco.languages.register(Language.extensionPoint());
monaco.languages.registerDocumentSymbolProvider(this.id, {
// eslint-disable-next-line
async provideDocumentSymbols(model, token): Promise<monaco.languages.DocumentSymbol[]> {
void token;
const response = await (client.request(proto.DocumentSymbolRequest.type.method, {
textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
} as proto.DocumentSymbolParams) as Promise<proto.SymbolInformation[]>);
const uri = model.uri.toString();
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const result: monaco.languages.DocumentSymbol[] = protocolToMonaco.asSymbolInformations(response, uri);
return result;
},
});
}
static initialize(client: Client): Language {
if (null == language) {
language = new Language(client);
} else {
console.warn("Language already initialized; ignoring");
}
return language;
}
}

103
packages/app/src/queue.ts Normal file
View file

@ -0,0 +1,103 @@
export default class Queue<T> implements WritableStream<T>, AsyncGenerator<T, never, void> {
readonly #promises: Promise<T>[] = [];
readonly #resolvers: ((item: T) => void)[] = [];
readonly #observers: ((item: T) => void)[] = [];
#closed = false;
#locked = false;
readonly #stream: WritableStream<T>;
static #__add<X>(promises: Promise<X>[], resolvers: ((item: X) => void)[]): void {
promises.push(
new Promise((resolve) => {
resolvers.push(resolve);
}),
);
}
static #__enqueue<X>(closed: boolean, promises: Promise<X>[], resolvers: ((item: X) => void)[], item: X): void {
if (!closed) {
if (!resolvers.length) Queue.#__add(promises, resolvers);
const resolve = resolvers.shift()!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
resolve(item);
}
}
constructor() {
const closed = this.#closed;
const promises = this.#promises;
const resolvers = this.#resolvers;
this.#stream = new WritableStream({
write(item: T): void {
Queue.#__enqueue(closed, promises, resolvers, item);
},
});
}
#add(): void {
return Queue.#__add(this.#promises, this.#resolvers);
}
enqueue(item: T): void {
return Queue.#__enqueue(this.#closed, this.#promises, this.#resolvers, item);
}
dequeue(): Promise<T> {
if (!this.#promises.length) this.#add();
const item = this.#promises.shift()!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
return item;
}
isEmpty(): boolean {
return !this.#promises.length;
}
isBlocked(): boolean {
return !!this.#resolvers.length;
}
get length(): number {
return this.#promises.length - this.#resolvers.length;
}
async next(): Promise<IteratorResult<T, never>> {
const done = false;
const value = await this.dequeue();
for (const observer of this.#observers) {
observer(value);
}
return { done, value };
}
return(): Promise<IteratorResult<T, never>> {
return new Promise(() => {
// empty
});
}
throw(err: Error): Promise<IteratorResult<T, never>> {
return new Promise((_resolve, reject) => {
reject(err);
});
}
[Symbol.asyncIterator](): AsyncGenerator<T, never, void> {
return this;
}
get locked(): boolean {
return this.#stream.locked;
}
abort(reason?: Error): Promise<void> {
return this.#stream.abort(reason);
}
close(): Promise<void> {
return this.#stream.close();
}
getWriter(): WritableStreamDefaultWriter<T> {
return this.#stream.getWriter();
}
}

View file

@ -0,0 +1,31 @@
import init, { InitOutput, serve, ServerConfig } from "../assets/wasm/demo_lsp_browser";
import { FromServer, IntoServer } from "./codec";
let server: null | Server;
export default class Server {
readonly initOutput: InitOutput;
readonly #intoServer: IntoServer;
readonly #fromServer: FromServer;
private constructor(initOutput: InitOutput, intoServer: IntoServer, fromServer: FromServer) {
this.initOutput = initOutput;
this.#intoServer = intoServer;
this.#fromServer = fromServer;
}
static async initialize(intoServer: IntoServer, fromServer: FromServer): Promise<Server> {
if (null == server) {
const initOutput = await init();
server = new Server(initOutput, intoServer, fromServer);
} else {
console.warn("Server already initialized; ignoring");
}
return server;
}
async start(): Promise<void> {
const config = new ServerConfig(this.#intoServer, this.#fromServer);
await serve(config);
}
}

View file

@ -0,0 +1,17 @@
import * as proto from "vscode-languageserver-protocol";
const clientChannel = document.getElementById("channel-client") as HTMLTextAreaElement;
const serverChannel = document.getElementById("channel-server") as HTMLTextAreaElement;
export default class Tracer {
static client(message: string): void {
clientChannel.value += message;
clientChannel.value += "\n";
}
static server(input: string | proto.Message): void {
const message: string = typeof input === "string" ? input : JSON.stringify(input);
serverChannel.value += message;
serverChannel.value += "\n";
}
}

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"exclude": []
}

View file

@ -0,0 +1,106 @@
// @ts-check
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const webpack = require("webpack");
/** @type {import("webpack").Configuration & { devServer?: import("webpack-dev-server").Configuration } } */
const config = {
experiments: {
asyncWebAssembly: true,
},
mode: "development",
target: "web",
entry: {
app: "./src/index.ts",
"editor.worker": "monaco-editor-core/esm/vs/editor/editor.worker.js",
},
resolve: {
alias: {
vscode: require.resolve("monaco-languageclient/vscode-compatibility"),
},
extensions: [".ts", ".js", ".json", ".ttf"],
fallback: {
fs: false,
child_process: false,
net: false,
crypto: false,
path: require.resolve("path-browserify"),
},
},
output: {
globalObject: "self",
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.ts?$/,
loader: "esbuild-loader",
options: {
loader: "ts",
target: "es2022",
minify: true,
},
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: "asset/resource",
},
],
},
plugins: [
new webpack.ProgressPlugin(),
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: "../../node_modules/web-tree-sitter/tree-sitter.wasm",
},
],
}),
new HtmlWebpackPlugin({
template: "assets/index.html",
scriptLoading: "module",
title: "tower-lsp web demo",
}),
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
format: {
comments: false,
},
compress: true,
// sourceMap: true,
},
}),
],
},
performance: {
hints: false,
},
devServer: {
static: {
directory: path.join(__dirname, "dist"),
},
compress: true,
port: 9000,
client: {
progress: true,
reconnect: false,
},
},
};
module.exports = config;

View file

@ -8,7 +8,7 @@ comment_width = 100
condense_wildcard_suffixes = true
# control_brace_style = "AlwaysSameLine"
# disable_all_formatting = false
edition = "2018"
edition = "2021"
empty_item_single_line = false
# enum_discrim_align_threshold = 0
error_on_line_overflow = true

View file

@ -1,22 +0,0 @@
[package]
edition = "2021"
name = "server"
version = "0.1.0"
[features]
default = ["tower-lsp/runtime-agnostic"]
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
futures = "0.3.21"
js-sys = "0.3.57"
tower-lsp = { version = "0.17.0", default-features = false }
wasm-bindgen = "0.2.80"
wasm-bindgen-futures = { version = "0.4.30", features = ["futures-core-03-stream"] }
wasm-streams = "0.2.3"
web-sys = { version = "0.3.57", features = [ "console", "ReadableStream", "WritableStream" ] }
[dev-dependencies]

View file

View file

@ -1,56 +0,0 @@
#![cfg(web_sys_unstable_apis)]
use futures::stream::TryStreamExt;
use js_sys::Uint8Array;
use tower_lsp::{jsonrpc, lsp_types::*, LanguageServer, LspService, Server};
use wasm_bindgen::{prelude::*, JsCast};
use wasm_bindgen_futures::stream::JsStream;
struct LspServer {}
#[tower_lsp::async_trait]
impl LanguageServer for LspServer {
async fn initialize(&self, _: InitializeParams) -> jsonrpc::Result<InitializeResult> {
web_sys::console::log_1(&"server::initialize".into());
Ok(InitializeResult {
..InitializeResult::default()
})
}
async fn shutdown(&self) -> jsonrpc::Result<()> {
web_sys::console::log_1(&"server::shutdown".into());
Ok(())
}
}
// NOTE: we don't use web_sys::ReadableStream for input here because on the
// browser side we need to use a ReadableByteStreamController to construct it
// and so far only Chromium-based browsers support that functionality.
// NOTE: input needs to be an AsyncIterator<Uint8Array> specifically
#[wasm_bindgen]
pub async fn serve(input: js_sys::AsyncIterator, output: web_sys::WritableStream) -> Result<(), JsValue> {
console_error_panic_hook::set_once();
web_sys::console::log_1(&"server::serve".into());
let input = JsStream::from(input);
let input = input
.map_ok(|value| {
value
.dyn_into::<Uint8Array>()
.expect("could not cast stream item to Uint8Array")
.to_vec()
})
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
.into_async_read();
let output = JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(output);
let output = wasm_streams::WritableStream::from_raw(output);
let output = output.try_into_async_write().map_err(|err| err.0)?;
let (service, messages) = LspService::new(|_client| LspServer {});
Server::new(input, output, messages).serve(service).await;
Ok(())
}

21
tsconfig.json Normal file
View file

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["DOM", "ES2022"],
"module": "ES2022",
"moduleResolution": "node",
"newLine": "lf",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
// "sourceMap": true,
},
"exclude": ["."],
"ts-node": {
"compilerOptions": {
"module": "CommonJS",
},
},
}