diff --git a/Composer/packages/lib/code-editor/src/LuEditor.tsx b/Composer/packages/lib/code-editor/src/LuEditor.tsx index 28d2385684..8b584900b8 100644 --- a/Composer/packages/lib/code-editor/src/LuEditor.tsx +++ b/Composer/packages/lib/code-editor/src/LuEditor.tsx @@ -12,7 +12,7 @@ import { registerLULanguage } from './languages'; import { createUrl, createWebSocket, createLanguageClient } from './utils/lspUtil'; import { RichEditor, RichEditorProps } from './RichEditor'; -const LU_HELP = 'https://github.com/microsoft/botframework-cli/blob/master/packages/lu/docs/lu-file-format.md'; +const LU_HELP = 'https://github.com/microsoft/botframework-cli/blob/master/packages/luis/docs/lu-file-format.md'; const placeholder = `> To learn more about the LU file format, read the documentation at > ${LU_HELP}`; diff --git a/Composer/packages/lib/code-editor/src/languages/lu.ts b/Composer/packages/lib/code-editor/src/languages/lu.ts index 37f459beac..d709d8206a 100644 --- a/Composer/packages/lib/code-editor/src/languages/lu.ts +++ b/Composer/packages/lib/code-editor/src/languages/lu.ts @@ -41,7 +41,7 @@ export function registerLULanguage(monaco: typeof monacoEditor) { ], [ // eslint-disable-next-line security/detect-unsafe-regex - /(@\s*)(ml|prebuilt|regex|list|composite|patternany|phraselist)(\s*[\w_]+)/, + /(@\s*)(ml|prebuilt|regex|list|composite|Pattern\.Any|phraseList)(\s*[\w_]+)/, ['intent-indentifier', 'entity-type', 'entity-name'], ], [/(@\s*)(\s*[\w_]+)/, ['intent-indentifier', 'entity-name']], diff --git a/Composer/packages/tools/language-servers/language-understanding/src/LUServer.ts b/Composer/packages/tools/language-servers/language-understanding/src/LUServer.ts index a4b43eef93..b2aab572b0 100644 --- a/Composer/packages/tools/language-servers/language-understanding/src/LUServer.ts +++ b/Composer/packages/tools/language-servers/language-understanding/src/LUServer.ts @@ -60,7 +60,7 @@ export class LUServer { codeActionProvider: false, completionProvider: { resolveProvider: true, - triggerCharacters: ['@', ' ', '{', ':', '['], + triggerCharacters: ['@', ' ', '{', ':', '[', '('], }, foldingRangeProvider: false, documentOnTypeFormattingProvider: { @@ -217,7 +217,6 @@ export class LUServer { const lastLineContent = this.getLastLineContent(params); const edits: TextEdit[] = []; const curLineNumber = params.position.line; - const lineCount = document.lineCount; const luDoc = this.getLUDocument(document); const text = luDoc?.index().content || document.getText(); const lines = text.split('\n'); @@ -229,12 +228,7 @@ export class LUServer { const inputState = this.getInputLineState(params); const pos = params.position; - if ( - key === '\n' && - inputState === 'utterance' && - lastLineContent.trim() !== '-' && - curLineNumber === lineCount - 1 - ) { + if (key === '\n' && inputState === 'utterance' && lastLineContent.trim() !== '-') { const newPos = Position.create(pos.line + 1, 0); const item: TextEdit = TextEdit.insert(newPos, '- '); edits.push(item); @@ -257,21 +251,23 @@ export class LUServer { } } - if ( - key === '\n' && - inputState === 'listEntity' && - lastLineContent.trim() !== '-' && - curLineNumber === lineCount - 1 - ) { - const newPos = Position.create(pos.line + 1, 0); + if (key === '\n' && inputState === 'listEntity' && lastLineContent.trim() !== '-') { + const newPos = Position.create(pos.line, 0); let insertStr = ''; + const indentLevel = this.getIndentLevel(lastLineContent); if (lastLineContent.trim().endsWith(':') || lastLineContent.trim().endsWith('=')) { - insertStr = '\t-'; + insertStr = '\t'.repeat(indentLevel + 1) + '-'; } else { - insertStr = '-'; + insertStr = '\t'.repeat(indentLevel) + '-'; } + const item: TextEdit = TextEdit.insert(newPos, insertStr); edits.push(item); + + //delete redundent \t from autoIndent + const deleteRange = Range.create(pos.line, pos.character - indentLevel, pos.line, pos.character); + const deleteItem: TextEdit = TextEdit.del(deleteRange); + edits.push(deleteItem); } if (lastLineContent.trim() === '-') { @@ -297,10 +293,31 @@ export class LUServer { } } + private getIndentLevel(lineContent: string): number { + if (lineContent.includes('-')) { + const tabStr = lineContent.split('-')[0]; + let numOfTab = 0; + let validIndentStr = true; + tabStr.split('').forEach(u => { + if (u === '\t') { + numOfTab += 1; + } else { + validIndentStr = false; + } + }); + + if (validIndentStr) { + return numOfTab; + } + } + + return 0; + } + private getInputLineState(params: DocumentOnTypeFormattingParams): LineState { const document = this.documents.get(params.textDocument.uri); const position = params.position; - const regListEnity = /^\s*@\s*list\s*.*$/; + const regListEnity = /^\s*@\s*(list|phraseList)\s*.*$/; const regUtterance = /^\s*#.*$/; const regDashLine = /^\s*-.*$/; const mlEntity = /^\s*@\s*ml\s*.*$/; @@ -382,12 +399,14 @@ export class LUServer { .join('\n'); const completionList: CompletionItem[] = []; if (util.isEntityType(curLineContent)) { + const triggerChar = curLineContent[position.character - 1]; + const extraWhiteSpace = triggerChar === '@' ? ' ' : ''; const entityTypes: string[] = EntityTypesObj.EntityType; entityTypes.forEach(entity => { const item = { label: entity, kind: CompletionItemKind.Keyword, - insertText: `${entity}`, + insertText: `${extraWhiteSpace}${entity}`, documentation: `Enitity type: ${entity}`, }; @@ -397,11 +416,13 @@ export class LUServer { if (util.isPrebuiltEntity(curLineContent)) { const prebuiltTypes: string[] = EntityTypesObj.Prebuilt; + const triggerChar = curLineContent[position.character - 1]; + const extraWhiteSpace = triggerChar !== ' ' ? ' ' : ''; prebuiltTypes.forEach(entity => { const item = { label: entity, kind: CompletionItemKind.Keyword, - insertText: `${entity}`, + insertText: `${extraWhiteSpace}${entity}`, documentation: `Prebuilt enitity: ${entity}`, }; @@ -439,6 +460,17 @@ export class LUServer { completionList.push(item2); } + if (util.isPhraseListEntity(curLineContent)) { + const item = { + label: 'interchangeable synonyms?', + kind: CompletionItemKind.Keyword, + insertText: `interchangeable`, + documentation: `interchangeable synonyms as part of the entity definition`, + }; + + completionList.push(item); + } + // completion for entities and patterns, use the text without current line due to usually it will cause parser errors, the luisjson will be undefined let luisJson = await this.extractLUISContent(text); diff --git a/Composer/packages/tools/language-servers/language-understanding/src/entityEnum.ts b/Composer/packages/tools/language-servers/language-understanding/src/entityEnum.ts index c30b5c1039..778e42895f 100644 --- a/Composer/packages/tools/language-servers/language-understanding/src/entityEnum.ts +++ b/Composer/packages/tools/language-servers/language-understanding/src/entityEnum.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. export const EntityTypesObj = { - EntityType: ['ml', 'prebuilt', 'regex', 'list', 'composite', 'patternany', 'phraselist'], + EntityType: ['ml', 'prebuilt', 'regex', 'list', 'composite', 'Pattern.any', 'phraseList'], Prebuilt: [ 'age', 'datetimeV2', diff --git a/Composer/packages/tools/language-servers/language-understanding/src/matchingPattern.ts b/Composer/packages/tools/language-servers/language-understanding/src/matchingPattern.ts index 368768c2b9..8d574d6ffc 100644 --- a/Composer/packages/tools/language-servers/language-understanding/src/matchingPattern.ts +++ b/Composer/packages/tools/language-servers/language-understanding/src/matchingPattern.ts @@ -82,8 +82,8 @@ export function isSeperatedEntityDef(content: string): boolean { } export function isEntityName(content: string): boolean { - const hasNameEntifyDef = /^\s*@\s*(ml|list|regex|prebuilt|composite|patternany|phraselist)\s*([\w._]+|"[\w._\s]+")\s*$/; - const hasTypeEntityDef = /^\s*@\s*(ml|list|regex|prebuilt|composite|patternany|phraselist|intent)\s*$/; + const hasNameEntifyDef = /^\s*@\s*(ml|list|regex|prebuilt|composite|Pattern\.any|phraseList)\s*([\w._]+|"[\w._\s]+")\s*$/; + const hasTypeEntityDef = /^\s*@\s*(ml|list|regex|prebuilt|composite|Pattern\.any|phraseList|intent)\s*$/; const hasNameEntifyDef2 = /^\s*@\s*([\w._]+|"[\w._\s]+")\s*$/; return hasNameEntifyDef.test(content) || (!hasTypeEntityDef.test(content) && hasNameEntifyDef2.test(content)); } @@ -93,6 +93,12 @@ export function isCompositeEntity(content: string): boolean { const compositePatternDef2 = /^\s*@\s*composite\s*[\w]*\s*=\s*\[\s*.*\s*\]\s*$/; return compositePatternDef.test(content) || compositePatternDef2.test(content); } + +export function isPhraseListEntity(content: string): boolean { + const phraseListEntityPatternDef = /^\s*@\s*phraseList\s*[\w]+\s*\(\s*$/; + return phraseListEntityPatternDef.test(content); +} + export function matchedEnterPattern(content: string): boolean { const regexPatternDef = /^\s*-.*{\s*$/; const regexPatternDef2 = /^\s*-.*{\s*}$/; @@ -147,7 +153,7 @@ export const suggestionAllEntityTypes = [ 'patternAnyEntities', 'preBuiltEntities', 'closedLists', - 'phraselists', + 'phraseLists', 'composites', ]; @@ -156,7 +162,7 @@ export const suggestionNoPatternAnyEntityTypes = [ 'regex_entities', 'preBuiltEntities', 'closedLists', - 'phraselists', + 'phraseLists', 'composites', ]; @@ -166,7 +172,7 @@ export const suggestionNoCompositeEntityTypes = [ 'patternAnyEntities', 'preBuiltEntities', 'closedLists', - 'phraselists', + 'phraseLists', ]; export function getSuggestionRoles(luisJson: any, suggestionEntityTypes: string[]): string[] {