Skip to content

Commit

Permalink
fix: Add support of auto suggestion memory variable (#1819)
Browse files Browse the repository at this point in the history
* add support of memory variables

* fix json format

* add fileResolver to handle user defined memory

* add dependency

* inject memoryResolver in

* using delegate in memory variables

* change endWithDotFlag to endWithDot

* add more Luis memory variables

Co-authored-by: Dong Lei <donglei@microsoft.com>
Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com>
  • Loading branch information
3 people committed Jan 13, 2020
1 parent a8c348e commit a51452e
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 5 deletions.
2 changes: 2 additions & 0 deletions Composer/packages/lib/indexers/src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,5 @@ export interface LgFile {
}

export type FileResolver = (id: string) => FileInfo | undefined;

export type MemoryResolver = (id: string) => string[] | undefined;
4 changes: 2 additions & 2 deletions Composer/packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ const wss: ws.Server = new ws.Server({
perMessageDeflate: false,
});

const { fileResolver } = 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);
const server = new LGServer(connection, fileResolver, staticMemoryResolver);
server.start();
}

Expand Down
13 changes: 13 additions & 0 deletions Composer/packages/server/src/services/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ export class BotProjectService {
return BotProjectService.currentBotProject?.files.find(file => file.name === name);
}

public static staticMemoryResolver(name: string): string[] | undefined {
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 {
BotProjectService.initialize();
return BotProjectService.currentBotProject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -43,8 +43,13 @@ export class LGServer {
protected readonly documents = new TextDocuments();
protected readonly pendingValidationRequests = new Map<string, number>();
protected LGDocuments: LGDocument[] = [];
private memoryVariables: Record<string, any> = {};

constructor(protected readonly connection: IConnection, protected readonly resolver?: FileResolver) {
constructor(
protected readonly connection: IConnection,
protected readonly resolver?: FileResolver,
protected readonly memoryResolver?: MemoryResolver
) {
this.documents.listen(this.connection);
this.documents.onDidChangeContent(change => this.validate(change.document));
this.documents.onDidClose(event => {
Expand All @@ -65,6 +70,7 @@ export class LGServer {
codeActionProvider: false,
completionProvider: {
resolveProvider: true,
triggerCharacters: ['.'],
},
hoverProvider: true,
foldingRangeProvider: false,
Expand All @@ -91,6 +97,41 @@ export class LGServer {
this.connection.listen();
}

protected updateObject(propertyList: string[]): void {
let tempVariable: Record<string, any> = this.memoryVariables;
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];
}
}
}

protected updateMemoryVariables(uri: string): void {
if (!this.memoryResolver) {
return;
}

const memoryFileInfo: string[] | undefined = this.memoryResolver(uri);
if (!memoryFileInfo || memoryFileInfo.length === 0) {
return;
}

memoryFileInfo.forEach(variable => {
const propertyList = variable.split('.');
if (propertyList.length >= 1) {
this.updateObject(propertyList);
}
});
}

protected validateLgOption(document: TextDocument, lgOption?: LGOption) {
if (!lgOption) return;

Expand Down Expand Up @@ -281,11 +322,82 @@ export class LGServer {
return state.pop();
}

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 tempVariable) {
tempVariable = tempVariable[property];
} else if (normalizedAnyPattern in tempVariable) {
tempVariable = tempVariable[normalizedAnyPattern];
} else {
tempVariable = {};
}
}

if (!tempVariable || Object.keys(tempVariable).length === 0) {
continue;
}

Object.keys(tempVariable).forEach(e => {
if (e.toString() !== normalizedAnyPattern) {
const item = {
label: e.toString(),
kind: CompletionItemKind.Property,
insertText: e.toString(),
documentation: '',
};
if (!completionList.includes(item)) {
completionList.push(item);
}
}
});
}

return completionList;
}

protected findValidMemoryVariables(params: TextDocumentPositionParams): CompletionItem[] {
const document = this.documents.get(params.textDocument.uri);
if (!document) return [];
const position = params.position;
const range = getRangeAtPosition(document, position);
const wordAtCurRange = document.getText(range);
const endWithDot = 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 || !endWithDot) {
return memoryVariblesRootCompletionList;
}

let propertyList = wordAtCurRange.split('.');
propertyList = propertyList.slice(0, propertyList.length - 1);

const completionList = this.matchingCompletionProperty(propertyList, this.memoryVariables);

return completionList;
}

protected completion(params: TextDocumentPositionParams): Thenable<CompletionList | null> {
const document = this.documents.get(params.textDocument.uri);
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);
Expand All @@ -312,9 +424,21 @@ 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 (endWithDot) {
return Promise.resolve({
isIncomplete: true,
items: completionPropertyResult,
});
} else {
return Promise.resolve({
isIncomplete: true,
items: completionTemplateList.concat(completionFunctionList.concat(completionPropertyResult)),
});
}
} else {
return Promise.resolve(null);
}
Expand Down

0 comments on commit a51452e

Please sign in to comment.