diff --git a/.github/ISSUE_TEMPLATE/bot-framework-composer-bug.md b/.github/ISSUE_TEMPLATE/bot-framework-composer-bug.md index c36d280efb..b97967cc7c 100644 --- a/.github/ISSUE_TEMPLATE/bot-framework-composer-bug.md +++ b/.github/ISSUE_TEMPLATE/bot-framework-composer-bug.md @@ -22,6 +22,7 @@ assignees: "" +- [ ] Electron distribution - [ ] Chrome - [ ] Safari - [ ] Firefox diff --git a/Composer/packages/lib/code-editor/src/LgEditor.tsx b/Composer/packages/lib/code-editor/src/LgEditor.tsx index 5f2ee06e87..fdf3382667 100644 --- a/Composer/packages/lib/code-editor/src/LgEditor.tsx +++ b/Composer/packages/lib/code-editor/src/LgEditor.tsx @@ -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?: @@ -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 }); } } @@ -68,6 +54,29 @@ export function LgEditor(props: LGLSPEditorProps) { editorId = [projectId, fileId, templateId].join('/'); } + const [editor, setEditor] = useState(); + + 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); @@ -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); } diff --git a/Composer/packages/lib/code-editor/src/LuEditor.tsx b/Composer/packages/lib/code-editor/src/LuEditor.tsx index c0b46e3945..32d8235340 100644 --- a/Composer/packages/lib/code-editor/src/LuEditor.tsx +++ b/Composer/packages/lib/code-editor/src/LuEditor.tsx @@ -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; @@ -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 = (props) => { const monacoRef = useRef(); const options = { @@ -91,6 +78,49 @@ const LuEditor: React.FC = (props) => { editorId = [projectId, fileId, sectionId].join('/'); } + const [editor, setEditor] = useState(); + + 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 }); + + 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; @@ -101,47 +131,7 @@ const LuEditor: React.FC = (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); } diff --git a/Composer/packages/lib/code-editor/src/utils/index.ts b/Composer/packages/lib/code-editor/src/utils/index.ts index cc1d6f71d8..e4a07f3262 100644 --- a/Composer/packages/lib/code-editor/src/utils/index.ts +++ b/Composer/packages/lib/code-editor/src/utils/index.ts @@ -2,3 +2,4 @@ // Licensed under the MIT License. export * from './common'; +export * from './types'; diff --git a/Composer/packages/lib/code-editor/src/utils/lspUtil.ts b/Composer/packages/lib/code-editor/src/utils/lspUtil.ts index db32ffd8dd..5e9c3313a5 100644 --- a/Composer/packages/lib/code-editor/src/utils/lspUtil.ts +++ b/Composer/packages/lib/code-editor/src/utils/lspUtil.ts @@ -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); + } +} diff --git a/Composer/packages/lib/code-editor/src/utils/types.ts b/Composer/packages/lib/code-editor/src/utils/types.ts new file mode 100644 index 0000000000..a4c41e8014 --- /dev/null +++ b/Composer/packages/lib/code-editor/src/utils/types.ts @@ -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; +}