Skip to content

Commit a43b165

Browse files
committed
WIP: clangd improvements
- new example: socket.io for file protocol to non worker target - define file system endpoints - clangd LS uses message port for communication - Use wtm new ComChannelEndpoints for handling async communication of message channels or workers - worker transfers files to client via message channel - clangd example: list open files below editor
1 parent 7d97711 commit a43b165

31 files changed

+7821
-423
lines changed

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ <h2>Monaco Editor React</h2>
7474
<h3>monaco-editor related examples</h3>
7575
<a href="./packages/examples/ts.html">Monaco Editor Wrapper TypeScript Example</a>
7676
<br>
77+
<a href="./packages/examples/files.html">Files Testbed</a>
7778

7879
<h2>Verification</h2>
7980
<h3>Angular</h2>

package-lock.json

Lines changed: 412 additions & 184 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
"@codingame/esbuild-import-meta-url-plugin": "~1.0.2",
66
"@codingame/monaco-vscode-rollup-vsix-plugin": "~9.0.3",
77
"@testing-library/react": "~16.0.1",
8-
"@types/node": "~20.16.9",
9-
"@types/react": "~18.3.9",
8+
"@types/node": "~20.16.10",
9+
"@types/react": "~18.3.10",
1010
"@types/react-dom": "~18.3.0",
1111
"@types/vscode": "~1.93.0",
1212
"@typescript-eslint/eslint-plugin": "~7.18.0",
1313
"@typescript-eslint/parser": "~7.18.0",
14-
"@vitejs/plugin-react": "~4.3.1",
14+
"@vitejs/plugin-react": "~4.3.2",
1515
"@vitest/browser": "~2.1.1",
1616
"editorconfig": "~2.0.0",
1717
"esbuild": "~0.24.0",
@@ -26,7 +26,7 @@
2626
"vite": "~5.4.8",
2727
"vite-node": "~2.1.1",
2828
"vitest": "~2.1.1",
29-
"webdriverio": "~9.1.1"
29+
"webdriverio": "~9.1.2"
3030
},
3131
"volta": {
3232
"node": "20.17.0",

packages/client/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
"./vscode/services": {
3232
"types": "./lib/vscode/index.d.ts",
3333
"default": "./lib/vscode/index.js"
34+
},
35+
"./fs": {
36+
"types": "./lib/fs/index.d.ts",
37+
"default": "./lib/fs/index.js"
3438
}
3539
},
3640
"typesVersions": {
@@ -43,6 +47,9 @@
4347
],
4448
"vscode/services": [
4549
"lib/vscode/index"
50+
],
51+
"fs": [
52+
"lib/fs/index"
4653
]
4754
}
4855
},

packages/client/src/fs/definitions.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/* --------------------------------------------------------------------------------------------
2+
* Copyright (c) 2024 TypeFox and others.
3+
* Licensed under the MIT License. See LICENSE in the package root for license information.
4+
* ------------------------------------------------------------------------------------------ */
5+
6+
import { Logger } from 'monaco-languageclient/tools';
7+
8+
export interface FileReadRequest {
9+
resourceUri: string
10+
}
11+
12+
export type FileReadResultStatus = 'success' | 'denied';
13+
14+
export interface FileReadRequestResult {
15+
status: FileReadResultStatus
16+
content: string | ArrayBuffer
17+
}
18+
19+
export interface FileUpdate {
20+
resourceUri: string
21+
content: string | ArrayBuffer
22+
}
23+
24+
export type FileUpdateResultStatus = 'equal' | 'updated' | 'created' | 'denied';
25+
26+
export interface FileUpdateResult {
27+
status: FileUpdateResultStatus
28+
message?: string
29+
}
30+
31+
export interface DirectoryListingRequest {
32+
directoryUri: string
33+
}
34+
35+
export interface DirectoryListingRequestResult {
36+
files: string[]
37+
}
38+
39+
export type StatsRequestType = 'directory' | 'file';
40+
41+
export interface StatsRequest {
42+
type: StatsRequestType,
43+
resourceUri: string
44+
}
45+
46+
export interface StatsRequestResult {
47+
type: StatsRequestType
48+
size: number
49+
name: string
50+
mtime: number
51+
}
52+
53+
export enum EndpointType {
54+
DRIVER,
55+
FOLLOWER,
56+
LOCAL,
57+
EMPTY
58+
}
59+
60+
export interface FileSystemCapabilities {
61+
62+
/**
63+
* Get a text file content
64+
* @param params the resourceUri of the file
65+
* @returns The ReadFileResult containing the content of the file
66+
*/
67+
readFile(params: FileReadRequest): Promise<FileReadRequestResult>
68+
69+
/**
70+
* Save a file on the filesystem
71+
* @param params the resourceUri and the content of the file
72+
* @returns The FileUpdateResult containing the result of the operation and an optional message
73+
*/
74+
writeFile(params: FileUpdate): Promise<FileUpdateResult>;
75+
76+
/**
77+
* The implementation has to decide if the file at given uri at need to be updated
78+
* @param params the resourceUri and the content of the file
79+
* @returns The FileUpdateResult containing the result of the operation and an optional message
80+
*/
81+
syncFile(params: FileUpdate): Promise<FileUpdateResult>;
82+
83+
/**
84+
* Get file stats on a given file
85+
* @param params the resourceUri and if a file or a directory is requested
86+
*/
87+
getFileStats(params: StatsRequest): Promise<StatsRequestResult>
88+
89+
/**
90+
* List the files of a directory
91+
* @param resourceUri the Uri of the directory
92+
*/
93+
listFiles(params: DirectoryListingRequest): Promise<DirectoryListingRequestResult>
94+
95+
}
96+
97+
/**
98+
* Defines the APT for a file system endpoint
99+
*/
100+
export interface FileSystemEndpoint extends FileSystemCapabilities {
101+
102+
/**
103+
* Whatever can't be handled in the constructor should be done here
104+
*/
105+
init?(): void;
106+
107+
/**
108+
* Set an optional logger
109+
* @param logger the logger implemenation
110+
*/
111+
setLogger?(logger: Logger): void;
112+
113+
/**
114+
* Get the type of the client
115+
*/
116+
getEndpointType(): EndpointType;
117+
118+
/**
119+
* Provide info about the file system
120+
*/
121+
getFileSystemInfo(): string;
122+
123+
/**
124+
* Signal readiness
125+
*/
126+
ready?(): void;
127+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/* --------------------------------------------------------------------------------------------
2+
* Copyright (c) 2024 TypeFox and others.
3+
* Licensed under the MIT License. See LICENSE in the package root for license information.
4+
* ------------------------------------------------------------------------------------------ */
5+
6+
import { Logger } from 'monaco-languageclient/tools';
7+
import { DirectoryListingRequest, DirectoryListingRequestResult, EndpointType, FileReadRequest, FileReadRequestResult, FileSystemEndpoint, FileUpdate, FileUpdateResult, StatsRequest, StatsRequestResult } from '../definitions.js';
8+
9+
export class EmptyFileSystemEndpoint implements FileSystemEndpoint {
10+
11+
private endpointType: EndpointType;
12+
private logger?: Logger;
13+
14+
constructor(endpointType: EndpointType) {
15+
this.endpointType = endpointType;
16+
}
17+
18+
init(): void { }
19+
20+
getFileSystemInfo(): string {
21+
return 'This file system performs no operations.';
22+
}
23+
24+
setLogger(logger: Logger): void {
25+
this.logger = logger;
26+
}
27+
28+
getEndpointType(): EndpointType {
29+
return this.endpointType;
30+
}
31+
32+
readFile(params: FileReadRequest): Promise<FileReadRequestResult> {
33+
this.logger?.info(`Reading file: ${params.resourceUri}`);
34+
return Promise.resolve({
35+
status: 'denied',
36+
content: ''
37+
});
38+
}
39+
40+
writeFile(params: FileUpdate): Promise<FileUpdateResult> {
41+
this.logger?.info(`Writing file: ${params.resourceUri}`);
42+
return Promise.resolve({ status: 'denied' });
43+
}
44+
45+
syncFile(params: FileUpdate): Promise<FileUpdateResult> {
46+
this.logger?.info(`Syncing file: ${params.resourceUri}`);
47+
return Promise.resolve({ status: 'denied' });
48+
}
49+
50+
getFileStats(params: StatsRequest): Promise<StatsRequestResult> {
51+
this.logger?.info(`Getting file stats for: "${params.resourceUri}" (${params.type})`);
52+
return Promise.reject('No stats available.');
53+
}
54+
55+
listFiles(params: DirectoryListingRequest): Promise<DirectoryListingRequestResult> {
56+
this.logger?.info(`Listing files for directory: "${params.directoryUri}"`);
57+
return Promise.reject('No file listing possible.');
58+
}
59+
60+
}

packages/client/src/fs/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/* --------------------------------------------------------------------------------------------
2+
* Copyright (c) 2024 TypeFox and others.
3+
* Licensed under the MIT License. See LICENSE in the package root for license information.
4+
* ------------------------------------------------------------------------------------------ */
5+
6+
export * from './definitions.js';
7+
export * from './endpoints/defaultEndpoint.js';

packages/client/src/vscode/services.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ export const initEnhancedMonacoEnvironment = () => {
4848
return envEnhanced;
4949
};
5050

51+
export const getMonacoEnvironmentEnhanced = () => {
52+
const monWin = (self as Window);
53+
return monWin.MonacoEnvironment as MonacoEnvironmentEnhanced;
54+
};
55+
5156
export const supplyRequiredServices = async () => {
5257
return {
5358
...getLanguagesServiceOverride(),
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* --------------------------------------------------------------------------------------------
2+
* Copyright (c) 2024 TypeFox and others.
3+
* Licensed under the MIT License. See LICENSE in the package root for license information.
4+
* ------------------------------------------------------------------------------------------ */
5+
6+
import { describe, expect, test } from 'vitest';
7+
import { EmptyFileSystemEndpoint, EndpointType } from 'monaco-languageclient/fs';
8+
9+
describe('EmptyFileSystemEndpoint Tests', () => {
10+
11+
const endpoint = new EmptyFileSystemEndpoint(EndpointType.EMPTY);
12+
13+
test('readFile', async () => {
14+
const result = await endpoint.readFile({ resourceUri: '/tmp/test.js' });
15+
expect(result).toEqual({
16+
status: 'denied',
17+
content: ''
18+
});
19+
});
20+
21+
test('writeFile', async () => {
22+
const result = await endpoint.writeFile({
23+
resourceUri: '/tmp/test.js',
24+
content: 'console.log("Hello World!");'
25+
});
26+
expect(result).toEqual({
27+
status: 'denied'
28+
});
29+
});
30+
31+
test('syncFile', async () => {
32+
const result = await endpoint.syncFile({
33+
resourceUri: '/tmp/test.js',
34+
content: 'console.log("Hello World!");'
35+
});
36+
expect(result).toEqual({
37+
status: 'denied'
38+
});
39+
});
40+
41+
test('getFileStats', async () => {
42+
expect(async () => {
43+
await endpoint.getFileStats({
44+
type: 'file',
45+
resourceUri: '/tmp/test.js'
46+
});
47+
}).rejects.toThrowError('No stats available.');
48+
});
49+
50+
test('listFiles', async () => {
51+
expect(async () => {
52+
await endpoint.listFiles({
53+
directoryUri: '/tmp'
54+
});
55+
}).rejects.toThrowError('No file listing possible.');
56+
});
57+
58+
});

packages/examples/clangd.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
<body>
1212
<h2>Cpp Language Client & Clangd Language Server (Worker/Wasm)</h2>
1313
This example has been derived from: <a href="https://github.com/guyutongxue/clangd-in-browser">clangd-in-browser</a><br>
14-
<button type="button" id="button-start">Start</button>
15-
<button type="button" id="button-dispose">Dispose</button>
1614
<div id="monaco-editor-root" style="width:800px;height:600px;border:1px solid grey"></div>
15+
<div id="openFiles"></div>
1716
<script type="module">
1817
import { runClangdWrapper } from "./src/clangd/client/main.ts";
1918

packages/examples/files.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<title>Files Testbed</title>
6+
<meta charset="UTF-8" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<link rel="stylesheet" href="style.css">
9+
</head>
10+
11+
12+
<body>
13+
<h2>Files Testbed</h2>
14+
<div id="monaco-editor-root" style="width:800px;height:600px;border:1px solid grey"></div>
15+
<script type="module">
16+
import { runFilesClient } from "./src/files/client/filesClient.ts";
17+
18+
runFilesClient();
19+
</script>
20+
</body>
21+
</html>

packages/examples/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,21 @@
8383
"react": "~18.3.1",
8484
"react-dom": "~18.3.1",
8585
"request-light": "~0.8.0",
86+
"socket.io": "~4.8.0",
87+
"socket.io-client": "~4.8.0",
8688
"vscode": "npm:@codingame/monaco-vscode-api@~9.0.3",
8789
"vscode-json-languageservice": "~5.4.1",
8890
"vscode-languageclient": "~9.0.1",
8991
"vscode-languageserver": "~9.0.1",
9092
"vscode-uri": "~3.0.8",
9193
"vscode-ws-jsonrpc": "~3.3.2",
92-
"ws": "~8.18.0"
94+
"ws": "~8.18.0",
95+
"wtd-core": "~4.0.1"
9396
},
9497
"devDependencies": {
9598
"@types/express": "~4.17.21",
9699
"@types/ws": "~8.5.12",
100+
"@types/emscripten": "~1.39.13",
97101
"langium-cli": "~3.2.0",
98102
"ts-node": "~10.9.1",
99103
"vscode-languageserver-types": "~3.17.5"
@@ -118,6 +122,7 @@
118122
"build:msg": "echo Building main examples:",
119123
"build": "npm run build:msg && npm run clean && npm run resources:download && npm run extract:docker && npm run compile",
120124
"build:bundle": "vite --config vite.bundle.config.ts build",
125+
"start:server:files": "vite-node src/files/server/direct.ts",
121126
"start:server:json": "vite-node src/json/server/direct.ts",
122127
"start:server:python": "vite-node src/python/server/direct.ts",
123128
"start:server:groovy": "vite-node src/groovy/server/direct.ts",

0 commit comments

Comments
 (0)