feat(intellisense): add hover feature for intelliSense

This commit is contained in:
Eric Chen 2019-10-13 20:17:44 +08:00
Родитель 8b15446cb1
Коммит fc73d089c8
10 изменённых файлов: 114 добавлений и 40 удалений

Просмотреть файл

@ -26,12 +26,12 @@ export class Utility {
const pattern = new RegExp(keys.join("|"), flag); const pattern = new RegExp(keys.join("|"), flag);
return str.replace(pattern, (matched) => { return str.replace(pattern, (matched) => {
const value: string | undefined = replacement.get(matched); const value: string | undefined = replacement.get(matched);
return value ? value : matched; return value || matched;
}); });
} }
public static async validateModelName(name: string, type: ModelType, folder?: string): Promise<string | undefined> { public static async validateModelName(name: string, type: ModelType, folder?: string): Promise<string | undefined> {
if (!name || name.trim() === "") { if (!name || name.trim() === Constants.EMPTY_STRING) {
return `Name ${Constants.NOT_EMPTY_MSG}`; return `Name ${Constants.NOT_EMPTY_MSG}`;
} }
if (!Constants.MODEL_NAME_REGEX.test(name)) { if (!Constants.MODEL_NAME_REGEX.test(name)) {
@ -48,7 +48,7 @@ export class Utility {
} }
public static validateNotEmpty(name: string, placeholder: string): string | undefined { public static validateNotEmpty(name: string, placeholder: string): string | undefined {
if (!name || name.trim() === "") { if (!name || name.trim() === Constants.EMPTY_STRING) {
return `${placeholder} ${Constants.NOT_EMPTY_MSG}`; return `${placeholder} ${Constants.NOT_EMPTY_MSG}`;
} }
return undefined; return undefined;

Просмотреть файл

@ -25,7 +25,7 @@ export class DeviceModelManager {
} }
public static generateModelFileName(name: string, type: ModelType): string { public static generateModelFileName(name: string, type: ModelType): string {
const fileType: string = type.replace(/\s+/g, "").toLowerCase(); const fileType: string = type.replace(/\s+/g, Constants.EMPTY_STRING).toLowerCase();
return `${name}.${fileType}.json`; return `${name}.${fileType}.json`;
} }

Просмотреть файл

@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved. // Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
import * as parser from "jsonc-parser";
import * as vscode from "vscode"; import * as vscode from "vscode";
import { IntelliSenseUtility } from "./intelliSenseUtility";
export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemProvider { export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemProvider {
public provideCompletionItems( public provideCompletionItems(
@ -10,6 +12,9 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
token: vscode.CancellationToken, token: vscode.CancellationToken,
context: vscode.CompletionContext, context: vscode.CompletionContext,
): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> { ): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
return null; const jsonNode: parser.Node | undefined = IntelliSenseUtility.parseDigitalTwinModel(document);
if (!jsonNode) {
return undefined;
}
} }
} }

Просмотреть файл

@ -6,20 +6,7 @@ import * as vscode from "vscode";
import { Constants } from "../common/constants"; import { Constants } from "../common/constants";
import { DiagnosticMessage, DigitalTwinConstants } from "./digitalTwinConstants"; import { DiagnosticMessage, DigitalTwinConstants } from "./digitalTwinConstants";
import { ClassNode, DigitalTwinGraph, PropertyNode, ValueSchema } from "./digitalTwinGraph"; import { ClassNode, DigitalTwinGraph, PropertyNode, ValueSchema } from "./digitalTwinGraph";
import { IntelliSenseUtility } from "./intelliSenseUtility"; import { IntelliSenseUtility, JsonNodeType, PropertyPair } from "./intelliSenseUtility";
export enum JsonNodeType {
Object = "object",
Array = "array",
String = "string",
Number = "number",
Boolean = "boolean",
}
interface PropertyPair {
name: parser.Node;
value: parser.Node;
}
interface Problem { interface Problem {
offset: number; offset: number;
@ -54,13 +41,6 @@ export class DigitalTwinDiagnosticProvider {
return classes; return classes;
} }
private static parseProperty(jsonNode: parser.Node): PropertyPair | undefined {
if (!jsonNode.children || jsonNode.children.length !== 2) {
return undefined;
}
return { name: jsonNode.children[0], value: jsonNode.children[1] };
}
private static getNamePropertyPair(jsonNode: parser.Node): PropertyPair | undefined { private static getNamePropertyPair(jsonNode: parser.Node): PropertyPair | undefined {
if (jsonNode.type !== JsonNodeType.Object || !jsonNode.children || jsonNode.children.length === 0) { if (jsonNode.type !== JsonNodeType.Object || !jsonNode.children || jsonNode.children.length === 0) {
return undefined; return undefined;
@ -68,7 +48,7 @@ export class DigitalTwinDiagnosticProvider {
let propertyPair: PropertyPair | undefined; let propertyPair: PropertyPair | undefined;
for (const child of jsonNode.children) { for (const child of jsonNode.children) {
propertyPair = DigitalTwinDiagnosticProvider.parseProperty(child); propertyPair = IntelliSenseUtility.parseProperty(child);
if (!propertyPair) { if (!propertyPair) {
continue; continue;
} }
@ -235,7 +215,7 @@ export class DigitalTwinDiagnosticProvider {
let propertyPair: PropertyPair | undefined; let propertyPair: PropertyPair | undefined;
let propertyNode: PropertyNode | undefined; let propertyNode: PropertyNode | undefined;
for (const child of jsonNode.children) { for (const child of jsonNode.children) {
propertyPair = DigitalTwinDiagnosticProvider.parseProperty(child); propertyPair = IntelliSenseUtility.parseProperty(child);
if (!propertyPair) { if (!propertyPair) {
continue; continue;
} }

Просмотреть файл

@ -21,6 +21,7 @@ export interface PropertyNode {
id: string; id: string;
label?: string; label?: string;
isArray?: boolean; isArray?: boolean;
comment?: string;
range?: ClassNode[]; range?: ClassNode[];
constraint?: ConstraintNode; constraint?: ConstraintNode;
} }
@ -56,7 +57,7 @@ enum EdgeType {
Label = "http://www.w3.org/2000/01/rdf-schema#label", Label = "http://www.w3.org/2000/01/rdf-schema#label",
Domain = "http://www.w3.org/2000/01/rdf-schema#domain", Domain = "http://www.w3.org/2000/01/rdf-schema#domain",
SubClassOf = "http://www.w3.org/2000/01/rdf-schema#subClassOf", SubClassOf = "http://www.w3.org/2000/01/rdf-schema#subClassOf",
Comment = "http://www.w3.org/2000/01/rdf-schema#comment", // Comment hasn't been used yet in DTDL Comment = "http://www.w3.org/2000/01/rdf-schema#comment",
} }
export class DigitalTwinGraph { export class DigitalTwinGraph {
@ -73,7 +74,7 @@ export class DigitalTwinGraph {
} }
public static getClassType(classNode: ClassNode): string { public static getClassType(classNode: ClassNode): string {
return classNode.label ? classNode.label : classNode.id; return classNode.label || classNode.id;
} }
public static getValidTypes(propertyNode: PropertyNode): string[] { public static getValidTypes(propertyNode: PropertyNode): string[] {
@ -122,12 +123,14 @@ export class DigitalTwinGraph {
private propertyNodes: Map<string, PropertyNode>; private propertyNodes: Map<string, PropertyNode>;
private contextNodes: Map<string, ContextNode>; private contextNodes: Map<string, ContextNode>;
private constraintNodes: Map<string, ConstraintNode>; private constraintNodes: Map<string, ConstraintNode>;
private reversedIndex: Map<string, string>;
private vocabulary: string; private vocabulary: string;
private constructor() { private constructor() {
this.classNodes = new Map<string, ClassNode>(); this.classNodes = new Map<string, ClassNode>();
this.propertyNodes = new Map<string, PropertyNode>(); this.propertyNodes = new Map<string, PropertyNode>();
this.contextNodes = new Map<string, ContextNode>(); this.contextNodes = new Map<string, ContextNode>();
this.constraintNodes = new Map<string, ConstraintNode>(); this.constraintNodes = new Map<string, ConstraintNode>();
this.reversedIndex = new Map<string, string>();
this.vocabulary = Constants.EMPTY_STRING; this.vocabulary = Constants.EMPTY_STRING;
} }
@ -139,6 +142,10 @@ export class DigitalTwinGraph {
return this.propertyNodes.get(id); return this.propertyNodes.get(id);
} }
public getNodeId(name: string): string {
return this.reversedIndex.get(name) || Constants.EMPTY_STRING;
}
private init(context: vscode.ExtensionContext): void { private init(context: vscode.ExtensionContext): void {
let contextJson; let contextJson;
let constraintJson; let constraintJson;
@ -161,17 +168,21 @@ export class DigitalTwinGraph {
const context = contextJson[DigitalTwinConstants.CONTEXT]; const context = contextJson[DigitalTwinConstants.CONTEXT];
this.vocabulary = context[DigitalTwinConstants.VOCABULARY] as string; this.vocabulary = context[DigitalTwinConstants.VOCABULARY] as string;
let id: string;
for (const key in context) { for (const key in context) {
if (DigitalTwinGraph.isReservedName(key)) { if (DigitalTwinGraph.isReservedName(key)) {
continue; continue;
} }
const value = context[key]; const value = context[key];
if (typeof value === "string") { if (typeof value === "string") {
this.contextNodes.set(this.getId(value), { name: key, isArray: false }); id = this.getId(value);
this.contextNodes.set(id, { name: key, isArray: false });
} else { } else {
const isArray: boolean = DigitalTwinGraph.isArrayType(value); const isArray: boolean = DigitalTwinGraph.isArrayType(value);
this.contextNodes.set(this.getId(value[DigitalTwinConstants.ID] as string), { name: key, isArray }); id = this.getId(value[DigitalTwinConstants.ID] as string);
this.contextNodes.set(id, { name: key, isArray });
} }
this.reversedIndex.set(key, id);
} }
} }
@ -215,6 +226,9 @@ export class DigitalTwinGraph {
case EdgeType.SubClassOf: case EdgeType.SubClassOf:
this.handleEdgeOfSubClassOf(edge); this.handleEdgeOfSubClassOf(edge);
break; break;
case EdgeType.Comment:
this.handleEdgeOfComment(edge);
break;
default: default:
} }
} }
@ -324,6 +338,22 @@ export class DigitalTwinGraph {
baseClassNode.children.push(classNode); baseClassNode.children.push(classNode);
} }
/**
* handle data of Comment edge
* 1. set comment of property node
* @param edge edge data
*/
private handleEdgeOfComment(edge: any): void {
const id: string = edge.SourceNode.Id as string;
const comment: string = edge.TargetNode.Value as string;
// TODO:(erichen): need to check if comment only exist in property
const propertyNode: PropertyNode | undefined = this.propertyNodes.get(id);
if (propertyNode) {
propertyNode.comment = comment;
}
}
private ensureClassNode(id: string): ClassNode { private ensureClassNode(id: string): ClassNode {
let classNode: ClassNode | undefined = this.classNodes.get(id); let classNode: ClassNode | undefined = this.classNodes.get(id);
if (!classNode) { if (!classNode) {

Просмотреть файл

@ -1,16 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved. // Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
import * as parser from "jsonc-parser";
import * as vscode from "vscode"; import * as vscode from "vscode";
import { Constants } from "../common/constants";
import { DigitalTwinConstants } from "./digitalTwinConstants";
import { PropertyNode } from "./digitalTwinGraph";
import { IntelliSenseUtility, PropertyPair } from "./intelliSenseUtility";
export class DigitalTwinHoverProvider implements vscode.HoverProvider { export class DigitalTwinHoverProvider implements vscode.HoverProvider {
private static getContent(propertyName: string): string {
if (!propertyName) {
return Constants.EMPTY_STRING;
}
switch (propertyName) {
case DigitalTwinConstants.ID:
return `An identifier for ${Constants.CHANNEL_NAME} Capability Model or interface`;
case DigitalTwinConstants.TYPE:
return `The type of ${Constants.CHANNEL_NAME} meta model object`;
case DigitalTwinConstants.CONTEXT:
return `The context for ${Constants.CHANNEL_NAME} Capability Model or interface`;
default:
const propertyNode: PropertyNode | undefined = IntelliSenseUtility.getPropertyNode(propertyName);
return propertyNode && propertyNode.comment ? propertyNode.comment : Constants.EMPTY_STRING;
}
}
public provideHover( public provideHover(
document: vscode.TextDocument, document: vscode.TextDocument,
position: vscode.Position, position: vscode.Position,
token: vscode.CancellationToken, token: vscode.CancellationToken,
): vscode.ProviderResult<vscode.Hover> { ): vscode.ProviderResult<vscode.Hover> {
if (!document) { const jsonNode: parser.Node | undefined = IntelliSenseUtility.parseDigitalTwinModel(document);
if (!jsonNode) {
return undefined; return undefined;
} }
const currentNode: parser.Node | undefined = parser.findNodeAtOffset(jsonNode, document.offsetAt(position));
// parent is property node
if (currentNode && currentNode.parent) {
const propertyPair: PropertyPair | undefined = IntelliSenseUtility.parseProperty(currentNode.parent);
if (propertyPair) {
const content: string = DigitalTwinHoverProvider.getContent(propertyPair.name.value as string);
if (content) {
return new vscode.Hover(content, IntelliSenseUtility.getNodeRange(document, currentNode.parent));
}
}
}
return undefined;
} }
} }

Просмотреть файл

@ -4,9 +4,21 @@
import * as parser from "jsonc-parser"; import * as parser from "jsonc-parser";
import * as vscode from "vscode"; import * as vscode from "vscode";
import { DigitalTwinConstants } from "./digitalTwinConstants"; import { DigitalTwinConstants } from "./digitalTwinConstants";
import { JsonNodeType } from "./digitalTwinDiagnosticProvider";
import { DigitalTwinGraph, PropertyNode } from "./digitalTwinGraph"; import { DigitalTwinGraph, PropertyNode } from "./digitalTwinGraph";
export enum JsonNodeType {
Object = "object",
Array = "array",
String = "string",
Number = "number",
Boolean = "boolean",
}
export interface PropertyPair {
name: parser.Node;
value: parser.Node;
}
export class IntelliSenseUtility { export class IntelliSenseUtility {
public static initGraph(context: vscode.ExtensionContext): boolean { public static initGraph(context: vscode.ExtensionContext): boolean {
IntelliSenseUtility.graph = DigitalTwinGraph.getInstance(context); IntelliSenseUtility.graph = DigitalTwinGraph.getInstance(context);
@ -40,8 +52,20 @@ export class IntelliSenseUtility {
return IntelliSenseUtility.getPropertyNode(DigitalTwinConstants.ENTRY_NODE); return IntelliSenseUtility.getPropertyNode(DigitalTwinConstants.ENTRY_NODE);
} }
public static getPropertyNode(propertyName: string): PropertyNode | undefined { public static getPropertyNode(name: string): PropertyNode | undefined {
return IntelliSenseUtility.graph.getPropertyNode(propertyName); const id: string = IntelliSenseUtility.graph.getNodeId(name) || name;
return IntelliSenseUtility.graph.getPropertyNode(id);
}
public static parseProperty(jsonNode: parser.Node): PropertyPair | undefined {
if (!jsonNode.children || jsonNode.children.length !== 2) {
return undefined;
}
return { name: jsonNode.children[0], value: jsonNode.children[1] };
}
public static getNodeRange(document: vscode.TextDocument, node: parser.Node): vscode.Range {
return new vscode.Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
} }
private static graph: DigitalTwinGraph; private static graph: DigitalTwinGraph;

Просмотреть файл

@ -121,7 +121,7 @@ export class ModelRepositoryClient {
if (repoInfo.repositoryId) { if (repoInfo.repositoryId) {
qs.repositoryId = repoInfo.repositoryId; qs.repositoryId = repoInfo.repositoryId;
} }
const accessToken = repoInfo.accessToken ? repoInfo.accessToken : ""; const accessToken = repoInfo.accessToken || Constants.EMPTY_STRING;
const options: request.OptionsWithUri = { const options: request.OptionsWithUri = {
method, method,
uri, uri,

Просмотреть файл

@ -83,7 +83,7 @@ export class ModelRepositoryManager {
accessToken: connection.generateAccessToken(), accessToken: connection.generateAccessToken(),
}; };
// test connection by calling searchModel // test connection by calling searchModel
await ModelRepositoryClient.searchModel(repoInfo, ModelType.Interface, "", 1, null); await ModelRepositoryClient.searchModel(repoInfo, ModelType.Interface, Constants.EMPTY_STRING, 1, null);
if (newConnection) { if (newConnection) {
await CredentialStore.set(Constants.MODEL_REPOSITORY_CONNECTION_KEY, connectionString); await CredentialStore.set(Constants.MODEL_REPOSITORY_CONNECTION_KEY, connectionString);
} }
@ -175,7 +175,7 @@ export class ModelRepositoryManager {
public async searchModel( public async searchModel(
type: ModelType, type: ModelType,
publicRepository: boolean, publicRepository: boolean,
keyword: string = "", keyword: string = Constants.EMPTY_STRING,
pageSize: number = Constants.DEFAULT_PAGE_SIZE, pageSize: number = Constants.DEFAULT_PAGE_SIZE,
continuationToken: string | null = null, continuationToken: string | null = null,
): Promise<SearchResult> { ): Promise<SearchResult> {

Просмотреть файл

@ -68,7 +68,7 @@ export class UI {
} }
items.push({ label: UIConstants.BROWSE_LABEL, description: "" }); items.push({ label: UIConstants.BROWSE_LABEL, description: "" });
const selected: vscode.QuickPickItem = await UI.showQuickPick(label, items); const selected: vscode.QuickPickItem = await UI.showQuickPick(label, items);
return selected.description ? selected.description : await UI.showOpenDialog(label); return selected.description || (await UI.showOpenDialog(label));
} }
public static async showQuickPick(label: string, items: vscode.QuickPickItem[]): Promise<vscode.QuickPickItem> { public static async showQuickPick(label: string, items: vscode.QuickPickItem[]): Promise<vscode.QuickPickItem> {