Skip to content

Handle languageclient errors cases more robustly #859

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package-lock.json

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

2 changes: 1 addition & 1 deletion packages/client/src/commonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */

import { MonacoLanguageClient } from './client.js';
import type { MonacoLanguageClient } from './client.js';

export type ConnectionConfigOptions = WebSocketConfigOptionsDirect | WebSocketConfigOptionsParams | WebSocketConfigOptionsUrl | WorkerConfigOptionsParams | WorkerConfigOptionsDirect;

Expand Down
3 changes: 2 additions & 1 deletion packages/client/src/vscode/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'vscode/localExtensionHost';
import { initialize, type IWorkbenchConstructionOptions } from '@codingame/monaco-vscode-api';
import type { OpenEditor } from '@codingame/monaco-vscode-editor-service-override';
import type { WorkerConfig } from '@codingame/monaco-vscode-extensions-service-override';
import getConfigurationServiceOverride, { initUserConfiguration } from '@codingame/monaco-vscode-configuration-service-override';
import getExtensionServiceOverride from '@codingame/monaco-vscode-extensions-service-override';
import getLanguagesServiceOverride from '@codingame/monaco-vscode-languages-service-override';
import getModelServiceOverride from '@codingame/monaco-vscode-model-service-override';
Expand All @@ -17,7 +18,6 @@ import type { EnvironmentOverride } from '@codingame/monaco-vscode-api/workbench
import type { Logger } from 'monaco-languageclient/tools';
import { FakeWorker as Worker } from './fakeWorker.js';
import { setUnexpectedErrorHandler } from '@codingame/monaco-vscode-api/monaco';
import { initUserConfiguration } from '@codingame/monaco-vscode-configuration-service-override';

export interface MonacoEnvironmentEnhanced extends monaco.Environment {
vscodeInitialising?: boolean;
Expand Down Expand Up @@ -75,6 +75,7 @@ export const getMonacoEnvironmentEnhanced = () => {

export const supplyRequiredServices = async () => {
return {
...getConfigurationServiceOverride(),
...getLanguagesServiceOverride(),
...getLogServiceOverride(),
...getModelServiceOverride()
Expand Down
4 changes: 2 additions & 2 deletions packages/client/test/fs/endpoints/emptyEndpoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('EmptyFileSystemEndpoint Tests', () => {
});

test('getFileStats', async () => {
expect(async () => {
await expect(async () => {
await endpoint.getFileStats({
type: 'file',
resourceUri: '/tmp/test.js'
Expand All @@ -48,7 +48,7 @@ describe('EmptyFileSystemEndpoint Tests', () => {
});

test('listFiles', async () => {
expect(async () => {
await expect(async () => {
await endpoint.listFiles({
directoryUri: '/tmp'
});
Expand Down
4 changes: 0 additions & 4 deletions packages/client/test/vscode/services.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@

import { describe, expect, test } from 'vitest';
import { initServices, type MonacoEnvironmentEnhanced } from 'monaco-languageclient/vscode/services';
import getConfigurationServiceOverride from '@codingame/monaco-vscode-configuration-service-override';

describe('VSCde services Tests', () => {

test('initServices', async () => {
const vscodeApiConfig = {
serviceOverrides: {
...getConfigurationServiceOverride()
},
userConfiguration: {
json: JSON.stringify({
'workbench.colorTheme': 'Default Dark Modern'
Expand Down
2 changes: 0 additions & 2 deletions packages/examples/src/appPlayground/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import * as vscode from 'vscode';
import { LogLevel } from '@codingame/monaco-vscode-api';
import { RegisteredFileSystemProvider, registerFileSystemOverlay, RegisteredMemoryFile } from '@codingame/monaco-vscode-files-service-override';
import getConfigurationServiceOverride from '@codingame/monaco-vscode-configuration-service-override';
import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override';
import getLifecycleServiceOverride from '@codingame/monaco-vscode-lifecycle-service-override';
import getLocalizationServiceOverride from '@codingame/monaco-vscode-localization-service-override';
Expand Down Expand Up @@ -51,7 +50,6 @@ export const configure = (htmlContainer?: HTMLElement): ConfigResult => {
htmlContainer,
vscodeApiConfig: {
serviceOverrides: {
...getConfigurationServiceOverride(),
...getKeybindingsServiceOverride(),
...getLifecycleServiceOverride(),
...getLocalizationServiceOverride(createDefaultLocaleConfiguration()),
Expand Down
4 changes: 0 additions & 4 deletions packages/examples/src/bare/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import * as monaco from '@codingame/monaco-vscode-editor-api';
import { initServices } from 'monaco-languageclient/vscode/services';
import { LogLevel } from '@codingame/monaco-vscode-api';
import getConfigurationServiceOverride from '@codingame/monaco-vscode-configuration-service-override';
// monaco-editor does not supply json highlighting with the json worker,
// that's why we use the textmate extension from VSCode
import '@codingame/monaco-vscode-json-default-extension';
Expand All @@ -20,9 +19,6 @@ export const runClient = async () => {
const logger = new ConsoleLogger(LogLevel.Debug);
const htmlContainer = document.getElementById('monaco-editor-root')!;
await initServices({
serviceOverrides: {
...getConfigurationServiceOverride()
},
userConfiguration: {
json: JSON.stringify({
'editor.experimental.asyncTokenization': true
Expand Down
2 changes: 0 additions & 2 deletions packages/examples/src/clangd/client/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* ------------------------------------------------------------------------------------------ */

import { Uri } from 'vscode';
import getConfigurationServiceOverride from '@codingame/monaco-vscode-configuration-service-override';
import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override';
import getLifecycleServiceOverride from '@codingame/monaco-vscode-lifecycle-service-override';
import getBannerServiceOverride from '@codingame/monaco-vscode-view-banner-service-override';
Expand Down Expand Up @@ -58,7 +57,6 @@ export const createWrapperConfig = async (config: {
},
vscodeApiConfig: {
serviceOverrides: {
...getConfigurationServiceOverride(),
...getKeybindingsServiceOverride(),
...getLifecycleServiceOverride(),
...getBannerServiceOverride(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */

import getConfigurationServiceOverride from '@codingame/monaco-vscode-configuration-service-override';
import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override';
import { LogLevel } from '@codingame/monaco-vscode-api';
import type { Logger } from 'monaco-languageclient/tools';
Expand All @@ -22,7 +21,6 @@ export const setupLangiumClientClassic = async (langiumWorker: Worker): Promise<
logLevel: LogLevel.Debug,
vscodeApiConfig: {
serviceOverrides: {
...getConfigurationServiceOverride(),
...getKeybindingsServiceOverride()
}
},
Expand Down
2 changes: 0 additions & 2 deletions packages/examples/src/python/client/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* ------------------------------------------------------------------------------------------ */

import * as vscode from 'vscode';
import getConfigurationServiceOverride from '@codingame/monaco-vscode-configuration-service-override';
import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override';
import getLifecycleServiceOverride from '@codingame/monaco-vscode-lifecycle-service-override';
import getLocalizationServiceOverride from '@codingame/monaco-vscode-localization-service-override';
Expand Down Expand Up @@ -134,7 +133,6 @@ export const createWrapperConfig = (): PythonAppConfig => {
},
vscodeApiConfig: {
serviceOverrides: {
...getConfigurationServiceOverride(),
...getKeybindingsServiceOverride(),
...getLifecycleServiceOverride(),
...getLocalizationServiceOverride(createDefaultLocaleConfiguration()),
Expand Down
62 changes: 32 additions & 30 deletions packages/wrapper-react/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,46 +51,44 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
}, [className]);

const handleReInit = useCallback(async () => {
if (wrapperRef.current.isStopping() === undefined) {
await destroyMonaco();
} else {
await wrapperRef.current.isStopping();
}

if (wrapperRef.current.isStarting() === undefined) {
await initMonaco();
await startMonaco();
} else {
await wrapperRef.current.isStarting();
}

await destroyMonaco();
await initMonaco();
await startMonaco();
}, [wrapperConfig]);

const initMonaco = useCallback(async () => {
if (containerRef.current) {
wrapperConfig.htmlContainer = containerRef.current;
await wrapperRef.current.init(wrapperConfig);
if (!wrapperRef.current.isInitializing()) {
if (containerRef.current) {
wrapperConfig.htmlContainer = containerRef.current;
await wrapperRef.current.init(wrapperConfig);
} else {
throw new Error('No htmlContainer found! Aborting...');
}
} else {
throw new Error('No htmlContainer found! Aborting...');
await wrapperRef.current.getInitializingAwait();
}
}, [wrapperConfig]);

const startMonaco = useCallback(async () => {
if (containerRef.current) {
try {
wrapperRef.current.registerTextChangeCallback(onTextChanged);
await wrapperRef.current.start();
onLoad?.(wrapperRef.current);
handleOnTextChanged();
} catch (e) {
if (onError) {
onError(e);
} else {
throw e;
if (!wrapperRef.current.isStarting()) {
if (containerRef.current) {
try {
wrapperRef.current.registerTextChangeCallback(onTextChanged);
await wrapperRef.current.start();
onLoad?.(wrapperRef.current);
handleOnTextChanged();
} catch (e) {
if (onError) {
onError(e);
} else {
throw e;
}
}
} else {
throw new Error('No htmlContainer found! Aborting...');
}
} else {
throw new Error('No htmlContainer found! Aborting...');
await wrapperRef.current.getStartingAwait();
}
}, [onError, onLoad, onTextChanged]);

Expand All @@ -100,7 +98,11 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {

const destroyMonaco = useCallback(async () => {
try {
await wrapperRef.current.dispose();
if (!wrapperRef.current.isStopping()) {
await wrapperRef.current.dispose();
} else {
await wrapperRef.current.getStoppingAwait();
}
} catch {
// The language client may throw an error during disposal.
// This should not prevent us from continue working.
Expand Down
21 changes: 21 additions & 0 deletions packages/wrapper-react/test/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */

import { LogLevel } from '@codingame/monaco-vscode-api';
import type { WrapperConfig } from 'monaco-editor-wrapper';
import { useWorkerFactory } from 'monaco-languageclient/workerFactory';

export const configureMonacoWorkers = () => {
Expand All @@ -12,3 +14,22 @@ export const configureMonacoWorkers = () => {
}
});
};

export const createDefaultWrapperConfig = (): WrapperConfig => {
return {
$type: 'extended',
logLevel: LogLevel.Debug,
vscodeApiConfig: {
loadThemes: false
},
editorAppConfig: {
codeResources: {
modified: {
text: 'hello world',
fileExt: 'js'
}
},
monacoWorkerFactory: configureMonacoWorkers
}
};
};
62 changes: 13 additions & 49 deletions packages/wrapper-react/test/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,38 @@
import { describe, expect, test } from 'vitest';
import { render, type RenderResult } from '@testing-library/react';
import React from 'react';
import { LogLevel } from '@codingame/monaco-vscode-api';
import { MonacoEditorLanguageClientWrapper, type TextContents, type WrapperConfig } from 'monaco-editor-wrapper';
import { MonacoEditorLanguageClientWrapper, type TextContents } from 'monaco-editor-wrapper';
import { MonacoEditorReactComp } from '@typefox/monaco-editor-react';
import { configureMonacoWorkers } from './helper.js';
import { createDefaultWrapperConfig } from './helper.js';

describe('Test MonacoEditorReactComp', () => {
test('rerender', async () => {
const wrapperConfig: WrapperConfig = {
$type: 'extended',
logLevel: LogLevel.Debug,
vscodeApiConfig: {
loadThemes: false
},
editorAppConfig: {
monacoWorkerFactory: configureMonacoWorkers
}
};
const wrapperConfig = createDefaultWrapperConfig();
const { rerender } = render(<MonacoEditorReactComp wrapperConfig={wrapperConfig} />);
rerender(<MonacoEditorReactComp wrapperConfig={wrapperConfig} />);
await Promise.resolve();
rerender(<MonacoEditorReactComp wrapperConfig={wrapperConfig} />);
});

test('onLoad', async () => {
const wrapperConfig = createDefaultWrapperConfig();

let renderResult: RenderResult;
// we have to await the full start of the editor with the onLoad callback, then it is save to contine
const p = await new Promise<void>(resolve => {
await expect(await new Promise<void>(resolve => {
const handleOnLoad = async (_wrapper: MonacoEditorLanguageClientWrapper) => {
renderResult.rerender(<MonacoEditorReactComp wrapperConfig={wrapperConfig} />);

console.log('onLoad');
resolve();
};
// render(<MonacoEditorReactComp wrapperConfig={wrapperConfig} onLoad={handleOnLoad} />);
renderResult = render(<MonacoEditorReactComp wrapperConfig={wrapperConfig} onLoad={handleOnLoad} />);
});
// void promise is undefined after it was awaited
expect(p).toBeUndefined();
})).toBeUndefined();
});

test('update onTextChanged', async () => {
const wrapperConfig: WrapperConfig = {
$type: 'extended',
logLevel: LogLevel.Debug,
vscodeApiConfig: {
loadThemes: false
},
editorAppConfig: {
codeResources: {
modified: {
text: 'hello world',
fileExt: 'js'
}
},
monacoWorkerFactory: configureMonacoWorkers
}
};
const wrapperConfig = createDefaultWrapperConfig();

const textReceiverHello = (textChanges: TextContents) => {
expect(textChanges.modified).toEqual('hello world');
Expand All @@ -71,22 +50,7 @@ describe('Test MonacoEditorReactComp', () => {
});

test('update codeResources', async () => {
const wrapperConfig: WrapperConfig = {
$type: 'extended',
logLevel: LogLevel.Debug,
vscodeApiConfig: {
loadThemes: false
},
editorAppConfig: {
codeResources: {
modified: {
text: 'hello world',
fileExt: 'js'
}
},
monacoWorkerFactory: configureMonacoWorkers
}
};
const wrapperConfig = createDefaultWrapperConfig();

let count = 0;
const textReceiver = (textChanges: TextContents) => {
Expand Down
1 change: 0 additions & 1 deletion packages/wrapper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@
},
"dependencies": {
"@codingame/monaco-vscode-api": "~14.0.2",
"@codingame/monaco-vscode-configuration-service-override": "~14.0.2",
"@codingame/monaco-vscode-editor-api": "~14.0.2",
"@codingame/monaco-vscode-editor-service-override": "~14.0.2",
"@codingame/monaco-vscode-extension-api": "~14.0.2",
Expand Down
Loading