Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 6 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: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bot-framework-composer-bug.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ assignees: ""

<!-- What browser are you using? -->

- [ ] Electron distribution
- [ ] Chrome
- [ ] Safari
- [ ] Firefox
Expand Down
71 changes: 28 additions & 43 deletions Composer/packages/lib/code-editor/src/LgEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import React from 'react';
import React, { useState, useEffect } from 'react';
import { listen, MessageConnection } from 'vscode-ws-jsonrpc';
import get from 'lodash/get';
import { MonacoServices, MonacoLanguageClient } from 'monaco-languageclient';
import { MonacoServices } from 'monaco-languageclient';
import { EditorDidMount } from '@monaco-editor/react';

import { registerLGLanguage } from './languages';
import { createUrl, createWebSocket, createLanguageClient } from './utils/lspUtil';
import { createUrl, createWebSocket, createLanguageClient, SendRequestWithRetry } from './utils/lspUtil';
import { BaseEditor, BaseEditorProps, OnInit } from './BaseEditor';
import { LGOption } from './utils';

const LG_HELP =
'https://github.com/microsoft/BotBuilder-Samples/blob/master/experimental/language-generation/docs/lg-file-format.md';
const placeholder = `> To learn more about the LG file format, read the documentation at
> ${LG_HELP}`;

export interface LGOption {
projectId?: string;
fileId: string;
templateId?: string;
}

export interface LGLSPEditorProps extends BaseEditorProps {
lgOption?: LGOption;
languageServer?:
Expand All @@ -40,15 +35,6 @@ const defaultLGServer = {
declare global {
interface Window {
monacoServiceInstance: MonacoServices;
monacoLGEditorInstance: MonacoLanguageClient;
}
}

async function initializeDocuments(lgOption: LGOption | undefined, uri: string) {
const languageClient = window.monacoLGEditorInstance;
if (languageClient) {
await languageClient.onReady();
languageClient.sendRequest('initializeDocuments', { uri, lgOption });
}
}

Expand All @@ -68,6 +54,29 @@ export function LgEditor(props: LGLSPEditorProps) {
editorId = [projectId, fileId, templateId].join('/');
}

const [editor, setEditor] = useState<any>();

useEffect(() => {
if (!editor) return;

if (!window.monacoServiceInstance) {
window.monacoServiceInstance = MonacoServices.install(editor as any);
}

const uri = get(editor.getModel(), 'uri._formatted', '');
const url = createUrl(lgServer);
const webSocket: WebSocket = createWebSocket(url);
listen({
webSocket,
onConnection: (connection: MessageConnection) => {
const languageClient = createLanguageClient('LG Language Client', ['botbuilderlg'], connection);
SendRequestWithRetry(languageClient, 'initializeDocuments', { lgOption, uri });
const disposable = languageClient.start();
connection.onClose(() => disposable.dispose());
},
});
}, [editor]);

const onInit: OnInit = (monaco) => {
registerLGLanguage(monaco);

Expand All @@ -77,31 +86,7 @@ export function LgEditor(props: LGLSPEditorProps) {
};

const editorDidMount: EditorDidMount = (_getValue, editor) => {
if (!window.monacoServiceInstance) {
window.monacoServiceInstance = MonacoServices.install(editor as any);
}

if (!window.monacoLGEditorInstance) {
const uri = get(editor.getModel(), 'uri._formatted', '');
const url = createUrl(lgServer);
const webSocket: WebSocket = createWebSocket(url);
listen({
webSocket,
onConnection: (connection: MessageConnection) => {
const languageClient = createLanguageClient('LG Language Client', ['botbuilderlg'], connection);
if (!window.monacoLGEditorInstance) {
window.monacoLGEditorInstance = languageClient;
}
initializeDocuments(lgOption, uri);
const disposable = languageClient.start();
connection.onClose(() => disposable.dispose());
},
});
} else {
const uri = get(editor.getModel(), 'uri._formatted', '');
initializeDocuments(lgOption, uri);
}

setEditor(editor);
if (typeof props.editorDidMount === 'function') {
return props.editorDidMount(_getValue, editor);
}
Expand Down
104 changes: 47 additions & 57 deletions Composer/packages/lib/code-editor/src/LuEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import React, { useRef } from 'react';
import React, { useRef, useState, useEffect } from 'react';
import { listen, MessageConnection } from 'vscode-ws-jsonrpc';
import get from 'lodash/get';
import { MonacoServices, MonacoLanguageClient } from 'monaco-languageclient';
import { EditorDidMount, Monaco } from '@monaco-editor/react';

import { registerLULanguage } from './languages';
import { createUrl, createWebSocket, createLanguageClient } from './utils/lspUtil';
import { createUrl, createWebSocket, createLanguageClient, SendRequestWithRetry } from './utils/lspUtil';
import { BaseEditor, BaseEditorProps, OnInit } from './BaseEditor';
import { defaultPlaceholder, LU_HELP } from './constants';

export interface LUOption {
projectId?: string;
fileId: string;
sectionId?: string;
}
import { LUOption } from './utils';

export interface LULSPEditorProps extends BaseEditorProps {
luOption?: LUOption;
Expand Down Expand Up @@ -61,14 +56,6 @@ function convertEdit(serverEdit: ServerEdit) {
};
}

async function initializeDocuments(luOption: LUOption | undefined, uri: string) {
const languageClient = window.monacoLUEditorInstance;
if (languageClient) {
await languageClient.onReady();
languageClient.sendRequest('initializeDocuments', { uri, luOption });
}
}

const LuEditor: React.FC<LULSPEditorProps> = (props) => {
const monacoRef = useRef<Monaco>();
const options = {
Expand All @@ -91,6 +78,49 @@ const LuEditor: React.FC<LULSPEditorProps> = (props) => {
editorId = [projectId, fileId, sectionId].join('/');
}

const [editor, setEditor] = useState<any>();

useEffect(() => {
if (!editor) return;

if (!window.monacoServiceInstance) {
window.monacoServiceInstance = MonacoServices.install(editor as any);
}

const uri = get(editor.getModel(), 'uri._formatted', '');
const url = createUrl(luServer);
const webSocket: WebSocket = createWebSocket(url);
listen({
webSocket,
onConnection: (connection: MessageConnection) => {
const languageClient = createLanguageClient('LU Language Client', ['lu'], connection);
if (!window.monacoLUEditorInstance) {
window.monacoLUEditorInstance = languageClient;
}

const m = monacoRef.current;
if (m) {
editor.addCommand(m.KeyMod.Shift | m.KeyCode.Enter, function () {
const position = editor.getPosition();
SendRequestWithRetry(languageClient, 'labelingExperienceRequest', { uri, position });
});
}

SendRequestWithRetry(languageClient, 'initializeDocuments', { luOption, uri });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be awaited to ensure the document has been initialized?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhixzhan I am going to go ahead and take this but please respond here when you get back online and create a follow-up PR if this needs to be fixed.

@zhixzhan zhixzhan Jun 4, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked code below SendRequestWithRetry(languageClient, 'initializeDocuments', {, nothing relied on it.

One effect is before initializeDocuments success, in LSP editor interface all information needs come from LSP server ( diagnostics/suggustions...) would be empty, looks like writing in a plain text-editor.


languageClient.onReady().then(() =>
languageClient.onNotification('addUnlabelUtterance', (result) => {
const edits = result.edits.map((e) => {
return convertEdit(e);
});
editor.executeEdits(uri, edits);
})
);
const disposable = languageClient.start();
connection.onClose(() => disposable.dispose());
},
});
});
const onInit: OnInit = (monaco) => {
registerLULanguage(monaco);
monacoRef.current = monaco;
Expand All @@ -101,47 +131,7 @@ const LuEditor: React.FC<LULSPEditorProps> = (props) => {
};

const editorDidMount: EditorDidMount = (_getValue, editor) => {
if (!window.monacoServiceInstance) {
window.monacoServiceInstance = MonacoServices.install(editor as any);
}

if (!window.monacoLUEditorInstance) {
const uri = get(editor.getModel(), 'uri._formatted', '');
const url = createUrl(luServer);
const webSocket: WebSocket = createWebSocket(url);
listen({
webSocket,
onConnection: (connection: MessageConnection) => {
const languageClient = createLanguageClient('LU Language Client', ['lu'], connection);
if (!window.monacoLUEditorInstance) {
window.monacoLUEditorInstance = languageClient;
}

const m = monacoRef.current;
if (m) {
editor.addCommand(m.KeyMod.Shift | m.KeyCode.Enter, function () {
const position = editor.getPosition();
languageClient.sendRequest('labelingExperienceRequest', { uri, position });
});
}
initializeDocuments(luOption, uri);
languageClient.onReady().then(() =>
languageClient.onNotification('addUnlabelUtterance', (result) => {
const edits = result.edits.map((e) => {
return convertEdit(e);
});
editor.executeEdits(uri, edits);
})
);
const disposable = languageClient.start();
connection.onClose(() => disposable.dispose());
},
});
} else {
const uri = get(editor.getModel(), 'uri._formatted', '');
initializeDocuments(luOption, uri);
}

setEditor(editor);
if (typeof props.editorDidMount === 'function') {
return props.editorDidMount(_getValue, editor);
}
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/lib/code-editor/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
// Licensed under the MIT License.

export * from './common';
export * from './types';
24 changes: 24 additions & 0 deletions Composer/packages/lib/code-editor/src/utils/lspUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,27 @@ export function createLanguageClient(
},
});
}

export async function SendRequestWithRetry(
languageClient: MonacoLanguageClient,
method: string,
data: any,
interval = 1000
) {
let sendTimer;

const send = (data) => {
try {
languageClient.sendRequest(method, data);
if (sendTimer) clearInterval(sendTimer);
} catch (error) {
sendTimer = setTimeout(() => {
send(data);
}, interval);
}
};
if (languageClient) {
await languageClient.onReady();
send(data);
}
}
14 changes: 14 additions & 0 deletions Composer/packages/lib/code-editor/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

export interface LGOption {
projectId?: string;
fileId: string;
templateId?: string;
}

export interface LUOption {
projectId?: string;
fileId: string;
sectionId?: string;
}