Skip to content

Commit 0760882

Browse files
committed
feat: working import completions, hover preview
1 parent 2049eae commit 0760882

File tree

4 files changed

+129
-65
lines changed

4 files changed

+129
-65
lines changed

src/extension.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33
import * as vscode from "vscode";
44

55
import { register as registerCompletions } from "./importCompletionProvider";
6+
import { register as registerHover } from "./importHoverProvider";
67

78
// This method is called when your extension is activated
89
// Your extension is activated the very first time the command is executed
910
export function activate(context: vscode.ExtensionContext) {
11+
const workspace = vscode.workspace.workspaceFolders?.[0];
12+
if (!workspace) {
13+
return;
14+
}
15+
1016
// Use the console to output diagnostic information (console.log) and errors (console.error)
1117
// This line of code will only be executed once when your extension is activated
1218
console.log(
@@ -22,10 +28,10 @@ export function activate(context: vscode.ExtensionContext) {
2228
// vscode.window.showInformationMessage('Hello World from Z-Wave JS Config Editor!');
2329
// });
2430

25-
const completions = registerCompletions(context);
26-
if (completions) {
27-
context.subscriptions.push(completions);
28-
}
31+
context.subscriptions.push(
32+
registerCompletions(workspace, context),
33+
registerHover(workspace, context)
34+
);
2935
}
3036

3137
// This method is called when your extension is deactivated

src/importCompletionProvider.ts

Lines changed: 43 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
// The module 'vscode' contains the VS Code extensibility API
22
// Import the module and reference it with the alias vscode in your code below
33
import * as vscode from "vscode";
4+
import { readJSON, resolveTemplateFile } from "./shared";
45

5-
import * as JSON5 from "json5";
6-
7-
const configRoot = "packages/config/config/devices";
8-
9-
const masterTemplateFilename = "templates/master_template.json";
10-
11-
export function register(context: vscode.ExtensionContext) {
12-
const workspace = vscode.workspace.workspaceFolders?.[0];
13-
if (!workspace) {
14-
return;
15-
}
16-
6+
export function register(
7+
workspace: vscode.WorkspaceFolder,
8+
context: vscode.ExtensionContext
9+
) {
1710
return vscode.languages.registerCompletionItemProvider(
1811
{
1912
language: "jsonc",
@@ -28,11 +21,13 @@ export function register(context: vscode.ExtensionContext) {
2821
.lineAt(position.line)
2922
.text.substring(0, position.character)
3023
.trimStart();
24+
const currentLineSuffix = document
25+
.lineAt(position.line)
26+
.text.substring(position.character)
27+
.trimEnd();
3128

3229
const ret: vscode.CompletionItem[] = [];
3330

34-
console.debug("currentLine", currentLinePrefix);
35-
3631
if (
3732
currentLinePrefix.startsWith(`"$import":`) &&
3833
currentLinePrefix.includes(`.json#`)
@@ -42,54 +37,39 @@ export function register(context: vscode.ExtensionContext) {
4237
currentLinePrefix.lastIndexOf('"') + 1
4338
);
4439
const [filename, importSpecifier] = filenameAndImport.split("#");
45-
const actualFilename =
46-
filename === "~/templates/master_template.json"
47-
? masterTemplateFilename
48-
: filename;
40+
const uri = resolveTemplateFile(workspace, filename);
4941

5042
try {
51-
const uri = vscode.Uri.joinPath(
52-
workspace.uri,
53-
configRoot,
54-
actualFilename
55-
);
56-
const fileContentRaw = await vscode.workspace.fs.readFile(uri);
57-
const fileContentString =
58-
Buffer.from(fileContentRaw).toString("utf8");
59-
const fileContent = JSON5.parse(fileContentString);
43+
const fileContent = await readJSON(uri);
6044

6145
const importSuggestions = Object.entries<Record<string, any>>(
6246
fileContent
63-
).map(([key, $import]) => {
64-
const label = $import.$label ?? key;
65-
const detail = $import.$label ? key : undefined;
66-
let documentation = `\`\`\`json
67-
${JSON.stringify(fileContent[key], null, 2)}
47+
).map(
48+
([key, { $label: label = key, $description, ...$import }]) => {
49+
let documentation = `\`\`\`json
50+
${JSON.stringify($import, null, 2)}
6851
\`\`\``;
69-
if ("$description" in $import) {
70-
documentation = $import.$description + "\n\n" + documentation;
71-
}
72-
documentation = `**${label}**\n\n` + documentation;
73-
74-
if (key === "base_enable_disable") {
75-
console.dir({ key, label, detail, documentation });
52+
if ($description) {
53+
documentation = $description + "\n\n" + documentation;
54+
}
55+
56+
const completionItem: vscode.CompletionItem =
57+
new vscode.CompletionItem(
58+
key, // Not sure why, but this CANNOT be $label or the item will disappear
59+
vscode.CompletionItemKind.Snippet
60+
);
61+
completionItem.detail = label;
62+
completionItem.insertText = key;
63+
completionItem.documentation = new vscode.MarkdownString(
64+
documentation
65+
);
66+
completionItem.range = new vscode.Range(
67+
position.translate(0, -importSpecifier.length),
68+
position
69+
);
70+
return completionItem;
7671
}
77-
78-
const completionItem = new vscode.CompletionItem(
79-
label,
80-
vscode.CompletionItemKind.Snippet
81-
);
82-
completionItem.detail = detail;
83-
completionItem.insertText = key;
84-
completionItem.documentation = new vscode.MarkdownString(
85-
documentation
86-
);
87-
completionItem.range = new vscode.Range(
88-
position.translate(0, -importSpecifier.length),
89-
position
90-
);
91-
return completionItem;
92-
});
72+
);
9373

9474
ret.push(...importSuggestions);
9575
} catch (e) {
@@ -119,12 +99,14 @@ ${JSON.stringify(fileContent[key], null, 2)}
11999
importMasterTemplate.sortText = "$import$0master";
120100

121101
// Put a comma after the import
122-
importMasterTemplate.additionalTextEdits = [
123-
vscode.TextEdit.insert(
124-
position.translate(0, importMasterTemplate.sortText.length),
125-
","
126-
),
127-
];
102+
if (!currentLineSuffix.includes(",")) {
103+
importMasterTemplate.additionalTextEdits = [
104+
vscode.TextEdit.insert(
105+
position.translate(0, importMasterTemplate.sortText.length),
106+
","
107+
),
108+
];
109+
}
128110

129111
// Trigger completions again, so an import can be chosen from the selected file
130112
importMasterTemplate.command = {

src/importHoverProvider.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as vscode from "vscode";
2+
3+
import { readJSON, resolveTemplateFile } from "./shared";
4+
5+
export function register(
6+
workspace: vscode.WorkspaceFolder,
7+
context: vscode.ExtensionContext
8+
) {
9+
return vscode.languages.registerHoverProvider(
10+
{
11+
language: "jsonc",
12+
pattern: new vscode.RelativePattern(
13+
workspace.uri,
14+
"packages/config/config/devices/*/*.json"
15+
),
16+
},
17+
{
18+
async provideHover(document, position, token) {
19+
let line = document.lineAt(position.line).text.trim();
20+
if (!line.startsWith(`"$import":`)) {
21+
return undefined;
22+
}
23+
24+
line = line.substring(line.indexOf(":") + 1).trim();
25+
line = line.substring(line.indexOf('"') + 1);
26+
line = line.substring(0, line.indexOf('"'));
27+
if (!line.includes(".json#")) {
28+
return undefined;
29+
}
30+
31+
const [filename, importSpecifier] = line.split("#");
32+
const uri = resolveTemplateFile(workspace, filename);
33+
34+
const fileContent = await readJSON(uri);
35+
const $import = fileContent[importSpecifier];
36+
if (!$import) {
37+
return undefined;
38+
}
39+
40+
const { $label, $description, ...$definition } = $import;
41+
42+
let documentation = `\`\`\`json
43+
${JSON.stringify($definition, null, 2)}
44+
\`\`\``;
45+
if ($description) {
46+
documentation = $description + "\n\n" + documentation;
47+
}
48+
if ($label) {
49+
documentation = `**${$label}**\n\n${documentation}`;
50+
}
51+
52+
return {
53+
contents: [new vscode.MarkdownString(documentation)],
54+
};
55+
},
56+
}
57+
);
58+
}

src/shared.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as vscode from "vscode";
2+
import * as JSON5 from "json5";
3+
4+
export const configRoot = "packages/config/config/devices";
5+
6+
export async function readJSON(uri: vscode.Uri): Promise<Record<string, any>> {
7+
const fileContentRaw = await vscode.workspace.fs.readFile(uri);
8+
const fileContentString = Buffer.from(fileContentRaw).toString("utf8");
9+
return JSON5.parse(fileContentString);
10+
}
11+
12+
export function resolveTemplateFile(
13+
workspace: vscode.WorkspaceFolder,
14+
filename: string
15+
): vscode.Uri {
16+
const actualFilename = filename.replace(/^~\//, configRoot + "/");
17+
return vscode.Uri.joinPath(workspace.uri, actualFilename);
18+
}

0 commit comments

Comments
 (0)