Skip to content

Commit f54798c

Browse files
authored
Merge pull request #328 from melpon/feature/clangd-lsp
clangd LSP を利用可能にする
2 parents 05c7306 + 51673b4 commit f54798c

Some content is hidden

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

42 files changed

+2694
-105
lines changed

.github/workflows/deploy-develop.yml

+8-8
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
python3 run.py deploy wandbox-ubuntu-24.04 kennel --env develop
5151
- uses: actions/setup-node@v2
5252
with:
53-
node-version: '16'
53+
node-version: '22'
5454
- name: Cache node modules
5555
uses: actions/cache@v4
5656
env:
@@ -62,17 +62,17 @@ jobs:
6262
${{ runner.os }}-build-${{ env.cache-name }}-
6363
${{ runner.os }}-build-
6464
${{ runner.os }}-
65-
- name: Build Workers
66-
working-directory: canine
67-
run: |
68-
set -ex
69-
npm install
70-
npm run build
65+
# - name: Build Workers
66+
# working-directory: canine
67+
# run: |
68+
# set -ex
69+
# npm install
70+
# npm run build
7171
# - name: Publish canine
7272
# working-directory: canine
7373
# env:
7474
# CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
7575
# run: |
7676
# set -ex
7777
# npm install -g @cloudflare/wrangler
78-
# npm run deploy:dev
78+
# npm run deploy:develop

.github/workflows/deploy-master.yml

+7-7
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
python3 run.py deploy wandbox-ubuntu-24.04 kennel --env master
5151
- uses: actions/setup-node@v2
5252
with:
53-
node-version: '16'
53+
node-version: '22'
5454
- name: Cache node modules
5555
uses: actions/cache@v4
5656
env:
@@ -62,12 +62,12 @@ jobs:
6262
${{ runner.os }}-build-${{ env.cache-name }}-
6363
${{ runner.os }}-build-
6464
${{ runner.os }}-
65-
- name: Build Workers
66-
working-directory: canine
67-
run: |
68-
set -ex
69-
npm install
70-
npm run build
65+
# - name: Build Workers
66+
# working-directory: canine
67+
# run: |
68+
# set -ex
69+
# npm install
70+
# npm run build
7171
# - name: Publish canine
7272
# working-directory: canine
7373
# env:

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/__pycache__/
12
/_source/
23
/_build/
34
/_install/
@@ -10,5 +11,6 @@
1011
/test/_tmp/
1112
/test/_build/
1213
/test/_crash/
14+
/build-clangd-wasm-build/
1315
*.swp
1416
*.sqlite

canine/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ node_modules
66
.dev.vars
77

88
.wrangler
9+
10+
/public/static/wasm
11+
/public-r2/
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
export class AsyncRunningQueue {
3+
#queue: (() => Promise<void>)[] = [];
4+
#running = false;
5+
6+
push(task: () => Promise<void>) {
7+
this.#queue.push(task);
8+
if (!this.#running) {
9+
this.run();
10+
}
11+
}
12+
13+
async run() {
14+
this.#running = true;
15+
while (this.#queue.length > 0) {
16+
await this.#queue.shift()!();
17+
}
18+
this.#running = false;
19+
}
20+
}

canine/app/clangd/ClangdBlob.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
2+
async function openDB(): Promise<IDBDatabase> {
3+
return new Promise((resolve, reject) => {
4+
const request = indexedDB.open("cache", 4);
5+
6+
request.onupgradeneeded = () => {
7+
const db = request.result;
8+
if (db.objectStoreNames.contains("cache")) {
9+
db.deleteObjectStore("cache");
10+
}
11+
db.createObjectStore("cache");
12+
};
13+
14+
request.onsuccess = () => {
15+
resolve(request.result);
16+
};
17+
18+
request.onerror = () => {
19+
reject(request.error);
20+
};
21+
});
22+
}
23+
24+
// IndexedDB にデータを保存する
25+
export async function saveToIndexedDB(key: string, data: Blob): Promise<void> {
26+
const db = await openDB();
27+
const transaction = db.transaction("cache", "readwrite");
28+
const store = transaction.objectStore("cache");
29+
store.put(data, key);
30+
return new Promise<void>((resolve, reject) => {
31+
transaction.oncomplete = () => resolve();
32+
transaction.onerror = () => reject(transaction.error);
33+
});
34+
}
35+
36+
// IndexedDB からデータを取得する
37+
export async function getFromIndexedDB(key: string): Promise<Blob | undefined> {
38+
const db = await openDB();
39+
const transaction = db.transaction("cache", "readonly");
40+
const store = transaction.objectStore("cache");
41+
const req = store.get(key) as IDBRequest<Blob | undefined>;
42+
return new Promise<Blob | undefined>((resolve, reject) => {
43+
req.onsuccess = () => resolve(req.result);
44+
req.onerror = () => reject(req.error);
45+
});
46+
}

canine/app/clangd/ClangdServer.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import clangdWorkerUrl from "~/clangd/main.worker?worker&url";
2+
3+
export class ClangdServer {
4+
onError: (error: unknown) => void = () => { };
5+
onProgress: (value: number, max: number) => void = () => { };
6+
onUpdate: (data: any) => void = () => { };
7+
onReady: (worker: Worker) => void = () => { };
8+
worker: Worker | null = null;
9+
#requestId: number = 0;
10+
11+
async start(workspace_path: string, compiler_arguments: string[]): Promise<void> {
12+
const worker = new Worker(clangdWorkerUrl, {
13+
type: 'module',
14+
name: 'Clangd Worker',
15+
});
16+
const runtimeListener = (e: MessageEvent) => {
17+
switch (e.data.type) {
18+
case "change-compiler-arguments-done":
19+
console.log("change-compiler-arguments-done", e.data.id);
20+
this.onUpdate(e.data);
21+
break;
22+
case "error":
23+
this.onError(e.data.error);
24+
break;
25+
}
26+
};
27+
const promise = new Promise<void>((resolve, reject) => {
28+
const readyListener = (e: MessageEvent) => {
29+
switch (e.data?.type) {
30+
case "ready":
31+
this.onReady(worker);
32+
this.worker = worker;
33+
worker.removeEventListener("message", readyListener);
34+
worker.addEventListener("message", runtimeListener);
35+
resolve()
36+
break;
37+
case "progress":
38+
this.onProgress(e.data.value, e.data.max);
39+
break;
40+
case "error":
41+
this.onError(e.data.error);
42+
worker.removeEventListener("message", readyListener);
43+
reject(e.data.error);
44+
break;
45+
}
46+
};
47+
worker.addEventListener("message", readyListener);
48+
49+
worker.postMessage({ type: "init", workspace_path: workspace_path, compiler_arguments: compiler_arguments });
50+
});
51+
return promise;
52+
}
53+
54+
change_compiler_arguments(workspace_path: string, compiler_arguments: string[]) {
55+
this.worker?.postMessage({ type: "change-compiler-arguments", workspace_path, compiler_arguments, id: this.#requestId++ });
56+
}
57+
}

canine/app/clangd/StdoutParser.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Message } from 'vscode-jsonrpc/browser';
2+
3+
const CR = 13;
4+
const LF = 10;
5+
6+
export class StdoutParser {
7+
#state: 0 | 1 = 0;
8+
#token: number[] = [];
9+
#contentLength: number = 0;
10+
readonly #textDecoder = new TextDecoder("utf-8");
11+
12+
onRead: (message: Message) => void = () => { };
13+
add(charCode: number) {
14+
this.#token.push(charCode & 0xff);
15+
if (this.#state === 0) {
16+
// \r\n\r\n という値が続くまで読む
17+
if (charCode === LF &&
18+
this.#token.length >= 4 &&
19+
this.#token[this.#token.length - 2] === CR &&
20+
this.#token[this.#token.length - 3] === LF &&
21+
this.#token[this.#token.length - 4] === CR) {
22+
// #token は "Content-Length: 1234\r\n\r\n"
23+
// みたいな文字列になっているはずなので、
24+
// ここから 1234 の部分を取り出す
25+
const header = this.#textDecoder.decode(new Uint8Array(this.#token));
26+
header.split("\r\n").forEach((line) => {
27+
if (line.startsWith("Content-Length:")) {
28+
this.#contentLength = parseInt(line.slice(15));
29+
}
30+
});
31+
if (this.#contentLength !== 0) {
32+
this.#state = 1;
33+
this.#token.length = 0;
34+
}
35+
}
36+
} else {
37+
if (this.#token.length === this.#contentLength) {
38+
const message = JSON.parse(this.#textDecoder.decode(new Uint8Array(this.#token)));
39+
this.onRead(message);
40+
this.#state = 0;
41+
this.#token.length = 0;
42+
this.#contentLength = 0;
43+
}
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)