From 647aae07bde3852917bc9684cbb43bd97ebe464f Mon Sep 17 00:00:00 2001 From: Shuai Wang Date: Thu, 2 Jan 2020 20:13:01 +0800 Subject: [PATCH 1/8] add support of memory variables --- .../resources/memoryVariables.json | 13 +++ .../language-generation/src/LGServer.ts | 81 ++++++++++++++++++- .../language-generation/src/utils.ts | 10 +++ 3 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json diff --git a/Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json b/Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json new file mode 100644 index 0000000000..8763fe3755 --- /dev/null +++ b/Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json @@ -0,0 +1,13 @@ +{ + "this" : [{"value": ""}, {"turnCount": ""}], + "turn" : [{ + "dialogEvent": "value" + }], + "dialog" : [ + {"item": ""}, + {"listType": ""} + ], + "#intent": [ + {"score": ""} + ] +} \ No newline at end of file diff --git a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts index bd023cc83d..e7d494a497 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { readFile } from 'fs'; +import * as path from 'path'; import { xhr, getErrorStatusDescription } from 'request-light'; import URI from 'vscode-uri'; @@ -31,6 +32,7 @@ import { convertDiagnostics, isValid, TRange, + loadMemoryVariavles, } from './utils'; // define init methods call from client @@ -43,6 +45,7 @@ export class LGServer { protected readonly documents = new TextDocuments(); protected readonly pendingValidationRequests = new Map(); protected LGDocuments: LGDocument[] = []; // LG Documents Store + private readonly memoryVariables: object; constructor(protected readonly connection: IConnection) { this.documents.listen(this.connection); @@ -65,6 +68,7 @@ export class LGServer { codeActionProvider: false, completionProvider: { resolveProvider: true, + triggerCharacters: ['.'], }, hoverProvider: true, foldingRangeProvider: false, @@ -85,6 +89,10 @@ export class LGServer { } } }); + + const curPath = __dirname; + const targetPath = path.join(curPath, '../resources/memoryVariables.json'); + this.memoryVariables = loadMemoryVariavles(targetPath); } start() { @@ -194,6 +202,12 @@ export class LGServer { * - Hi, @{name}, what's the meaning of 'state' * - Hi---------, @{name}--------, what-------' ------s the meaning of "state" * - , @{<expression>}, <plaintext><single><plaintext>------<double> + * in LG, functions and template can only be valid in expression. + * expression means a valid expression, eg: @{add(1,2)} + * single means single quote string, eg: 'hello world' + * double means double quote string, eg: "hello world" + * including single and double since "@{text}" is a string rather that expression. + * plaintext means text after dash, eg: - Today is monday */ let i = 0; while (i < lineContent.length) { @@ -229,6 +243,52 @@ export class LGServer { return { matched: true, state: finalState }; } + protected findValidMemoryVariables(params: TextDocumentPositionParams): CompletionItem[] | null { + const document = this.documents.get(params.textDocument.uri); + if (!document) return null; + const position = params.position; + const range = getRangeAtPosition(document, position); + const wordAtCurRange = document.getText(range); + + if (!wordAtCurRange || !wordAtCurRange.endsWith('.')) { + return null; + } + + let propertyList = wordAtCurRange.split('.'); + propertyList = propertyList.slice(0, propertyList.length - 1); + let tempVariable: object = this.memoryVariables; + for (const property of propertyList) { + if (property in tempVariable) { + tempVariable = tempVariable[property]; + } else { + tempVariable = {}; + } + } + + if (Object.keys(tempVariable).length === 0) { + return null; + } + + if (tempVariable instanceof Array) { + const completionList: CompletionItem[] = []; + for (const variable of tempVariable) { + Object.keys(variable).forEach(e => { + const item = { + label: e.toString(), + kind: CompletionItemKind.Property, + insertText: e.toString(), + documentation: '', + }; + completionList.push(item); + }); + } + + return completionList; + } + + return null; + } + protected completion(params: TextDocumentPositionParams): Thenable<CompletionList | null> { const document = this.documents.get(params.textDocument.uri); if (!document) { @@ -266,17 +326,32 @@ export class LGServer { }; }); - const completionList = completionTemplateList.concat(completionFunctionList); + const completionVariableList = this.findValidMemoryVariables(params); + const completionRootVariableList = Object.keys(this.memoryVariables).map(e => { + return { + label: e.toString(), + kind: CompletionItemKind.Property, + insertText: e.toString(), + documentation: '', + }; + }); + + let completionList = completionTemplateList.concat(completionFunctionList); + completionList = completionList.concat(completionRootVariableList); const matchResult = this.matchedStates(params); - // TODO: more precise match + if ( matchResult && matchResult.matched && matchResult.state && allowedCompletionStates.includes(matchResult.state.toLowerCase()) ) { - return Promise.resolve({ isIncomplete: true, items: completionList }); + if (completionVariableList !== null && completionVariableList.length > 0) { + return Promise.resolve({ isIncomplete: true, items: completionVariableList }); + } else { + return Promise.resolve({ isIncomplete: true, items: completionList }); + } } else { return Promise.resolve(null); } diff --git a/Composer/packages/tools/language-servers/language-generation/src/utils.ts b/Composer/packages/tools/language-servers/language-generation/src/utils.ts index e9af71c534..678cbfdbd0 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/utils.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/utils.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { readFileSync } from 'fs'; + import { TextDocument, Range, Position, DiagnosticSeverity, Diagnostic } from 'vscode-languageserver-types'; import { LGResource, @@ -132,3 +134,11 @@ export function updateTemplateInContent(content: string, { name, parameters = [] const resource = LGParser.parse(content); return resource.updateTemplate(name, name, parameters, body).toString(); } + +export function loadMemoryVariavles(path: string): object { + const text = readFileSync(path, 'utf-8'); + const varibles = JSON.parse(text); + return varibles; +} + +console.log(__dirname); From 9916955fe787fc1670a815ae2ee9b8a7b0ae9a76 Mon Sep 17 00:00:00 2001 From: Shuai Wang <shuwan@microsoft.com> Date: Fri, 3 Jan 2020 18:59:19 +0800 Subject: [PATCH 2/8] fix json format --- .../resources/memoryVariables.json | 20 +++++------ .../language-generation/src/LGServer.ts | 34 +++++++++++-------- .../language-generation/src/utils.ts | 2 -- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json b/Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json index 8763fe3755..94091a561a 100644 --- a/Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json +++ b/Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json @@ -1,13 +1,13 @@ { - "this" : [{"value": ""}, {"turnCount": ""}], - "turn" : [{ - "dialogEvent": "value" - }], - "dialog" : [ - {"item": ""}, - {"listType": ""} - ], - "#intent": [ + "this" : {"value": "", "turnCount": ""}, + "turn" : { + "dialogEvent": "value", + "recognitionResult": "" + }, + "dialog" : + {"item": "", + "listType": ""} + , + "intent": {"score": ""} - ] } \ No newline at end of file diff --git a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts index e7d494a497..934014a880 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts @@ -265,25 +265,32 @@ export class LGServer { } } - if (Object.keys(tempVariable).length === 0) { + if (!tempVariable || Object.keys(tempVariable).length === 0) { return null; } - if (tempVariable instanceof Array) { + if (tempVariable instanceof Object) { const completionList: CompletionItem[] = []; - for (const variable of tempVariable) { - Object.keys(variable).forEach(e => { - const item = { - label: e.toString(), - kind: CompletionItemKind.Property, - insertText: e.toString(), - documentation: '', - }; - completionList.push(item); - }); - } + Object.keys(tempVariable).forEach(e => { + const item = { + label: e.toString(), + kind: CompletionItemKind.Property, + insertText: e.toString(), + documentation: '', + }; + completionList.push(item); + }); return completionList; + } else if (typeof tempVariable === 'string') { + return [ + { + label: tempVariable, + kind: CompletionItemKind.Property, + insertText: tempVariable, + documentation: '', + }, + ]; } return null; @@ -340,7 +347,6 @@ export class LGServer { completionList = completionList.concat(completionRootVariableList); const matchResult = this.matchedStates(params); - if ( matchResult && matchResult.matched && diff --git a/Composer/packages/tools/language-servers/language-generation/src/utils.ts b/Composer/packages/tools/language-servers/language-generation/src/utils.ts index 678cbfdbd0..8390cb46ce 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/utils.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/utils.ts @@ -140,5 +140,3 @@ export function loadMemoryVariavles(path: string): object { const varibles = JSON.parse(text); return varibles; } - -console.log(__dirname); From 1f8b019702ef44dffe85e030cf498769643c9050 Mon Sep 17 00:00:00 2001 From: Shuai Wang <shuwan@microsoft.com> Date: Tue, 7 Jan 2020 19:01:10 +0800 Subject: [PATCH 3/8] add fileResolver to handle user defined memory --- Composer/packages/lib/indexers/src/type.ts | 2 + Composer/packages/server/src/server.ts | 5 +- .../packages/server/src/services/project.ts | 6 + .../resources/memoryVariables.json | 13 -- .../language-generation/src/LGServer.ts | 146 +++++++++++------- .../src/staticMemoryVariables.ts | 12 ++ .../language-generation/src/utils.ts | 8 - 7 files changed, 113 insertions(+), 79 deletions(-) delete mode 100644 Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json create mode 100644 Composer/packages/tools/language-servers/language-generation/src/staticMemoryVariables.ts diff --git a/Composer/packages/lib/indexers/src/type.ts b/Composer/packages/lib/indexers/src/type.ts index 86c4883e97..cc053ee425 100644 --- a/Composer/packages/lib/indexers/src/type.ts +++ b/Composer/packages/lib/indexers/src/type.ts @@ -77,3 +77,5 @@ export interface LgFile { diagnostics: Diagnostic[]; templates: LgTemplate[]; } + +export type FileResolver = (id: string) => FileInfo | undefined; diff --git a/Composer/packages/server/src/server.ts b/Composer/packages/server/src/server.ts index 2c0e980fa1..defec8b881 100644 --- a/Composer/packages/server/src/server.ts +++ b/Composer/packages/server/src/server.ts @@ -14,6 +14,7 @@ import * as rpc from 'vscode-ws-jsonrpc'; import { IConnection, createConnection } from 'vscode-languageserver'; import { LGServer } from '@bfc/lg-languageserver'; +import { BotProjectService } from './services/project'; import { getAuthProvider } from './router/auth'; import { apiRouter } from './router/api'; import { BASEURL } from './constants'; @@ -113,11 +114,13 @@ const wss: ws.Server = new ws.Server({ perMessageDeflate: false, }); +const { fileResolver } = BotProjectService; + function launchLanguageServer(socket: rpc.IWebSocket) { const reader = new rpc.WebSocketMessageReader(socket); const writer = new rpc.WebSocketMessageWriter(socket); const connection: IConnection = createConnection(reader, writer); - const server = new LGServer(connection); + const server = new LGServer(connection, fileResolver); server.start(); } diff --git a/Composer/packages/server/src/services/project.ts b/Composer/packages/server/src/services/project.ts index fb58e5e822..fe26dc5b28 100644 --- a/Composer/packages/server/src/services/project.ts +++ b/Composer/packages/server/src/services/project.ts @@ -3,6 +3,7 @@ import merge from 'lodash/merge'; import find from 'lodash/find'; +import { FileInfo } from '@bfc/indexers'; import { BotProject } from '../models/bot/botProject'; import { LocationRef } from '../models/bot/interface'; @@ -32,6 +33,11 @@ export class BotProjectService { } } + public static fileResolver(name: string): FileInfo | undefined { + BotProjectService.initialize(); + return BotProjectService.currentBotProject?.files.find(file => file.name === name); + } + public static getCurrentBotProject(): BotProject | undefined { BotProjectService.initialize(); return BotProjectService.currentBotProject; diff --git a/Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json b/Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json deleted file mode 100644 index 94091a561a..0000000000 --- a/Composer/packages/tools/language-servers/language-generation/resources/memoryVariables.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "this" : {"value": "", "turnCount": ""}, - "turn" : { - "dialogEvent": "value", - "recognitionResult": "" - }, - "dialog" : - {"item": "", - "listType": ""} - , - "intent": - {"score": ""} -} \ No newline at end of file diff --git a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts index 934014a880..7b2988022b 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts @@ -2,7 +2,6 @@ // Licensed under the MIT License. import { readFile } from 'fs'; -import * as path from 'path'; import { xhr, getErrorStatusDescription } from 'request-light'; import URI from 'vscode-uri'; @@ -16,10 +15,12 @@ import { CompletionItem, Range, } from 'vscode-languageserver-types'; +import { FileResolver, FileInfo } from '@bfc/indexers'; import { TextDocumentPositionParams } from 'vscode-languageserver-protocol'; import get from 'lodash/get'; import { LGTemplate, Diagnostic as LGDiagnostic } from 'botbuilder-lg'; +import { staticMemoryVariables } from './staticMemoryVariables'; import { buildInfunctionsMap } from './builtinFunctionsMap'; import { getRangeAtPosition, @@ -32,11 +33,11 @@ import { convertDiagnostics, isValid, TRange, - loadMemoryVariavles, } from './utils'; // define init methods call from client const InitializeDocumentsMethodName = 'initializeDocuments'; +const UpdateUserDefinedMemoryVariablesMethod = 'updateUserDefinedMemoryVariables'; const allowedCompletionStates = ['expression']; @@ -45,9 +46,10 @@ export class LGServer { protected readonly documents = new TextDocuments(); protected readonly pendingValidationRequests = new Map<string, number>(); protected LGDocuments: LGDocument[] = []; // LG Documents Store - private readonly memoryVariables: object; + private readonly staticMemoryVariables: object; + private userDefinedMemoryVariables: object = {}; - constructor(protected readonly connection: IConnection) { + constructor(protected readonly connection: IConnection, protected readonly resolver?: FileResolver) { this.documents.listen(this.connection); this.documents.onDidChangeContent(change => this.validate(change.document)); this.documents.onDidClose(event => { @@ -88,17 +90,35 @@ export class LGServer { this.validate(textDocument); } } + + if (UpdateUserDefinedMemoryVariablesMethod === method) { + const { uri } = params; + this.updateUserDefinedMemoryVariables(uri); + } }); - const curPath = __dirname; - const targetPath = path.join(curPath, '../resources/memoryVariables.json'); - this.memoryVariables = loadMemoryVariavles(targetPath); + // load static memory from file + this.staticMemoryVariables = staticMemoryVariables; } start() { this.connection.listen(); } + protected updateUserDefinedMemoryVariables(uri: string): void { + if (!this.resolver) { + return; + } + + const userMemoryFileInfo: FileInfo | undefined = this.resolver(uri); + if (!userMemoryFileInfo) { + return; + } + + const content: string = userMemoryFileInfo.content; + this.userDefinedMemoryVariables = JSON.parse(content); + } + protected getLGDocument(document: TextDocument): LGDocument | undefined { return this.LGDocuments.find(({ uri }) => uri === document.uri); } @@ -196,19 +216,6 @@ export class LGServer { //initialize the root state to plaintext state.push('PlainText'); - - // find out the context state of current cursor, offer precise suggestion and completion etc. - /** - * - Hi, @{name}, what's the meaning of 'state' - * - Hi---------, @{name}--------, what-------' ------s the meaning of "state" - * - <plaintext>, @{<expression>}, <plaintext><single><plaintext>------<double> - * in LG, functions and template can only be valid in expression. - * expression means a valid expression, eg: @{add(1,2)} - * single means single quote string, eg: 'hello world' - * double means double quote string, eg: "hello world" - * including single and double since "@{text}" is a string rather that expression. - * plaintext means text after dash, eg: - Today is monday - */ let i = 0; while (i < lineContent.length) { const char = lineContent.charAt(i); @@ -243,6 +250,51 @@ export class LGServer { return { matched: true, state: finalState }; } + protected matchingCompletionProperty(propertyList: string[], ...objects: object[]): CompletionItem[] { + const completionList: CompletionItem[] = []; + for (const obj of objects) { + let tempVariable = obj; + for (const property of propertyList) { + if (property in obj) { + tempVariable = tempVariable[property]; + } else { + tempVariable = {}; + } + } + + if (!tempVariable || Object.keys(tempVariable).length === 0) { + continue; + } + + if (tempVariable instanceof Object) { + Object.keys(tempVariable).forEach(e => { + const item = { + label: e.toString(), + kind: CompletionItemKind.Property, + insertText: e.toString(), + documentation: '', + }; + if (!completionList.includes(item)) { + completionList.push(item); + } + }); + } else if (typeof tempVariable === 'string') { + const item = { + label: tempVariable, + kind: CompletionItemKind.Property, + insertText: tempVariable, + documentation: '', + }; + + if (!completionList.includes(item)) { + completionList.push(item); + } + } + } + + return completionList; + } + protected findValidMemoryVariables(params: TextDocumentPositionParams): CompletionItem[] | null { const document = this.documents.get(params.textDocument.uri); if (!document) return null; @@ -256,41 +308,14 @@ export class LGServer { let propertyList = wordAtCurRange.split('.'); propertyList = propertyList.slice(0, propertyList.length - 1); - let tempVariable: object = this.memoryVariables; - for (const property of propertyList) { - if (property in tempVariable) { - tempVariable = tempVariable[property]; - } else { - tempVariable = {}; - } - } - - if (!tempVariable || Object.keys(tempVariable).length === 0) { - return null; - } - - if (tempVariable instanceof Object) { - const completionList: CompletionItem[] = []; - Object.keys(tempVariable).forEach(e => { - const item = { - label: e.toString(), - kind: CompletionItemKind.Property, - insertText: e.toString(), - documentation: '', - }; - completionList.push(item); - }); + const completionList = this.matchingCompletionProperty( + propertyList, + this.staticMemoryVariables, + this.userDefinedMemoryVariables + ); + if (completionList.length > 0) { return completionList; - } else if (typeof tempVariable === 'string') { - return [ - { - label: tempVariable, - kind: CompletionItemKind.Property, - insertText: tempVariable, - documentation: '', - }, - ]; } return null; @@ -301,6 +326,11 @@ export class LGServer { if (!document) { return Promise.resolve(null); } + const position = params.position; + const range = getRangeAtPosition(document, position); + const wordAtCurRange = document.getText(range); + const endWithDotFlag = wordAtCurRange.endsWith('.'); + const text = this.getLGDocumentContent(document); let templates: LGTemplate[] = []; @@ -334,7 +364,7 @@ export class LGServer { }); const completionVariableList = this.findValidMemoryVariables(params); - const completionRootVariableList = Object.keys(this.memoryVariables).map(e => { + const completionRootVariableList = Object.keys(this.staticMemoryVariables).map(e => { return { label: e.toString(), kind: CompletionItemKind.Property, @@ -356,11 +386,13 @@ export class LGServer { if (completionVariableList !== null && completionVariableList.length > 0) { return Promise.resolve({ isIncomplete: true, items: completionVariableList }); } else { - return Promise.resolve({ isIncomplete: true, items: completionList }); + if (!endWithDotFlag) { + return Promise.resolve({ isIncomplete: true, items: completionList }); + } } - } else { - return Promise.resolve(null); } + + return Promise.resolve(null); } protected validate(document: TextDocument): void { diff --git a/Composer/packages/tools/language-servers/language-generation/src/staticMemoryVariables.ts b/Composer/packages/tools/language-servers/language-generation/src/staticMemoryVariables.ts new file mode 100644 index 0000000000..86d6d20d91 --- /dev/null +++ b/Composer/packages/tools/language-servers/language-generation/src/staticMemoryVariables.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export const staticMemoryVariables = { + this: { value: '', turnCount: '' }, + turn: { + dialogEvent: 'value', + recognitionResult: '', + }, + dialog: { item: '', listType: '' }, + intent: { score: '' }, +}; diff --git a/Composer/packages/tools/language-servers/language-generation/src/utils.ts b/Composer/packages/tools/language-servers/language-generation/src/utils.ts index 8390cb46ce..e9af71c534 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/utils.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/utils.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { readFileSync } from 'fs'; - import { TextDocument, Range, Position, DiagnosticSeverity, Diagnostic } from 'vscode-languageserver-types'; import { LGResource, @@ -134,9 +132,3 @@ export function updateTemplateInContent(content: string, { name, parameters = [] const resource = LGParser.parse(content); return resource.updateTemplate(name, name, parameters, body).toString(); } - -export function loadMemoryVariavles(path: string): object { - const text = readFileSync(path, 'utf-8'); - const varibles = JSON.parse(text); - return varibles; -} From 3d39057518b7e36436f1d25e12d96874858c334d Mon Sep 17 00:00:00 2001 From: Shuai Wang <shuwan@microsoft.com> Date: Tue, 7 Jan 2020 19:09:39 +0800 Subject: [PATCH 4/8] add dependency --- Composer/package.json | 2 +- .../tools/language-servers/language-generation/package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Composer/package.json b/Composer/package.json index 58cde06e03..dce8d3039a 100644 --- a/Composer/package.json +++ b/Composer/package.json @@ -22,7 +22,7 @@ "scripts": { "build": "node scripts/update.js && node scripts/begin.js && yarn build:prod", "build:prod": "yarn build:dev && yarn build:server && yarn build:client", - "build:dev": "yarn build:tools && yarn build:lib && yarn build:extensions", + "build:dev": "yarn build:lib && yarn build:tools && yarn build:extensions", "build:lib": "yarn workspace @bfc/libs build:all", "build:extensions": "yarn workspace @bfc/extensions build:all", "build:server": "yarn workspace @bfc/server build", diff --git a/Composer/packages/tools/language-servers/language-generation/package.json b/Composer/packages/tools/language-servers/language-generation/package.json index 317bdfa940..b05fb13a4d 100644 --- a/Composer/packages/tools/language-servers/language-generation/package.json +++ b/Composer/packages/tools/language-servers/language-generation/package.json @@ -15,6 +15,7 @@ "lint:typecheck": "tsc --noEmit" }, "dependencies": { + "@bfc/indexers": "*", "botbuilder-lg": "4.7.0-preview.93464", "request-light": "^0.2.2", "vscode-languageserver": "^5.3.0-next" From ba9d13034adee39c4473c71fc39e43429c15a985 Mon Sep 17 00:00:00 2001 From: Shuai Wang <shuwan@microsoft.com> Date: Thu, 9 Jan 2020 23:53:21 +0800 Subject: [PATCH 5/8] inject memoryResolver in --- Composer/packages/server/src/server.ts | 4 ++-- Composer/packages/server/src/services/project.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Composer/packages/server/src/server.ts b/Composer/packages/server/src/server.ts index defec8b881..d163ec65eb 100644 --- a/Composer/packages/server/src/server.ts +++ b/Composer/packages/server/src/server.ts @@ -114,13 +114,13 @@ const wss: ws.Server = new ws.Server({ perMessageDeflate: false, }); -const { fileResolver } = BotProjectService; +const { fileResolver, memoryResolver } = BotProjectService; function launchLanguageServer(socket: rpc.IWebSocket) { const reader = new rpc.WebSocketMessageReader(socket); const writer = new rpc.WebSocketMessageWriter(socket); const connection: IConnection = createConnection(reader, writer); - const server = new LGServer(connection, fileResolver); + const server = new LGServer(connection, fileResolver, memoryResolver); server.start(); } diff --git a/Composer/packages/server/src/services/project.ts b/Composer/packages/server/src/services/project.ts index fe26dc5b28..c5566a1aa6 100644 --- a/Composer/packages/server/src/services/project.ts +++ b/Composer/packages/server/src/services/project.ts @@ -38,6 +38,11 @@ export class BotProjectService { return BotProjectService.currentBotProject?.files.find(file => file.name === name); } + public static memoryResolver(name: string): FileInfo | undefined { + BotProjectService.initialize(); + return BotProjectService.currentBotProject?.files.find(file => file.name === name); + } + public static getCurrentBotProject(): BotProject | undefined { BotProjectService.initialize(); return BotProjectService.currentBotProject; From fef59b1a77e718ed95e2313142ee2ac1cfa159ce Mon Sep 17 00:00:00 2001 From: Shuai Wang <shuwan@microsoft.com> Date: Fri, 10 Jan 2020 17:33:24 +0800 Subject: [PATCH 6/8] using delegate in memory variables --- Composer/packages/lib/indexers/src/type.ts | 2 + Composer/packages/server/src/server.ts | 4 +- .../packages/server/src/services/project.ts | 4 +- .../language-generation/src/LGServer.ts | 73 +++++++++++-------- .../language-generation/src/utils.ts | 14 +++- 5 files changed, 60 insertions(+), 37 deletions(-) diff --git a/Composer/packages/lib/indexers/src/type.ts b/Composer/packages/lib/indexers/src/type.ts index cc053ee425..473da6fea7 100644 --- a/Composer/packages/lib/indexers/src/type.ts +++ b/Composer/packages/lib/indexers/src/type.ts @@ -79,3 +79,5 @@ export interface LgFile { } export type FileResolver = (id: string) => FileInfo | undefined; + +export type MemoryResolver = (id: string) => string[] | undefined; diff --git a/Composer/packages/server/src/server.ts b/Composer/packages/server/src/server.ts index d163ec65eb..4a514bbc22 100644 --- a/Composer/packages/server/src/server.ts +++ b/Composer/packages/server/src/server.ts @@ -114,13 +114,13 @@ const wss: ws.Server = new ws.Server({ perMessageDeflate: false, }); -const { fileResolver, memoryResolver } = BotProjectService; +const { fileResolver, staticMemoryResolver } = BotProjectService; function launchLanguageServer(socket: rpc.IWebSocket) { const reader = new rpc.WebSocketMessageReader(socket); const writer = new rpc.WebSocketMessageWriter(socket); const connection: IConnection = createConnection(reader, writer); - const server = new LGServer(connection, fileResolver, memoryResolver); + const server = new LGServer(connection, fileResolver, staticMemoryResolver); server.start(); } diff --git a/Composer/packages/server/src/services/project.ts b/Composer/packages/server/src/services/project.ts index c5566a1aa6..464555ff59 100644 --- a/Composer/packages/server/src/services/project.ts +++ b/Composer/packages/server/src/services/project.ts @@ -38,9 +38,9 @@ export class BotProjectService { return BotProjectService.currentBotProject?.files.find(file => file.name === name); } - public static memoryResolver(name: string): FileInfo | undefined { + public static staticMemoryResolver(name: string): string[] | undefined { BotProjectService.initialize(); - return BotProjectService.currentBotProject?.files.find(file => file.name === name); + return ['this.value', 'this.turnCount', 'turn.DialogEvent.value', 'intent.score']; } public static getCurrentBotProject(): BotProject | undefined { diff --git a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts index 1d141674ca..f3a9ed1b0c 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts @@ -18,7 +18,7 @@ import { } from 'vscode-languageserver-types'; import { TextDocumentPositionParams } from 'vscode-languageserver-protocol'; import get from 'lodash/get'; -import { lgIndexer, filterTemplateDiagnostics, isValid, FileResolver, FileInfo } from '@bfc/indexers'; +import { lgIndexer, filterTemplateDiagnostics, isValid, FileResolver, FileInfo, MemoryResolver } from '@bfc/indexers'; import { buildInfunctionsMap } from './builtinFunctionsMap'; import { @@ -29,13 +29,13 @@ import { generageDiagnostic, LGOption, LGCursorState, + MemoryVaribleCompletionResult, } from './utils'; const { check, indexOne } = lgIndexer; // define init methods call from client const InitializeDocumentsMethodName = 'initializeDocuments'; -const UpdateUserDefinedMemoryVariablesMethod = 'updateUserDefinedMemoryVariables'; const { ROOT, TEMPLATENAME, TEMPLATEBODY, EXPRESSION, COMMENTS, SINGLE, DOUBLE } = LGCursorState; @@ -49,7 +49,7 @@ export class LGServer { constructor( protected readonly connection: IConnection, protected readonly resolver?: FileResolver, - protected readonly memoryResolver?: FileResolver + protected readonly memoryResolver?: MemoryResolver ) { this.documents.listen(this.connection); this.documents.onDidChangeContent(change => this.validate(change.document)); @@ -91,11 +91,6 @@ export class LGServer { this.validate(textDocument); } } - - if (UpdateUserDefinedMemoryVariablesMethod === method) { - const { uri } = params; - this.updateMemoryVariables(uri); - } }); } @@ -120,21 +115,17 @@ export class LGServer { return; } - const memoryFileInfo: FileInfo | undefined = this.memoryResolver(uri); - if (!memoryFileInfo) { + const memoryFileInfo: string[] | undefined = this.memoryResolver(uri); + if (!memoryFileInfo || memoryFileInfo.length === 0) { return; } - const content: string = memoryFileInfo.content; - if (content) { - const variablesList = content.split(','); - variablesList.forEach(variable => { - const propertyList = variable.split('.'); - if (propertyList.length >= 1) { - this.updateObject(propertyList); - } - }); - } + memoryFileInfo.forEach(variable => { + const propertyList = variable.split('.'); + if (propertyList.length >= 1) { + this.updateObject(propertyList); + } + }); } protected validateLgOption(document: TextDocument, lgOption?: LGOption) { @@ -372,26 +363,34 @@ export class LGServer { return completionList; } - protected findValidMemoryVariables(params: TextDocumentPositionParams): CompletionItem[] | null { + protected findValidMemoryVariables(params: TextDocumentPositionParams): MemoryVaribleCompletionResult { const document = this.documents.get(params.textDocument.uri); - if (!document) return null; + if (!document) return { endWithDotFlag: false, completionList: [] }; const position = params.position; const range = getRangeAtPosition(document, position); const wordAtCurRange = document.getText(range); + const flag = wordAtCurRange.endsWith('.'); + + this.updateMemoryVariables(params.textDocument.uri); + const memoryVariblesRootCompletionList = Object.keys(this.memoryVariables).map(e => { + return { + label: e.toString(), + kind: CompletionItemKind.Property, + insertText: e.toString(), + documentation: '', + }; + }); - if (!wordAtCurRange || !wordAtCurRange.endsWith('.')) { - return null; + if (!wordAtCurRange || !flag) { + return { endWithDotFlag: flag, completionList: memoryVariblesRootCompletionList }; } let propertyList = wordAtCurRange.split('.'); propertyList = propertyList.slice(0, propertyList.length - 1); - const completionList = this.matchingCompletionProperty(propertyList, this.memoryVariables); - if (completionList.length > 0) { - return completionList; - } + const completionList = this.matchingCompletionProperty(propertyList, this.memoryVariables); - return null; + return { endWithDotFlag: true, completionList: completionList }; } protected completion(params: TextDocumentPositionParams): Thenable<CompletionList | null> { @@ -425,14 +424,24 @@ export class LGServer { }; }); + const completionPropertyResult = this.findValidMemoryVariables(params); + const matchedState = this.matchState(params); if (matchedState === EXPRESSION) { - return Promise.resolve({ isIncomplete: true, items: completionTemplateList.concat(completionFunctionList) }); + if (completionPropertyResult.endWithDotFlag) { + return Promise.resolve({ + isIncomplete: true, + items: completionPropertyResult.completionList, + }); + } else { + return Promise.resolve({ + isIncomplete: true, + items: completionTemplateList.concat(completionFunctionList.concat(completionPropertyResult.completionList)), + }); + } } else { return Promise.resolve(null); } - - return Promise.resolve(null); } protected validate(document: TextDocument): void { diff --git a/Composer/packages/tools/language-servers/language-generation/src/utils.ts b/Composer/packages/tools/language-servers/language-generation/src/utils.ts index d6c268eb52..0dcff2ea91 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/utils.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/utils.ts @@ -1,7 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { TextDocument, Range, Position, DiagnosticSeverity, Diagnostic } from 'vscode-languageserver-types'; +import { + TextDocument, + Range, + Position, + DiagnosticSeverity, + Diagnostic, + CompletionItem, +} from 'vscode-languageserver-types'; import { DiagnosticSeverity as LGDiagnosticSeverity, ImportResolver, @@ -125,3 +132,8 @@ export function checkTemplate(template: Template): LGDiagnostic[] { return diagnostic.message.includes('does not have an evaluator') === false; }); } + +export type MemoryVaribleCompletionResult = { + endWithDotFlag: boolean; + completionList: CompletionItem[]; +}; From da47d17b9d06b8ce70f54c2786c67a31cb290713 Mon Sep 17 00:00:00 2001 From: Shuai Wang <shuwan@microsoft.com> Date: Mon, 13 Jan 2020 11:24:21 +0800 Subject: [PATCH 7/8] change endWithDotFlag to endWithDot --- .../language-servers/language-generation/src/LGServer.ts | 8 ++++---- .../language-servers/language-generation/src/utils.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts index f3a9ed1b0c..503b75daf2 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts @@ -365,7 +365,7 @@ export class LGServer { protected findValidMemoryVariables(params: TextDocumentPositionParams): MemoryVaribleCompletionResult { const document = this.documents.get(params.textDocument.uri); - if (!document) return { endWithDotFlag: false, completionList: [] }; + if (!document) return { endWithDot: false, completionList: [] }; const position = params.position; const range = getRangeAtPosition(document, position); const wordAtCurRange = document.getText(range); @@ -382,7 +382,7 @@ export class LGServer { }); if (!wordAtCurRange || !flag) { - return { endWithDotFlag: flag, completionList: memoryVariblesRootCompletionList }; + return { endWithDot: flag, completionList: memoryVariblesRootCompletionList }; } let propertyList = wordAtCurRange.split('.'); @@ -390,7 +390,7 @@ export class LGServer { const completionList = this.matchingCompletionProperty(propertyList, this.memoryVariables); - return { endWithDotFlag: true, completionList: completionList }; + return { endWithDot: true, completionList: completionList }; } protected completion(params: TextDocumentPositionParams): Thenable<CompletionList | null> { @@ -428,7 +428,7 @@ export class LGServer { const matchedState = this.matchState(params); if (matchedState === EXPRESSION) { - if (completionPropertyResult.endWithDotFlag) { + if (completionPropertyResult.endWithDot) { return Promise.resolve({ isIncomplete: true, items: completionPropertyResult.completionList, diff --git a/Composer/packages/tools/language-servers/language-generation/src/utils.ts b/Composer/packages/tools/language-servers/language-generation/src/utils.ts index 0dcff2ea91..6d5d71760e 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/utils.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/utils.ts @@ -134,6 +134,6 @@ export function checkTemplate(template: Template): LGDiagnostic[] { } export type MemoryVaribleCompletionResult = { - endWithDotFlag: boolean; + endWithDot: boolean; completionList: CompletionItem[]; }; From 73f7e3fd43d49250c48f5720947dbb95b444928e Mon Sep 17 00:00:00 2001 From: Shuai Wang <shuwan@microsoft.com> Date: Mon, 13 Jan 2020 16:04:47 +0800 Subject: [PATCH 8/8] add more Luis memory variables --- .../packages/server/src/services/project.ts | 12 ++++- .../language-generation/src/LGServer.ts | 52 +++++++++---------- .../language-generation/src/utils.ts | 14 +---- 3 files changed, 37 insertions(+), 41 deletions(-) diff --git a/Composer/packages/server/src/services/project.ts b/Composer/packages/server/src/services/project.ts index 464555ff59..ee396cd750 100644 --- a/Composer/packages/server/src/services/project.ts +++ b/Composer/packages/server/src/services/project.ts @@ -39,8 +39,16 @@ export class BotProjectService { } public static staticMemoryResolver(name: string): string[] | undefined { - BotProjectService.initialize(); - return ['this.value', 'this.turnCount', 'turn.DialogEvent.value', 'intent.score']; + return [ + 'this.value', + 'this.turnCount', + 'turn.DialogEvent.value', + 'intent.score', + 'turn.recognized.intent', + 'turn.recognized.intents.***.score', + 'turn.recognized.score', + 'turn.recognized.entities', + ]; } public static getCurrentBotProject(): BotProject | undefined { diff --git a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts index 503b75daf2..de4d7784c4 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/LGServer.ts @@ -29,7 +29,6 @@ import { generageDiagnostic, LGOption, LGCursorState, - MemoryVaribleCompletionResult, } from './utils'; const { check, indexOne } = lgIndexer; @@ -100,10 +99,15 @@ export class LGServer { protected updateObject(propertyList: string[]): void { let tempVariable: Record<string, any> = this.memoryVariables; - for (const property of propertyList) { + const antPattern = /\*+/; + const normalizedAnyPattern = '***'; + for (let property of propertyList) { if (property in tempVariable) { tempVariable = tempVariable[property]; } else { + if (antPattern.test(property)) { + property = normalizedAnyPattern; + } tempVariable[property] = {}; tempVariable = tempVariable[property]; } @@ -320,11 +324,14 @@ export class LGServer { protected matchingCompletionProperty(propertyList: string[], ...objects: object[]): CompletionItem[] { const completionList: CompletionItem[] = []; + const normalizedAnyPattern = '***'; for (const obj of objects) { let tempVariable = obj; for (const property of propertyList) { - if (property in obj) { + if (property in tempVariable) { tempVariable = tempVariable[property]; + } else if (normalizedAnyPattern in tempVariable) { + tempVariable = tempVariable[normalizedAnyPattern]; } else { tempVariable = {}; } @@ -334,8 +341,8 @@ export class LGServer { continue; } - if (tempVariable instanceof Object) { - Object.keys(tempVariable).forEach(e => { + Object.keys(tempVariable).forEach(e => { + if (e.toString() !== normalizedAnyPattern) { const item = { label: e.toString(), kind: CompletionItemKind.Property, @@ -345,31 +352,20 @@ export class LGServer { if (!completionList.includes(item)) { completionList.push(item); } - }); - } else if (typeof tempVariable === 'string') { - const item = { - label: tempVariable, - kind: CompletionItemKind.Property, - insertText: tempVariable, - documentation: '', - }; - - if (!completionList.includes(item)) { - completionList.push(item); } - } + }); } return completionList; } - protected findValidMemoryVariables(params: TextDocumentPositionParams): MemoryVaribleCompletionResult { + protected findValidMemoryVariables(params: TextDocumentPositionParams): CompletionItem[] { const document = this.documents.get(params.textDocument.uri); - if (!document) return { endWithDot: false, completionList: [] }; + if (!document) return []; const position = params.position; const range = getRangeAtPosition(document, position); const wordAtCurRange = document.getText(range); - const flag = wordAtCurRange.endsWith('.'); + const endWithDot = wordAtCurRange.endsWith('.'); this.updateMemoryVariables(params.textDocument.uri); const memoryVariblesRootCompletionList = Object.keys(this.memoryVariables).map(e => { @@ -381,8 +377,8 @@ export class LGServer { }; }); - if (!wordAtCurRange || !flag) { - return { endWithDot: flag, completionList: memoryVariblesRootCompletionList }; + if (!wordAtCurRange || !endWithDot) { + return memoryVariblesRootCompletionList; } let propertyList = wordAtCurRange.split('.'); @@ -390,7 +386,7 @@ export class LGServer { const completionList = this.matchingCompletionProperty(propertyList, this.memoryVariables); - return { endWithDot: true, completionList: completionList }; + return completionList; } protected completion(params: TextDocumentPositionParams): Thenable<CompletionList | null> { @@ -398,6 +394,10 @@ export class LGServer { if (!document) { return Promise.resolve(null); } + const position = params.position; + const range = getRangeAtPosition(document, position); + const wordAtCurRange = document.getText(range); + const endWithDot = wordAtCurRange.endsWith('.'); const lgFile = this.getLGDocument(document)?.index(); if (!lgFile) { return Promise.resolve(null); @@ -428,15 +428,15 @@ export class LGServer { const matchedState = this.matchState(params); if (matchedState === EXPRESSION) { - if (completionPropertyResult.endWithDot) { + if (endWithDot) { return Promise.resolve({ isIncomplete: true, - items: completionPropertyResult.completionList, + items: completionPropertyResult, }); } else { return Promise.resolve({ isIncomplete: true, - items: completionTemplateList.concat(completionFunctionList.concat(completionPropertyResult.completionList)), + items: completionTemplateList.concat(completionFunctionList.concat(completionPropertyResult)), }); } } else { diff --git a/Composer/packages/tools/language-servers/language-generation/src/utils.ts b/Composer/packages/tools/language-servers/language-generation/src/utils.ts index 6d5d71760e..d6c268eb52 100644 --- a/Composer/packages/tools/language-servers/language-generation/src/utils.ts +++ b/Composer/packages/tools/language-servers/language-generation/src/utils.ts @@ -1,14 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { - TextDocument, - Range, - Position, - DiagnosticSeverity, - Diagnostic, - CompletionItem, -} from 'vscode-languageserver-types'; +import { TextDocument, Range, Position, DiagnosticSeverity, Diagnostic } from 'vscode-languageserver-types'; import { DiagnosticSeverity as LGDiagnosticSeverity, ImportResolver, @@ -132,8 +125,3 @@ export function checkTemplate(template: Template): LGDiagnostic[] { return diagnostic.message.includes('does not have an evaluator') === false; }); } - -export type MemoryVaribleCompletionResult = { - endWithDot: boolean; - completionList: CompletionItem[]; -};