Skip to content

Commit

Permalink
clangd improvements
Browse files Browse the repository at this point in the history
- 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
- Prototype: File system related code added to monaco-languageclient/fs
  • Loading branch information
kaisalmen committed Oct 23, 2024
1 parent f4fd13a commit 8531e4e
Show file tree
Hide file tree
Showing 26 changed files with 7,375 additions and 247 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Click [here](https://www.typefox.io/blog/teaching-the-language-server-protocol-t
- [Python Language client and pyright language server example (Location)](#python-language-client-and-pyright-language-server-example-location)
- [Groovy Language client and language server example (Location)](#groovy-language-client-and-language-server-example-location)
- [Java Language client and language server example (Location)](#java-language-client-and-language-server-example-location)
- [Cpp / Clangd (Location)](#cpp--clangd-location)
- [Langium grammar DSL (Location)](#langium-grammar-dsl-location)
- [Statemachine DSL (created with Langium) (Location)](#statemachine-dsl-created-with-langium-location)
- [bare monaco-languageclient (Location)](#bare-monaco-languageclient-location)
Expand Down Expand Up @@ -138,6 +139,10 @@ The **java-client** contains the [monaco-editor-wrapper app](./packages/examples

Langium examples (here client and server communicate via `vscode-languageserver-protocol/browser` instead of a web socket used in the three examples above

#### Cpp / Clangd ([Location](./packages/examples/src/clangd))

It contains both the [language client](./packages/examples/src/clangd/client/main.ts) and the [langauge server (web worker)](./packages/examples/src/clangd/worker/clangd-server.ts). The clangd language server is compiled to wasm so it can be executed in the browser.

#### Langium grammar DSL ([Location](./packages/examples/src/langium/langium-dsl))

It contains both the [language client](./packages/examples/src/langium/langium-dsl/wrapperLangium.ts) and the [langauge server (web worker)](./packages/examples/src/langium/langium-dsl/worker/langium-server.ts). Here you can chose beforehand if the wrapper should be started in classic or extended mode.
Expand Down
17 changes: 16 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this npm module are documented in this file.

## [9.0.0-next.5] - 2024-10-23

- Prototype: File system endpoint.
- Added `createUrl` to `monaco-languageclient/tools`. Moved it here from `monaco-editor-wrapper`.
- Updated to eslint 9
- Support all arguments for monaco-vscode-api `initialize` [#756](https://github.com/TypeFox/monaco-languageclient/pull/756)
Expand Down
7 changes: 7 additions & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
"./vscode/services": {
"types": "./lib/vscode/index.d.ts",
"default": "./lib/vscode/index.js"
},
"./fs": {
"types": "./lib/fs/index.d.ts",
"default": "./lib/fs/index.js"
}
},
"typesVersions": {
Expand All @@ -43,6 +47,9 @@
],
"vscode/services": [
"lib/vscode/index"
],
"fs": [
"lib/fs/index"
]
}
},
Expand Down
127 changes: 127 additions & 0 deletions packages/client/src/fs/definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) 2024 TypeFox and others.
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */

import { Logger } from 'monaco-languageclient/tools';

export interface FileReadRequest {
resourceUri: string
}

export type FileReadResultStatus = 'success' | 'denied';

export interface FileReadRequestResult {
status: FileReadResultStatus
content: string | ArrayBuffer
}

export interface FileUpdate {
resourceUri: string
content: string | ArrayBuffer
}

export type FileUpdateResultStatus = 'equal' | 'updated' | 'created' | 'denied';

export interface FileUpdateResult {
status: FileUpdateResultStatus
message?: string
}

export interface DirectoryListingRequest {
directoryUri: string
}

export interface DirectoryListingRequestResult {
files: string[]
}

export type StatsRequestType = 'directory' | 'file';

export interface StatsRequest {
type: StatsRequestType,
resourceUri: string
}

export interface StatsRequestResult {
type: StatsRequestType
size: number
name: string
mtime: number
}

export enum EndpointType {
DRIVER,
FOLLOWER,
LOCAL,
EMPTY
}

export interface FileSystemCapabilities {

/**
* Get a text file content
* @param params the resourceUri of the file
* @returns The ReadFileResult containing the content of the file
*/
readFile(params: FileReadRequest): Promise<FileReadRequestResult>

/**
* Save a file on the filesystem
* @param params the resourceUri and the content of the file
* @returns The FileUpdateResult containing the result of the operation and an optional message
*/
writeFile(params: FileUpdate): Promise<FileUpdateResult>;

/**
* The implementation has to decide if the file at given uri at need to be updated
* @param params the resourceUri and the content of the file
* @returns The FileUpdateResult containing the result of the operation and an optional message
*/
syncFile(params: FileUpdate): Promise<FileUpdateResult>;

/**
* Get file stats on a given file
* @param params the resourceUri and if a file or a directory is requested
*/
getFileStats(params: StatsRequest): Promise<StatsRequestResult>

/**
* List the files of a directory
* @param resourceUri the Uri of the directory
*/
listFiles(params: DirectoryListingRequest): Promise<DirectoryListingRequestResult>

}

/**
* Defines the APT for a file system endpoint
*/
export interface FileSystemEndpoint extends FileSystemCapabilities {

/**
* Whatever can't be handled in the constructor should be done here
*/
init?(): void;

/**
* Set an optional logger
* @param logger the logger implemenation
*/
setLogger?(logger: Logger): void;

/**
* Get the type of the client
*/
getEndpointType(): EndpointType;

/**
* Provide info about the file system
*/
getFileSystemInfo(): string;

/**
* Signal readiness
*/
ready?(): void;
}
60 changes: 60 additions & 0 deletions packages/client/src/fs/endpoints/defaultEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) 2024 TypeFox and others.
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */

import { Logger } from 'monaco-languageclient/tools';
import { DirectoryListingRequest, DirectoryListingRequestResult, EndpointType, FileReadRequest, FileReadRequestResult, FileSystemEndpoint, FileUpdate, FileUpdateResult, StatsRequest, StatsRequestResult } from '../definitions.js';

export class EmptyFileSystemEndpoint implements FileSystemEndpoint {

private endpointType: EndpointType;
private logger?: Logger;

constructor(endpointType: EndpointType) {
this.endpointType = endpointType;
}

init(): void { }

getFileSystemInfo(): string {
return 'This file system performs no operations.';
}

setLogger(logger: Logger): void {
this.logger = logger;
}

getEndpointType(): EndpointType {
return this.endpointType;
}

readFile(params: FileReadRequest): Promise<FileReadRequestResult> {
this.logger?.info(`Reading file: ${params.resourceUri}`);
return Promise.resolve({
status: 'denied',
content: ''
});
}

writeFile(params: FileUpdate): Promise<FileUpdateResult> {
this.logger?.info(`Writing file: ${params.resourceUri}`);
return Promise.resolve({ status: 'denied' });
}

syncFile(params: FileUpdate): Promise<FileUpdateResult> {
this.logger?.info(`Syncing file: ${params.resourceUri}`);
return Promise.resolve({ status: 'denied' });
}

getFileStats(params: StatsRequest): Promise<StatsRequestResult> {
this.logger?.info(`Getting file stats for: "${params.resourceUri}" (${params.type})`);
return Promise.reject('No stats available.');
}

listFiles(params: DirectoryListingRequest): Promise<DirectoryListingRequestResult> {
this.logger?.info(`Listing files for directory: "${params.directoryUri}"`);
return Promise.reject('No file listing possible.');
}

}
7 changes: 7 additions & 0 deletions packages/client/src/fs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) 2024 TypeFox and others.
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */

export * from './definitions.js';
export * from './endpoints/defaultEndpoint.js';
58 changes: 58 additions & 0 deletions packages/client/test/fs/endpoints/emptyEndpoint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) 2024 TypeFox and others.
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */

import { describe, expect, test } from 'vitest';
import { EmptyFileSystemEndpoint, EndpointType } from 'monaco-languageclient/fs';

describe('EmptyFileSystemEndpoint Tests', () => {

const endpoint = new EmptyFileSystemEndpoint(EndpointType.EMPTY);

test('readFile', async () => {
const result = await endpoint.readFile({ resourceUri: '/tmp/test.js' });
expect(result).toEqual({
status: 'denied',
content: ''
});
});

test('writeFile', async () => {
const result = await endpoint.writeFile({
resourceUri: '/tmp/test.js',
content: 'console.log("Hello World!");'
});
expect(result).toEqual({
status: 'denied'
});
});

test('syncFile', async () => {
const result = await endpoint.syncFile({
resourceUri: '/tmp/test.js',
content: 'console.log("Hello World!");'
});
expect(result).toEqual({
status: 'denied'
});
});

test('getFileStats', async () => {
expect(async () => {
await endpoint.getFileStats({
type: 'file',
resourceUri: '/tmp/test.js'
});
}).rejects.toThrowError('No stats available.');
});

test('listFiles', async () => {
expect(async () => {
await endpoint.listFiles({
directoryUri: '/tmp'
});
}).rejects.toThrowError('No file listing possible.');
});

});
4 changes: 4 additions & 0 deletions packages/examples/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this npm module are documented in this file.

## [2024.10.5] - 2024-10-2x

- Added clangd example.

## [2024.10.4] - 2024-10-23

- Updated to `[email protected]`, `[email protected]` and `@typefox/[email protected]`.
Expand Down
7 changes: 5 additions & 2 deletions packages/examples/clangd.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@

<body>
<h2>Cpp Language Client & Clangd Language Server (Worker/Wasm)</h2>
This example has been derived from: <a href="https://github.com/guyutongxue/clangd-in-browser">clangd-in-browser</a><br>
<b>Heads up:</b> This is a prototype and still evolving.<br>
The clangd language server worker has been derived from: <a href="https://github.com/guyutongxue/clangd-in-browser">clangd-in-browser</a><br>
<button type="button" id="button-start">Start</button>
<button type="button" id="button-dispose">Dispose</button>
<div id="monaco-editor-root" style="width:800px;height:600px;border:1px solid grey"></div>
<label for="openFiles">Select open file:</label>
<select name="openFiles" id="openFiles">
</select>
<script type="module">
import { runClangdWrapper } from "./src/clangd/client/main.ts";

Expand Down
4 changes: 3 additions & 1 deletion packages/examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@
"vscode-languageserver": "~9.0.1",
"vscode-uri": "~3.0.8",
"vscode-ws-jsonrpc": "~3.3.2",
"ws": "~8.18.0"
"ws": "~8.18.0",
"wtd-core": "~4.0.1"
},
"devDependencies": {
"@types/express": "~5.0.0",
"@types/ws": "~8.5.12",
"@types/emscripten": "~1.39.13",
"langium-cli": "~3.2.0",
"ts-node": "~10.9.1",
"vscode-languageserver-types": "~3.17.5"
Expand Down
Loading

0 comments on commit 8531e4e

Please sign in to comment.