auto-completion
This commit is contained in:
Родитель
fb2505469c
Коммит
973696312a
|
@ -8,6 +8,7 @@ export class Constants {
|
|||
public static readonly CHANNEL_NAME = "DTDL";
|
||||
public static readonly UTF8 = "utf8";
|
||||
public static readonly EMPTY_STRING = "";
|
||||
public static readonly DEFAULT_SEPARATOR = ",";
|
||||
public static readonly COMPLETION_TRIGGER = '"';
|
||||
public static readonly JSON_SPACE = 2;
|
||||
public static readonly DEFAULT_TIMER_MS = 1000;
|
||||
|
|
|
@ -5,33 +5,462 @@ import * as parser from "jsonc-parser";
|
|||
import * as vscode from "vscode";
|
||||
import { Constants } from "../common/constants";
|
||||
import { DigitalTwinConstants } from "./digitalTwinConstants";
|
||||
import { ClassNode, PropertyNode } from "./digitalTwinGraph";
|
||||
import { ClassNode, NodeType, PropertyNode } from "./digitalTwinGraph";
|
||||
import { IntelliSenseUtility, JsonNodeType, ModelContent, PropertyPair } from "./intelliSenseUtility";
|
||||
import { LANGUAGE_CODE } from "./languageCode";
|
||||
|
||||
interface Suggestion {
|
||||
isProperty: boolean;
|
||||
label: string;
|
||||
insertText: string;
|
||||
withSeparator: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completion item provider for DigitalTwin IntelliSense
|
||||
*/
|
||||
export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemProvider {
|
||||
/**
|
||||
* get text for json parser after completion
|
||||
* @param document text document
|
||||
* @param position position
|
||||
*/
|
||||
private static getTextForParse(document: vscode.TextDocument, position: vscode.Position): string {
|
||||
const text: string = document.getText();
|
||||
export class DigitalTwinCompletionItemProvider
|
||||
implements vscode.CompletionItemProvider {
|
||||
private static getDocumentNode(document: vscode.TextDocument, position: vscode.Position): parser.Node {
|
||||
let text: string = document.getText();
|
||||
let textNode: parser.Node = parser.parseTree(text);
|
||||
if (textNode && textNode.type === JsonNodeType.Property) {
|
||||
const offset: number = document.offsetAt(position);
|
||||
text = DigitalTwinCompletionItemProvider.completeText(text, offset);
|
||||
textNode = parser.parseTree(text);
|
||||
}
|
||||
return textNode;
|
||||
}
|
||||
|
||||
private static completeText(text: string, offset: number): string {
|
||||
if (text[offset] === Constants.COMPLETION_TRIGGER) {
|
||||
const edit: parser.Edit = {
|
||||
text = parser.applyEdits(text, [
|
||||
{
|
||||
offset,
|
||||
length: 1,
|
||||
content: Constants.COMPLETION_TRIGGER + DigitalTwinConstants.DEFAULT_DELIMITER,
|
||||
};
|
||||
return parser.applyEdits(text, [edit]);
|
||||
content: Constants.COMPLETION_TRIGGER + Constants.DEFAULT_SEPARATOR,
|
||||
},
|
||||
]);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private static getSuggestion(stringNode: parser.Node): Suggestion[] {
|
||||
const jsonPropertyNode: parser.Node | undefined = stringNode.parent;
|
||||
if (!jsonPropertyNode || jsonPropertyNode.type !== JsonNodeType.Property || !jsonPropertyNode.children) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (stringNode === jsonPropertyNode.children[0]) {
|
||||
const suggestWithValue: boolean = jsonPropertyNode.children.length < 2;
|
||||
return DigitalTwinCompletionItemProvider.suggestKey(jsonPropertyNode, suggestWithValue);
|
||||
} else {
|
||||
return DigitalTwinCompletionItemProvider.suggestValue(jsonPropertyNode);
|
||||
}
|
||||
}
|
||||
|
||||
private static suggestKey(
|
||||
jsonPropertyNode: parser.Node,
|
||||
suggestWithValue: boolean,
|
||||
): Suggestion[] {
|
||||
const existedProperties = DigitalTwinCompletionItemProvider.getExistedProperties(jsonPropertyNode);
|
||||
|
||||
const type = DigitalTwinCompletionItemProvider.tryGetType(jsonPropertyNode);
|
||||
if (!type) {
|
||||
// type is neither defined nor inferable. User should destinate @type first.
|
||||
return DigitalTwinCompletionItemProvider.suggestTypeKey(suggestWithValue);
|
||||
} else {
|
||||
return DigitalTwinCompletionItemProvider.suggestPropertiesCandidates(type, existedProperties, suggestWithValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static getExistedProperties(jsonPropertyNode: parser.Node): string[] {
|
||||
const existedProperties: string[] = [];
|
||||
const parentObjectNode: parser.Node | undefined = jsonPropertyNode.parent;
|
||||
if (!parentObjectNode || parentObjectNode.type !== JsonNodeType.Object || !parentObjectNode.children) {
|
||||
return existedProperties;
|
||||
}
|
||||
|
||||
for (const child of parentObjectNode.children) {
|
||||
if (child === jsonPropertyNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const propertyPair: PropertyPair|undefined = IntelliSenseUtility.parseProperty(child);
|
||||
if (propertyPair) {
|
||||
const jsonPropertyKey = propertyPair.name.value;
|
||||
existedProperties.push(jsonPropertyKey);
|
||||
}
|
||||
}
|
||||
return existedProperties;
|
||||
}
|
||||
|
||||
private static tryGetType(jsonPropertyNode: parser.Node): string|undefined {
|
||||
const parentObjectNode: parser.Node | undefined = jsonPropertyNode.parent;
|
||||
if (!parentObjectNode || parentObjectNode.type !== JsonNodeType.Object || !parentObjectNode.children) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let type: string|undefined = DigitalTwinCompletionItemProvider.tryGetTypeByTypeProperty(parentObjectNode);
|
||||
if (!type) {
|
||||
type = DigitalTwinCompletionItemProvider.tryGetTypeByOuterProperty(jsonPropertyNode);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private static tryGetTypeByTypeProperty(objectNode: parser.Node): string|undefined {
|
||||
if (objectNode.children) {
|
||||
for (const child of objectNode.children) {
|
||||
const property: PropertyPair|undefined = IntelliSenseUtility.parseProperty(child);
|
||||
if (property && property.name.value === DigitalTwinConstants.TYPE) {
|
||||
return property.value.value as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private static tryGetTypeByOuterProperty(jsonPropertyNode: parser.Node): string|undefined {
|
||||
const outerPropertyClassNode: ClassNode|undefined =
|
||||
IntelliSenseUtility.getOuterPropertyClassNode(jsonPropertyNode);
|
||||
|
||||
if (outerPropertyClassNode && !outerPropertyClassNode.isAbstract) {
|
||||
return outerPropertyClassNode.name;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private static suggestTypeKey(suggestWithValue: boolean): Suggestion[] {
|
||||
const suggestions: Suggestion[] = [];
|
||||
|
||||
const dummyTypeNode: PropertyNode = {
|
||||
id: "dummy-type",
|
||||
name: DigitalTwinConstants.TYPE,
|
||||
nodeKind: Constants.EMPTY_STRING,
|
||||
type: Constants.EMPTY_STRING,
|
||||
constraint: {},
|
||||
};
|
||||
suggestions.push({
|
||||
isProperty: true,
|
||||
label: DigitalTwinCompletionItemProvider.formatLabel(dummyTypeNode.name, true),
|
||||
insertText: DigitalTwinCompletionItemProvider.getInsertedTextForProperty(dummyTypeNode, suggestWithValue),
|
||||
withSeparator: suggestWithValue,
|
||||
});
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* get insert text for property
|
||||
* @param propertyNode DigitalTwin property node
|
||||
* @param includeValue identify if insert text includes property value
|
||||
*/
|
||||
private static getInsertedTextForProperty(propertyNode: PropertyNode, includeValue: boolean): string {
|
||||
const name: string = propertyNode.name;
|
||||
if (!includeValue) {
|
||||
return `"${name}"`;
|
||||
}
|
||||
|
||||
// provide value snippet according to property type
|
||||
let value = "$1";
|
||||
if (propertyNode.isPlural && propertyNode.type !== NodeType.RdfLangString) {
|
||||
value = "[$1]";
|
||||
} else if (propertyNode.type) {
|
||||
const typeClassNode: ClassNode|undefined = IntelliSenseUtility.getClassNodeByClassId(propertyNode.type);
|
||||
if (typeClassNode && DigitalTwinCompletionItemProvider.isObjectClass(typeClassNode)) {
|
||||
value = "{$1}";
|
||||
} else {
|
||||
let type: string|undefined = propertyNode.type;
|
||||
|
||||
if (typeClassNode) {
|
||||
type = DigitalTwinCompletionItemProvider.tryGetUniqueTypeByTypeClassNode(typeClassNode);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case NodeType.Boolean:
|
||||
case NodeType.XsdBoolean:
|
||||
value = "${1:false}";
|
||||
break;
|
||||
case NodeType.Integer:
|
||||
case NodeType.XsdInteger:
|
||||
value = "${1:0}";
|
||||
break;
|
||||
case NodeType.String:
|
||||
case NodeType.XsdString:
|
||||
case NodeType.RdfLangString:
|
||||
value = '"$1"';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return `"${name}": ${value}`;
|
||||
}
|
||||
|
||||
private static isObjectClass(typeClassNode: ClassNode): boolean {
|
||||
return (!typeClassNode.isAbstract && !typeClassNode.instances);
|
||||
}
|
||||
|
||||
private static tryGetUniqueTypeByTypeClassNode(typeClassNode: ClassNode): string|undefined {
|
||||
if (!typeClassNode.isAbstract && typeClassNode.instances && typeClassNode.instances.length === 1) {
|
||||
return typeClassNode.instances[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private static suggestPropertiesCandidates(
|
||||
type: string,
|
||||
existedPropertyNames: string[],
|
||||
suggestWithValue: boolean,
|
||||
): Suggestion[] {
|
||||
const suggestions: Suggestion[] = [];
|
||||
|
||||
DigitalTwinCompletionItemProvider.
|
||||
suggestReservedProperties(type, existedPropertyNames, suggestWithValue, suggestions);
|
||||
DigitalTwinCompletionItemProvider.
|
||||
suggestUnreservedProperties(type, existedPropertyNames, suggestWithValue, suggestions);
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
private static suggestUnreservedProperties(
|
||||
type: string,
|
||||
existedPropertyNames: string[],
|
||||
suggestWithValue: boolean,
|
||||
suggestions: Suggestion[],
|
||||
): void {
|
||||
const propertiesCandidates: Set<PropertyNode> =
|
||||
DigitalTwinCompletionItemProvider.getPropertiesCandidates(type, existedPropertyNames);
|
||||
for (const propertyCandidate of propertiesCandidates) {
|
||||
const isRequired: boolean = propertyCandidate.isRequired ? true : false;
|
||||
suggestions.push({
|
||||
isProperty: true,
|
||||
label: DigitalTwinCompletionItemProvider.formatLabel(propertyCandidate.name, isRequired),
|
||||
insertText: DigitalTwinCompletionItemProvider.getInsertedTextForProperty(propertyCandidate, suggestWithValue),
|
||||
withSeparator: suggestWithValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static getPropertiesCandidates(type: string, existedPropertyNames: string[]): Set<PropertyNode> {
|
||||
const propertiesCandidates: Set<PropertyNode> = new Set<PropertyNode>();
|
||||
|
||||
let typeClassNode: ClassNode|undefined = IntelliSenseUtility.getClassNodeByClassName(type);
|
||||
if (!typeClassNode) {
|
||||
// type is not a class name, it can be a property name
|
||||
typeClassNode = IntelliSenseUtility.getClassNodeByPropertyName(type);
|
||||
}
|
||||
|
||||
if (typeClassNode && typeClassNode.properties) {
|
||||
for (const property of typeClassNode.properties) {
|
||||
const propertyNode: PropertyNode|undefined = IntelliSenseUtility.getPropertyNodeById(property);
|
||||
|
||||
if (propertyNode && !existedPropertyNames.includes(propertyNode.name)) {
|
||||
propertiesCandidates.add(propertyNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return propertiesCandidates;
|
||||
}
|
||||
|
||||
private static suggestReservedProperties(
|
||||
type: string,
|
||||
existedPropertyNames: string[],
|
||||
suggestWithValue: boolean,
|
||||
suggestions: Suggestion[],
|
||||
): void {
|
||||
if (existedPropertyNames.includes(DigitalTwinConstants.ID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Suggest @id
|
||||
const isIdRequired: boolean = IntelliSenseUtility.isPartitionNode(type);
|
||||
const dummyIdNode: PropertyNode = {
|
||||
id: "dummy-id",
|
||||
name: DigitalTwinConstants.ID,
|
||||
nodeKind: Constants.EMPTY_STRING,
|
||||
type: Constants.EMPTY_STRING,
|
||||
constraint: {},
|
||||
};
|
||||
suggestions.push({
|
||||
isProperty: true,
|
||||
label: DigitalTwinCompletionItemProvider.formatLabel(dummyIdNode.name, isIdRequired),
|
||||
insertText: DigitalTwinCompletionItemProvider.getInsertedTextForProperty(dummyIdNode, suggestWithValue),
|
||||
withSeparator: suggestWithValue,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* suggest completion item for property value
|
||||
* @param jsonPropertyNode json node
|
||||
* @param position position
|
||||
* @param range overwrite range
|
||||
* @param separator separator after completion text
|
||||
*/
|
||||
private static suggestValue(jsonPropertyNode: parser.Node): Suggestion[] {
|
||||
const suggestions: Suggestion[] = [];
|
||||
if (!jsonPropertyNode.children || jsonPropertyNode.children.length < 1) {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
const propertyObserveName = jsonPropertyNode.children[0].value as string;
|
||||
let possibleValues: string[];
|
||||
if (propertyObserveName === DigitalTwinConstants.TYPE) {
|
||||
possibleValues = DigitalTwinCompletionItemProvider.getPossibleTypeValues(jsonPropertyNode);
|
||||
} else {
|
||||
possibleValues =
|
||||
DigitalTwinCompletionItemProvider.getPossibleNonTypeValues(propertyObserveName, jsonPropertyNode);
|
||||
}
|
||||
|
||||
for (const value of possibleValues) {
|
||||
suggestions.push({
|
||||
isProperty: false,
|
||||
label: value,
|
||||
insertText: `"${value}"`,
|
||||
withSeparator: true,
|
||||
});
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
private static getPossibleTypeValues(jsonPropertyNode: parser.Node): string[] {
|
||||
const valueCandidates: string[] = [];
|
||||
|
||||
const outerPropertyClassNode: ClassNode|undefined =
|
||||
IntelliSenseUtility.getOuterPropertyClassNode(jsonPropertyNode);
|
||||
|
||||
if (!outerPropertyClassNode) {
|
||||
const entryNode = IntelliSenseUtility.getEntryNode();
|
||||
if (entryNode?.constraint.in) {
|
||||
valueCandidates.concat(entryNode?.constraint.in);
|
||||
}
|
||||
return valueCandidates;
|
||||
}
|
||||
|
||||
if (outerPropertyClassNode.isAbstract) {
|
||||
if (outerPropertyClassNode.children) {
|
||||
for (const child of outerPropertyClassNode.children) {
|
||||
const childClassNode = IntelliSenseUtility.getClassNodeByClassId(child);
|
||||
if (childClassNode) {
|
||||
valueCandidates.push(childClassNode.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
valueCandidates.push(outerPropertyClassNode.name);
|
||||
}
|
||||
|
||||
return valueCandidates;
|
||||
}
|
||||
|
||||
private static getPossibleNonTypeValues(propertyObserveName: string, jsonPropertyNode: parser.Node): string[] {
|
||||
let valueCandidates: string[] = [];
|
||||
|
||||
const propertyNode: PropertyNode|undefined =
|
||||
DigitalTwinCompletionItemProvider.getPropertyNodeByPropertyObserveName(propertyObserveName, jsonPropertyNode);
|
||||
if (!propertyNode) {
|
||||
return valueCandidates;
|
||||
}
|
||||
|
||||
if (propertyNode.type) {
|
||||
valueCandidates = DigitalTwinCompletionItemProvider.getAllInstancesOfClass(propertyNode.type);
|
||||
|
||||
if (valueCandidates.length > 0 && propertyNode.constraint.in) {
|
||||
valueCandidates =
|
||||
DigitalTwinCompletionItemProvider.takeIntersectionOfInItems(valueCandidates, propertyNode.constraint.in);
|
||||
}
|
||||
}
|
||||
|
||||
return valueCandidates;
|
||||
}
|
||||
|
||||
private static getPropertyNodeByPropertyObserveName(
|
||||
propertyObserveName: string,
|
||||
jsonPropertyNode: parser.Node,
|
||||
): PropertyNode|undefined {
|
||||
let propertyNode: PropertyNode|undefined;
|
||||
|
||||
const propertyId: string|undefined = IntelliSenseUtility.getIdByName(propertyObserveName);
|
||||
if (propertyId) {
|
||||
propertyNode = IntelliSenseUtility.getPropertyNodeById(propertyId);
|
||||
} else {
|
||||
// property name is shared by multiple property.
|
||||
propertyNode =
|
||||
DigitalTwinCompletionItemProvider.getPropertyNodeByOuterProperty(jsonPropertyNode, propertyObserveName);
|
||||
}
|
||||
|
||||
return propertyNode;
|
||||
}
|
||||
|
||||
private static takeIntersectionOfInItems(instances: string[], inItems: string[]): string[] {
|
||||
const result: string[] = [];
|
||||
for (const possibleValue of inItems) {
|
||||
if (instances.includes(possibleValue)) {
|
||||
result.push(possibleValue);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static getPropertyNodeByOuterProperty(
|
||||
jsonPropertyNode: parser.Node,
|
||||
propertyObserveName: string,
|
||||
): PropertyNode|undefined {
|
||||
const type = DigitalTwinCompletionItemProvider.tryGetType(jsonPropertyNode);
|
||||
if (!type) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const outerPropertyClassNode: ClassNode|undefined = IntelliSenseUtility.getClassNodeByClassName(type);
|
||||
// IntelliSenseUtility.getOuterPropertyClassNode(jsonPropertyNode);
|
||||
|
||||
if (outerPropertyClassNode && outerPropertyClassNode.properties) {
|
||||
for (const propertyId of outerPropertyClassNode.properties) {
|
||||
const propertyNode: PropertyNode|undefined = IntelliSenseUtility.getPropertyNodeById(propertyId);
|
||||
if (propertyNode?.name === propertyObserveName) {
|
||||
return propertyNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private static getAllInstancesOfClass(className: string): string[] {
|
||||
const instances: string[] = [];
|
||||
|
||||
const classes: string[] = [];
|
||||
classes.push(className);
|
||||
|
||||
while (classes.length > 0) {
|
||||
const classId = classes.values().next().value;
|
||||
// classes.delete(classId);
|
||||
|
||||
const typeClassNode: ClassNode|undefined = IntelliSenseUtility.getClassNodeByClassId(classId);
|
||||
if (!typeClassNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeClassNode.children) {
|
||||
for (const child of typeClassNode.children) {
|
||||
classes.push(child);
|
||||
}
|
||||
} else {
|
||||
if (typeClassNode.instances) {
|
||||
for (const instance of typeClassNode.instances) {
|
||||
instances.push(instance);
|
||||
}
|
||||
} else {
|
||||
instances.push(typeClassNode.name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* create completion item
|
||||
* @param label label
|
||||
|
@ -41,17 +470,17 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
|
|||
* @param range overwrite range for completion text
|
||||
*/
|
||||
private static createCompletionItem(
|
||||
label: string,
|
||||
isProperty: boolean,
|
||||
insertText: string,
|
||||
suggestion: Suggestion,
|
||||
position: vscode.Position,
|
||||
range: vscode.Range,
|
||||
separator: string,
|
||||
): vscode.CompletionItem {
|
||||
const insertTextTemp = suggestion.insertText + (suggestion.withSeparator ? separator : Constants.EMPTY_STRING);
|
||||
const completionItem: vscode.CompletionItem = {
|
||||
label,
|
||||
kind: isProperty ? vscode.CompletionItemKind.Property : vscode.CompletionItemKind.Value,
|
||||
insertText: new vscode.SnippetString(insertText),
|
||||
// the start of range should not be before position, otherwise completion item will not be shown
|
||||
label: suggestion.label,
|
||||
kind: suggestion.isProperty ? vscode.CompletionItemKind.Property : vscode.CompletionItemKind.Value,
|
||||
insertText: new vscode.SnippetString(insertTextTemp),
|
||||
// the start of range should be after position, otherwise completion item will not be shown
|
||||
range: new vscode.Range(position, range.end),
|
||||
};
|
||||
if (position.isAfter(range.start)) {
|
||||
|
@ -72,10 +501,17 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
|
|||
node: parser.Node,
|
||||
): vscode.Range {
|
||||
let range: vscode.Range;
|
||||
if (node.type === JsonNodeType.String || node.type === JsonNodeType.Number || node.type === JsonNodeType.Boolean) {
|
||||
if (
|
||||
node.type === JsonNodeType.String ||
|
||||
node.type === JsonNodeType.Number ||
|
||||
node.type === JsonNodeType.Boolean
|
||||
) {
|
||||
range = IntelliSenseUtility.getNodeRange(document, node);
|
||||
} else {
|
||||
const word: string = DigitalTwinCompletionItemProvider.getCurrentWord(document, position);
|
||||
const word: string = DigitalTwinCompletionItemProvider.getCurrentWord(
|
||||
document,
|
||||
position,
|
||||
);
|
||||
const start: number = document.offsetAt(position) - word.length;
|
||||
range = new vscode.Range(document.positionAt(start), position);
|
||||
}
|
||||
|
@ -87,10 +523,16 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
|
|||
* @param document text document
|
||||
* @param position position
|
||||
*/
|
||||
private static getCurrentWord(document: vscode.TextDocument, position: vscode.Position): string {
|
||||
private static getCurrentWord(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
): string {
|
||||
let i: number = position.character - 1;
|
||||
const text: string = document.lineAt(position.line).text;
|
||||
while (i >= 0 && DigitalTwinConstants.WORD_STOP.indexOf(text.charAt(i)) === -1) {
|
||||
while (
|
||||
i >= 0 &&
|
||||
DigitalTwinConstants.WORD_STOP.indexOf(text.charAt(i)) === -1
|
||||
) {
|
||||
i--;
|
||||
}
|
||||
return text.slice(i + 1, position.character);
|
||||
|
@ -116,152 +558,55 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* suggest completion item for property
|
||||
* @param node json node
|
||||
* @param position position
|
||||
* @param range overwrite range
|
||||
* @param includeValue identifiy if includes property value
|
||||
* @param separator separator after completion text
|
||||
*/
|
||||
private static suggestProperty(
|
||||
node: parser.Node,
|
||||
position: vscode.Position,
|
||||
range: vscode.Range,
|
||||
includeValue: boolean,
|
||||
separator: string,
|
||||
): vscode.CompletionItem[] {
|
||||
const completionItems: vscode.CompletionItem[] = [];
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the type of json object node and record existing properties
|
||||
* @param node json node
|
||||
* @param exist existing properties
|
||||
*/
|
||||
private static getObjectType(node: parser.Node, exist: Set<string>): ClassNode | undefined {
|
||||
const parent: parser.Node | undefined = node.parent;
|
||||
if (!parent || parent.type !== JsonNodeType.Object || !parent.children) {
|
||||
return undefined;
|
||||
}
|
||||
let propertyName: string;
|
||||
let objectType: ClassNode | undefined;
|
||||
let propertyPair: PropertyPair | undefined;
|
||||
for (const child of parent.children) {
|
||||
if (child === node) {
|
||||
continue;
|
||||
}
|
||||
propertyPair = IntelliSenseUtility.parseProperty(child);
|
||||
if (!propertyPair || !propertyPair.name.value) {
|
||||
continue;
|
||||
}
|
||||
propertyName = propertyPair.name.value as string;
|
||||
exist.add(propertyName);
|
||||
// get from @type property
|
||||
if (propertyName === DigitalTwinConstants.TYPE) {
|
||||
const propertyValue: parser.Node = propertyPair.value;
|
||||
if (propertyValue.type === JsonNodeType.String) {
|
||||
} else if (propertyValue.type === JsonNodeType.Array && propertyValue.children) {
|
||||
// support semantic type array
|
||||
for (const element of propertyValue.children) {
|
||||
if (element.type === JsonNodeType.String) {
|
||||
const type: string = element.value as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// infer from outer property
|
||||
if (!objectType) {
|
||||
const propertyNode: PropertyNode | undefined = DigitalTwinCompletionItemProvider.getOuterPropertyNode(parent);
|
||||
if (propertyNode) {
|
||||
const classes: ClassNode[] = [];
|
||||
if (classes.length === 1) {
|
||||
objectType = classes[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return objectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* get outer DigitalTwin property node from current node
|
||||
* @param node json node
|
||||
*/
|
||||
private static getOuterPropertyNode(node: parser.Node): PropertyNode | undefined {
|
||||
const propertyPair: PropertyPair | undefined = IntelliSenseUtility.getOuterPropertyPair(node);
|
||||
if (!propertyPair) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* format property label with required information
|
||||
* @param label label
|
||||
* @param required required properties
|
||||
*/
|
||||
private static formatLabel(label: string, required: Set<string>): string {
|
||||
return required.has(label) ? `${label} ${DigitalTwinConstants.REQUIRED_PROPERTY_LABEL}` : label;
|
||||
}
|
||||
|
||||
/**
|
||||
* suggest completion item for property value
|
||||
* @param node json node
|
||||
* @param position position
|
||||
* @param range overwrite range
|
||||
* @param separator separator after completion text
|
||||
*/
|
||||
private static suggestValue(
|
||||
node: parser.Node,
|
||||
position: vscode.Position,
|
||||
range: vscode.Range,
|
||||
separator: string,
|
||||
): vscode.CompletionItem[] {
|
||||
const completionItems: vscode.CompletionItem[] = [];
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* provide completion items
|
||||
* @param document text document
|
||||
* @param position position
|
||||
* @param token cancellation token
|
||||
* @param context completion context
|
||||
*/
|
||||
public provideCompletionItems(
|
||||
private static getCompletionItems(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken,
|
||||
context: vscode.CompletionContext,
|
||||
): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
|
||||
const text: string = DigitalTwinCompletionItemProvider.getTextForParse(document, position);
|
||||
const modelContent: ModelContent | undefined = IntelliSenseUtility.parseDigitalTwinModel(text);
|
||||
if (!modelContent) {
|
||||
return undefined;
|
||||
}
|
||||
if (!IntelliSenseUtility.isGraphInitialized()) {
|
||||
return undefined;
|
||||
}
|
||||
const node: parser.Node | undefined = parser.findNodeAtOffset(modelContent.jsonNode, document.offsetAt(position));
|
||||
if (!node || node.type !== JsonNodeType.String) {
|
||||
return undefined;
|
||||
}
|
||||
const range: vscode.Range = DigitalTwinCompletionItemProvider.evaluateOverwriteRange(document, position, node);
|
||||
stringNode: parser.Node,
|
||||
suggestions: Suggestion[],
|
||||
): vscode.CompletionItem[] {
|
||||
const range: vscode.Range =
|
||||
DigitalTwinCompletionItemProvider.evaluateOverwriteRange(document, position, stringNode);
|
||||
const separator: string = DigitalTwinCompletionItemProvider.evaluateSeparatorAfter(
|
||||
document.getText(),
|
||||
document.offsetAt(range.end),
|
||||
);
|
||||
const parent: parser.Node | undefined = node.parent;
|
||||
if (!parent || parent.type !== JsonNodeType.Property || !parent.children) {
|
||||
|
||||
const completionItems: vscode.CompletionItem[] = suggestions.map((s) =>
|
||||
DigitalTwinCompletionItemProvider.createCompletionItem(s, position, range, separator),
|
||||
);
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* format property label with required information
|
||||
* @param jsonPropertyKey label
|
||||
* @param required required properties
|
||||
*/
|
||||
private static formatLabel(jsonPropertyKey: string, isRequired: boolean): string {
|
||||
const requiredInfo: string = isRequired ? ` ${DigitalTwinConstants.REQUIRED_PROPERTY_LABEL}` : "";
|
||||
return `${jsonPropertyKey}` + requiredInfo;
|
||||
}
|
||||
|
||||
public provideCompletionItems(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
|
||||
const documentNode: parser.Node = DigitalTwinCompletionItemProvider.getDocumentNode(document, position);
|
||||
if (!IntelliSenseUtility.isDigitalTwinDefinition(documentNode)) {
|
||||
return undefined;
|
||||
}
|
||||
if (node === parent.children[0]) {
|
||||
const includeValue: boolean = parent.children.length < 2;
|
||||
return DigitalTwinCompletionItemProvider.suggestProperty(parent, position, range, includeValue, separator);
|
||||
} else {
|
||||
return DigitalTwinCompletionItemProvider.suggestValue(parent, position, range, separator);
|
||||
}
|
||||
|
||||
if (!IntelliSenseUtility.isGraphInitialized()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const stringNode: parser.Node | undefined = parser.findNodeAtOffset(documentNode, document.offsetAt(position));
|
||||
if (!stringNode || stringNode.type !== JsonNodeType.String) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const suggestions: Suggestion[] = DigitalTwinCompletionItemProvider.getSuggestion(stringNode);
|
||||
|
||||
return DigitalTwinCompletionItemProvider.getCompletionItems(document, position, stringNode, suggestions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,16 @@ export interface PropertyNode {
|
|||
constraint: ConstraintNode;
|
||||
}
|
||||
|
||||
export enum NodeType {
|
||||
RdfLangString = "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString",
|
||||
XsdString = "http://www.w3.org/2001/XMLSchema#string",
|
||||
String = "string",
|
||||
XsdBoolean = "http://www.w3.org/2001/XMLSchema#boolean",
|
||||
Boolean = "boolean",
|
||||
XsdInteger = "http://www.w3.org/2001/XMLSchema#integer",
|
||||
Integer = "integer",
|
||||
}
|
||||
|
||||
/**
|
||||
* Constraint node of DigitalTwin graph
|
||||
*/
|
||||
|
@ -186,6 +196,34 @@ export class DigitalTwinGraph {
|
|||
return children;
|
||||
}
|
||||
|
||||
public getClassNodeByClassId(classId: string): ClassNode|undefined {
|
||||
return this.classNodes.get(classId);
|
||||
}
|
||||
|
||||
public getClassNodeByClassName(className: string): ClassNode|undefined {
|
||||
const classId = this.getIdByName(className);
|
||||
if (classId) {
|
||||
return this.classNodes.get(classId);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getIdByName(id: string): string|undefined {
|
||||
return this.dtdlContext.get(id);
|
||||
}
|
||||
|
||||
public getPropertyNodeById(propertyId: string): PropertyNode|undefined {
|
||||
return this.propertyNodes.get(propertyId);
|
||||
}
|
||||
|
||||
public getPropertyNodeByName(propertyName: string): PropertyNode|undefined {
|
||||
const propertyId = this.getIdByName(propertyName);
|
||||
if (propertyId) {
|
||||
return this.propertyNodes.get(propertyId);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* get obverse children of abstract class
|
||||
* @param abstractClass abstract class node
|
||||
|
|
|
@ -224,13 +224,14 @@ export class IntelliSenseUtility {
|
|||
|
||||
/**
|
||||
* parse json node, return property pair
|
||||
* @param node json node
|
||||
* @param jsonPropertyNode json node
|
||||
*/
|
||||
public static parseProperty(node: parser.Node): PropertyPair | undefined {
|
||||
if (node.type !== JsonNodeType.Property || !node.children || node.children.length !== 2) {
|
||||
public static parseProperty(jsonPropertyNode: parser.Node): PropertyPair | undefined {
|
||||
if (jsonPropertyNode.type !== JsonNodeType.Property
|
||||
|| !jsonPropertyNode.children || jsonPropertyNode.children.length !== 2) {
|
||||
return undefined;
|
||||
}
|
||||
return { name: node.children[0], value: node.children[1] };
|
||||
return { name: jsonPropertyNode.children[0], value: jsonPropertyNode.children[1] };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -265,19 +266,88 @@ export class IntelliSenseUtility {
|
|||
|
||||
/**
|
||||
* get outer property pair from current node
|
||||
* @param node json node
|
||||
* @param objectNode json node
|
||||
*/
|
||||
public static getOuterPropertyPair(node: parser.Node): PropertyPair | undefined {
|
||||
if (node.type !== JsonNodeType.Object) {
|
||||
public static getOuterPropertyPair(objectNode: parser.Node): PropertyPair | undefined {
|
||||
if (objectNode.type !== JsonNodeType.Object) {
|
||||
return undefined;
|
||||
}
|
||||
let outerProperty: parser.Node | undefined = node.parent;
|
||||
let outerProperty: parser.Node | undefined = objectNode.parent;
|
||||
if (outerProperty && outerProperty.type === JsonNodeType.Array) {
|
||||
outerProperty = outerProperty.parent;
|
||||
}
|
||||
return outerProperty ? IntelliSenseUtility.parseProperty(outerProperty) : undefined;
|
||||
}
|
||||
|
||||
public static isDigitalTwinDefinition(documentNode: parser.Node): boolean {
|
||||
const contextPath: string[] = [DigitalTwinConstants.CONTEXT];
|
||||
const contextNode: parser.Node | undefined = parser.findNodeAtLocation(documentNode, contextPath);
|
||||
if (contextNode && IntelliSenseUtility.isDigitalTwinContext(contextNode)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static getOuterPropertyClassNode(jsonPropertyNode: parser.Node): ClassNode|undefined {
|
||||
const outerPropertyObserveName = IntelliSenseUtility.getOuterPropertyObserveName(jsonPropertyNode);
|
||||
if (!outerPropertyObserveName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return IntelliSenseUtility.getClassNodeByPropertyName(outerPropertyObserveName);
|
||||
}
|
||||
|
||||
public static getOuterPropertyObserveName(jsonPropertyNode: parser.Node): string|undefined {
|
||||
const parentObjectNode: parser.Node | undefined = jsonPropertyNode.parent;
|
||||
if (!parentObjectNode || parentObjectNode.type !== JsonNodeType.Object || !parentObjectNode.children) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const outerPropertyPair: PropertyPair|undefined = IntelliSenseUtility.getOuterPropertyPair(parentObjectNode);
|
||||
return outerPropertyPair?.name.value;
|
||||
}
|
||||
|
||||
public static getClassNodeByPropertyName(propertyName: string): ClassNode|undefined {
|
||||
const propertyNode: PropertyNode|undefined = IntelliSenseUtility.getPropertyNodeByName(propertyName);
|
||||
if (!propertyNode || !propertyNode.type) {
|
||||
return undefined;
|
||||
}
|
||||
return IntelliSenseUtility.getClassNodeByClassId(propertyNode.type);
|
||||
}
|
||||
|
||||
public static getPropertyNodeByName(propertyName: string): PropertyNode|undefined {
|
||||
return IntelliSenseUtility.graph.getPropertyNodeByName(propertyName);
|
||||
}
|
||||
|
||||
public static getPropertyNodeById(propertyId: string): PropertyNode|undefined {
|
||||
return IntelliSenseUtility.graph.getPropertyNodeById(propertyId);
|
||||
}
|
||||
|
||||
public static getClassNodeByClassId(classDtmi: string): ClassNode|undefined {
|
||||
return IntelliSenseUtility.graph.getClassNodeByClassId(classDtmi);
|
||||
}
|
||||
|
||||
public static getClassNodeByClassName(className: string): ClassNode|undefined {
|
||||
return IntelliSenseUtility.graph.getClassNodeByClassName(className);
|
||||
}
|
||||
|
||||
public static getIdByName(id: string): string|undefined {
|
||||
return IntelliSenseUtility.graph.getIdByName(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if name is a reserved name
|
||||
* @param name name
|
||||
*/
|
||||
public static isReservedName(name: string): boolean {
|
||||
return name.startsWith(DigitalTwinConstants.RESERVED);
|
||||
}
|
||||
|
||||
public static isPartitionNode(propertyName: string): boolean {
|
||||
return propertyName === DigitalTwinConstants.INTERFACE;
|
||||
}
|
||||
|
||||
private static graph: DigitalTwinGraph;
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче