Skip to content

Commit 7fd19a8

Browse files
committed
Use flags reprocessConfig to trigger a editor config re-processing and enforceLanguageClientDispose to dispose the language client on demand
1 parent 3e4aa3a commit 7fd19a8

File tree

9 files changed

+180
-118
lines changed

9 files changed

+180
-118
lines changed

packages/client/src/wrapper/lcconfig.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export interface LanguageClientConfig {
1919
restartOptions?: LanguageClientRestartOptions;
2020
disposeWorker?: boolean;
2121
logLevel?: LogLevel | number;
22-
enforceDispose?: boolean;
2322
}
2423

2524
export interface LanguageClientRestartOptions {

packages/examples/src/langium/statemachine/main-react.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const runStatemachineReact = async (noControls: boolean) => {
2828
const App = () => {
2929
const [codeState, setCodeState] = useState<string>(text);
3030
const [disposeLcState, setDisposeLcState] = useState<boolean>(false);
31-
const [uriState, setUriState] = useState<string>('/workspace/example.statemachine');
31+
const [reprocessConfig, setReprocessConfig] = useState<boolean>(false);
3232

3333
const onTextChanged = (textChanges: TextContents) => {
3434
if (textChanges.modified !== codeState) {
@@ -40,26 +40,33 @@ export const runStatemachineReact = async (noControls: boolean) => {
4040
languageServerId: 'react',
4141
codeContent: {
4242
text: codeState,
43-
uri: uriState
43+
uri: '/workspace/example.statemachine'
4444
},
4545
worker,
4646
messageTransports: { reader, writer }
4747
});
48-
appConfig.languageClientConfig.enforceDispose = disposeLcState;
4948

5049
return (
5150
<>
5251
<div>
53-
<button style={{background: 'purple'}} onClick={() => setCodeState(codeState + '\n// comment')}>Change Text</button>
54-
<button style={{background: 'red'}} onClick={() => setDisposeLcState(!disposeLcState)}>Swatch LC Dispose</button>
55-
<button style={{background: 'orange'}} onClick={() => setUriState('/workspace/example2.statemachine')}>Change URI</button>
52+
<button style={{background: 'purple'}} onClick={() => {
53+
setCodeState(codeState + '\n// comment');
54+
setReprocessConfig(!reprocessConfig);
55+
}}>Change Text</button>
56+
<button style={{background: 'green'}} onClick={() => setReprocessConfig(!reprocessConfig)}>Reprocess Config</button>
57+
<button style={{background: 'red'}} onClick={() => setDisposeLcState(!disposeLcState)}>Flip LC</button>
5658

5759
<MonacoEditorReactComp
5860
style={{ 'height': '50vh' }}
5961
vscodeApiConfig={appConfig.vscodeApiConfig}
6062
editorAppConfig={appConfig.editorAppConfig}
6163
languageClientConfig={appConfig.languageClientConfig}
6264
onTextChanged={onTextChanged}
65+
logLevel={LogLevel.Debug}
66+
reprocessConfig={reprocessConfig}
67+
onConfigProcessed={() => console.log(' >>> config processed <<<')}
68+
enforceLanguageClientDispose={disposeLcState}
69+
onDisposeLanguageClient={() => console.log(' >>> language client disposed <<<')}
6370
/>
6471
<b>Debug:</b><br />{codeState}
6572
</div>

packages/wrapper-react/src/index.tsx

Lines changed: 73 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ export type MonacoEditorProps = {
2222
onEditorStartDone?: (editorApp?: EditorApp) => void;
2323
onLanguageClientsStartDone?: (lcsManager: LanguageClientManager) => void;
2424
onTextChanged?: (textChanges: TextContents) => void;
25-
onConfigProcessed?: (editorApp?: EditorApp) => void;
25+
onConfigProcessed?: (result: { textUpdated: boolean, modelUpdated: boolean}, editorApp?: EditorApp) => void;
2626
onError?: (error: Error) => void;
2727
onDisposeEditor?: () => void;
2828
onDisposeLanguageClient?: () => void;
29+
reprocessConfig?: boolean;
30+
enforceLanguageClientDispose?: boolean;
2931
logLevel?: LogLevel | number;
3032
}
3133

@@ -61,7 +63,7 @@ const executeQueue = async () => {
6163
const lengthBefore = runQueue.length;
6264
const queueObj = runQueue.shift();
6365
debugLogging('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<');
64-
debugLogging(`QUEUE ${queueObj?.id} start: SIZE before: ${lengthBefore}`, true);
66+
debugLogging(`QUEUE ${queueObj?.id} start: SIZE before: ${lengthBefore}`);
6567
await queueObj?.func(queueObj.currentContainer);
6668
debugLogging(`QUEUE ${queueObj?.id} end: SIZE after: ${runQueue.length}`);
6769
}
@@ -89,12 +91,9 @@ const stopQueue = () => {
8991
}
9092
};
9193

92-
const debugLogging = (id: string, useTime?: boolean) => {
93-
if (useTime === true) {
94-
logger.debug(`${id}: ${Date.now()}`);
95-
} else {
96-
logger.debug(id);
97-
}
94+
const debugLogging = (id: string) => {
95+
const now = new Date(Date.now())
96+
logger.debug(`[${now.getMinutes()}:${now.getSeconds()}:${now.getMilliseconds()}]: ${id}`);
9897
};
9998

10099
export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
@@ -112,7 +111,9 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
112111
onError,
113112
onDisposeEditor,
114113
onDisposeLanguageClient,
115-
logLevel
114+
logLevel,
115+
reprocessConfig,
116+
enforceLanguageClientDispose
116117
} = props;
117118

118119
const editorAppRef = useRef<EditorApp>(undefined);
@@ -124,9 +125,11 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
124125
const onTextChangedRef = useRef(onTextChanged);
125126
const launchingRef = useRef<boolean>(false);
126127
const editorAppConfigRef = useRef<EditorAppConfig>(undefined);
128+
const flipReprocessConfigRef = useRef<boolean>(false);
129+
const flipLanguageClientDisposeRef = useRef<boolean>(false);
127130

128131
const performErrorHandling = (error: Error) => {
129-
debugLogging(`ERROR: ${error.message}`, true);
132+
debugLogging(`ERROR: ${error.message}`);
130133
if (onError) {
131134
onError(error);
132135
} else {
@@ -152,15 +155,10 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
152155
apiWrapper = new MonacoVscodeApiWrapper(vscodeApiConfig!);
153156
const globalInitFunc = async () => {
154157
try {
155-
debugLogging('GLOBAL INIT', true);
156-
157158
if (apiWrapper === undefined) throw new Error('Unexpected error occurred: apiWrapper is not available! Aborting...');
158159

159160
await apiWrapper.start();
160161
onVscodeApiInitDone?.(apiWrapper);
161-
162-
debugLogging('GLOBAL INIT DONE', true);
163-
164162
runQueueLock = false;
165163
} catch (error) {
166164
performErrorHandling(error as Error);
@@ -176,16 +174,14 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
176174

177175
const editorInit = async (htmlContainer: HTMLElement | null) => {
178176
try {
179-
debugLogging('INIT EDITOR', true);
180177
// it is possible to run without an editorApp, when the ViewsService or WorkbenchService
181178
if (haveEditorService()) {
182179
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
183180
if (htmlContainer === null || (htmlContainer !== null && htmlContainer.parentElement === null)) {
184-
debugLogging('INIT EDITOR: Unable to create editor. HTML container or the parent is missing.', true);
181+
debugLogging('INIT EDITOR: Unable to create editor. HTML container or the parent is missing.');
185182
} else {
186183
if (editorAppRef.current === undefined && !launchingRef.current) {
187184
launchingRef.current = true;
188-
debugLogging('INIT EDITOR: Creating editor', true);
189185

190186
editorAppRef.current = new EditorApp(editorAppConfigRef.current);
191187
if (editorAppRef.current.isStarting() === true || editorAppRef.current.isDisposing() === true) {
@@ -212,30 +208,30 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
212208

213209
onEditorStartDone?.(editorAppRef.current);
214210
launchingRef.current = false;
211+
debugLogging('INIT EDITOR: Editor start was successful.');
215212
} else {
216-
debugLogging('INIT EDITOR: Editor already created', true);
213+
debugLogging('INIT EDITOR: Editor was already started.');
217214
}
218215
}
219216
} else {
220-
debugLogging('INIT EDITOR: Do nothing: Using ViewsService', true);
217+
debugLogging('INIT EDITOR: Do nothing: Using ViewsService');
221218
}
222-
debugLogging('INIT EDITOR: Done', true);
223219
} catch (error) {
224220
performErrorHandling(error as Error);
225221
}
226222
};
227223

228224
const updateEditorModel = async () => {
229225
try {
230-
debugLogging('UPDATE EDITOR MODEL', true);
231226
if (!launchingRef.current && editorAppRef.current) {
232227
editorAppRef.current.updateCodeResources(editorAppConfigRef.current?.codeResources);
233228
updateModelRelatedRefs();
234-
onConfigProcessed?.(editorAppRef.current);
229+
onConfigProcessed?.({modelUpdated: true, textUpdated: true}, editorAppRef.current);
230+
debugLogging('UPDATE EDITOR MODEL: Model was updated.');
235231
} else {
236-
debugLogging('UPDATE EDITOR MODEL: Not Possible: No editor', true);
232+
onConfigProcessed?.({modelUpdated: false, textUpdated: false}, editorAppRef.current);
233+
debugLogging('UPDATE EDITOR MODEL: No editor is avilable. Model update was not possible.');
237234
}
238-
debugLogging('UPDATE EDITOR MODEL: Done', true);
239235
} catch (error) {
240236
performErrorHandling(error as Error);
241237
}
@@ -251,25 +247,24 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
251247
const disposeEditor = async () => {
252248
try {
253249
// dispose editor if used
254-
debugLogging('DISPOSE', true);
255-
256250
if (editorAppRef.current !== undefined) {
257251
await editorAppRef.current.dispose();
258252
editorAppRef.current = undefined;
259253
onDisposeEditor?.();
254+
debugLogging('DISPOSE: EditorApp was disposed');
260255
} else {
261-
debugLogging('DISPOSE: EditorApp is not disposed', true);
256+
debugLogging('DISPOSE: EditorApp is not disposed');
262257
}
263-
debugLogging('DISPOSE DONE', true);
264-
} catch (error) {
258+
} catch (error) {
265259
performErrorHandling(error as Error);
266260
}
267261
};
268262

269263
const processConfig = () => {
270-
let updateModel = false;
264+
let modelUpdated = false;
265+
let textUpdated = false;
271266
try {
272-
debugLogging('CONFIG PROCESSED', true);
267+
debugLogging('CONFIG PROCESSED: Started');
273268
if (!launchingRef.current && editorAppRef.current) {
274269
if (editorAppConfigRef.current?.codeResources !== undefined) {
275270
const newModifiedCodeUri = editorAppConfigRef.current.codeResources.modified?.uri;
@@ -279,13 +274,14 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
279274
const originalUri = originalCodeUriRef.current !== newOriginalCodeUri ? newOriginalCodeUri : undefined;
280275
// re-create the editor if the URIs have changed
281276
if (modifiedUri !== undefined || originalUri !== undefined) {
282-
updateModel = true;
277+
modelUpdated = true;
283278
} else {
284279
const newModifiedCode = editorAppConfigRef.current.codeResources.modified?.text;
285280
const newOriginalCode = editorAppConfigRef.current.codeResources.original?.text;
286281
const modified = modifiedCodeRef.current !== newModifiedCode ? newModifiedCode : undefined;
287282
const original = originalCodeRef.current !== newOriginalCode ? newOriginalCode : undefined;
288283
if (modified !== undefined || original !== undefined) {
284+
textUpdated = true;
289285
editorAppRef.current.updateCode({ modified, original });
290286
}
291287
}
@@ -301,14 +297,15 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
301297
}
302298
}
303299
}
304-
if (!updateModel) {
305-
onConfigProcessed?.(editorAppRef.current);
300+
// notitfy now if no async model update was necessary
301+
if (!modelUpdated) {
302+
onConfigProcessed?.({modelUpdated, textUpdated}, editorAppRef.current);
306303
}
307-
debugLogging('CONFIG PROCESSED: Done', true);
304+
debugLogging('CONFIG PROCESSED: Done');
308305
} catch (error) {
309306
performErrorHandling(error as Error);
310307
}
311-
return updateModel;
308+
return modelUpdated;
312309
};
313310

314311
useEffect(() => {
@@ -321,66 +318,69 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
321318
editorAppConfigRef.current = editorAppConfig;
322319
// it is possible to run without an editorApp, when the ViewsService or WorkbenchService
323320
if (haveEditorService()) {
324-
const updateModel = processConfig();
325-
if (updateModel) {
326-
addQueue({ id: 'model update', func: updateEditorModel, currentContainer: containerRef.current});
321+
if (editorAppRef.current === undefined) {
322+
addQueue({ id: 'editorInit', func: editorInit, currentContainer: containerRef.current});
327323
} else {
328-
if (editorAppRef.current === undefined) {
329-
addQueue({ id: 'editorInit', func: editorInit, currentContainer: containerRef.current});
330-
} else {
331-
debugLogging('CHECK EDITOR: Editor already created', true);
332-
}
324+
debugLogging('CHECK EDITOR: Editor already created. No queueing necessary.');
333325
}
334326
} else {
335-
debugLogging('INIT EDITOR: Do nothing: Using ViewsService', true);
327+
debugLogging('INIT EDITOR: Do nothing: Using ViewsService');
336328
}
337329
}, [editorAppConfig]);
338330

331+
useEffect(() => {
332+
if (flipReprocessConfigRef.current !== (reprocessConfig === true)) {
333+
debugLogging('REPROCESS CONFIG: Triggered');
334+
const updateModel = processConfig();
335+
if (updateModel) {
336+
addQueue({ id: 'modelUpdate', func: updateEditorModel, currentContainer: containerRef.current});
337+
}
338+
flipReprocessConfigRef.current = !flipReprocessConfigRef.current;
339+
} else {
340+
debugLogging('REPROCESS CONFIG: Denied');
341+
}
342+
}, [reprocessConfig]);
343+
339344
useEffect(() => {
340345
// fast-fail
341346
if (languageClientConfig === undefined) return;
342347

343348
// always try to perform global init. Reason: we cannot ensure order
344349
performGlobalInit();
345350

346-
if (languageClientConfig.enforceDispose === true) {
351+
const lcInitFunc = async () => {
352+
try {
353+
await lcsManager.start();
354+
onLanguageClientsStartDone?.(lcsManager);
355+
} catch (error) {
356+
performErrorHandling(error as Error);
357+
}
358+
};
359+
lcsManager.setLogLevel(languageClientConfig.logLevel);
360+
lcsManager.setConfig(languageClientConfig);
361+
if (!lcsManager.isStarted()) {
362+
addQueue({ id:'lcInit', func: lcInitFunc, currentContainer: containerRef.current });
363+
} else {
364+
debugLogging('INIT LC: Language client is already running. No need to schedule async start.');
365+
}
366+
}, [languageClientConfig]);
367+
368+
useEffect(() => {
369+
if (flipLanguageClientDisposeRef.current !== (enforceLanguageClientDispose === true)) {
347370
const disposeLCFunc = async () => {
348-
// dispose editor if used
349371
try {
350-
debugLogging('DISPOSE LC ENFORCED', true);
351-
352372
await lcsManager.dispose();
353373
onDisposeLanguageClient?.();
354-
355-
debugLogging('DISPOSE LC ENFORCED DONE', true);
356374
} catch (error) {
357375
// The language client may throw an error during disposal, but we want to continue anyway
358376
performErrorHandling(new Error(`Unexpected error occurred during disposal of the language client: ${error}`));
359377
}
360378
};
361-
addQueue({ id:'dispose lc', func: disposeLCFunc, currentContainer: containerRef.current });
379+
addQueue({ id:'lcDispose', func: disposeLCFunc, currentContainer: containerRef.current });
362380
} else {
363-
const lcInitFunc = async () => {
364-
try {
365-
debugLogging('INIT LC', true);
366-
367-
await lcsManager.start();
368-
onLanguageClientsStartDone?.(lcsManager);
369-
debugLogging('INIT LC: Language client started', true);
370-
debugLogging('INIT LC DONE', true);
371-
} catch (error) {
372-
performErrorHandling(error as Error);
373-
}
374-
};
375-
lcsManager.setLogLevel(languageClientConfig.logLevel);
376-
lcsManager.setConfig(languageClientConfig);
377-
if (!lcsManager.isStarted()) {
378-
addQueue({ id:'lcInit', func: lcInitFunc, currentContainer: containerRef.current });
379-
} else {
380-
debugLogging('INIT LC: Language client is already running. No need to schedule async start.', true);
381-
}
381+
debugLogging('ENFORCE DISPOSE LC: Denied');
382382
}
383-
}, [languageClientConfig]);
383+
}, [enforceLanguageClientDispose]);
384384

385385
useEffect(() => {
386386
// this part runs on mount (componentDidMount)

0 commit comments

Comments
 (0)