Skip to content

Commit 55317df

Browse files
committed
feat: go to definition, refactoring
1 parent 0760882 commit 55317df

File tree

5 files changed

+210
-32
lines changed

5 files changed

+210
-32
lines changed

src/extension.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as vscode from "vscode";
44

55
import { register as registerCompletions } from "./importCompletionProvider";
66
import { register as registerHover } from "./importHoverProvider";
7+
import { register as registerGoToDefinition } from "./importGoToDefinitionProvider";
78

89
// This method is called when your extension is activated
910
// Your extension is activated the very first time the command is executed
@@ -13,11 +14,11 @@ export function activate(context: vscode.ExtensionContext) {
1314
return;
1415
}
1516

16-
// Use the console to output diagnostic information (console.log) and errors (console.error)
17-
// This line of code will only be executed once when your extension is activated
18-
console.log(
19-
'Congratulations, your extension "zwave-js-config-editor" is now active!'
20-
);
17+
// // Use the console to output diagnostic information (console.log) and errors (console.error)
18+
// // This line of code will only be executed once when your extension is activated
19+
// console.log(
20+
// 'Congratulations, your extension "zwave-js-config-editor" is now active!'
21+
// );
2122

2223
// // The command has been defined in the package.json file
2324
// // Now provide the implementation of the command with registerCommand
@@ -30,7 +31,8 @@ export function activate(context: vscode.ExtensionContext) {
3031

3132
context.subscriptions.push(
3233
registerCompletions(workspace, context),
33-
registerHover(workspace, context)
34+
registerHover(workspace, context),
35+
registerGoToDefinition(workspace, context)
3436
);
3537
}
3638

src/importCompletionProvider.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
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";
4+
import {
5+
getImportSpecifierFromLine,
6+
readJSON,
7+
resolveTemplateFile,
8+
} from "./shared";
59

610
export function register(
711
workspace: vscode.WorkspaceFolder,
@@ -28,16 +32,11 @@ export function register(
2832

2933
const ret: vscode.CompletionItem[] = [];
3034

31-
if (
32-
currentLinePrefix.startsWith(`"$import":`) &&
33-
currentLinePrefix.includes(`.json#`)
34-
) {
35+
const imp = getImportSpecifierFromLine(currentLinePrefix);
36+
37+
if (imp) {
3538
// We're in the import specifier, return valid imports
36-
const filenameAndImport = currentLinePrefix.substring(
37-
currentLinePrefix.lastIndexOf('"') + 1
38-
);
39-
const [filename, importSpecifier] = filenameAndImport.split("#");
40-
const uri = resolveTemplateFile(workspace, filename);
39+
const uri = resolveTemplateFile(workspace, imp.filename);
4140

4241
try {
4342
const fileContent = await readJSON(uri);
@@ -64,7 +63,7 @@ ${JSON.stringify($import, null, 2)}
6463
documentation
6564
);
6665
completionItem.range = new vscode.Range(
67-
position.translate(0, -importSpecifier.length),
66+
position.translate(0, -imp.importSpecifier.length),
6867
position
6968
);
7069
return completionItem;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as vscode from "vscode";
2+
3+
import {
4+
getBlockRange,
5+
getImportSpecifierFromLine,
6+
readJSON,
7+
readTextFile,
8+
resolveTemplate,
9+
resolveTemplateFile,
10+
} from "./shared";
11+
12+
export function register(
13+
workspace: vscode.WorkspaceFolder,
14+
context: vscode.ExtensionContext
15+
) {
16+
return vscode.languages.registerDefinitionProvider(
17+
{
18+
language: "jsonc",
19+
pattern: new vscode.RelativePattern(
20+
workspace.uri,
21+
"packages/config/config/devices/*/*.json"
22+
),
23+
},
24+
{
25+
async provideDefinition(document, position, token) {
26+
// Provide definitions for "$import" directives
27+
const line = document.lineAt(position.line).text;
28+
29+
const imp = getImportSpecifierFromLine(line);
30+
if (!imp) {
31+
return undefined;
32+
}
33+
34+
const file = resolveTemplateFile(workspace, imp.filename);
35+
if (!file) {
36+
return undefined;
37+
}
38+
39+
// Find the template in the target file. For now do a simple text search
40+
// TODO: Figure out if we can use the JSON parser to find the template
41+
const fileContent = await readTextFile(file);
42+
const fileLines = fileContent.split("\n");
43+
const templateLineIndex = fileLines.findIndex((l) => {
44+
return l.trimStart().startsWith(`"${imp.importSpecifier}":`);
45+
});
46+
const targetRange = getBlockRange(
47+
fileContent,
48+
new vscode.Position(templateLineIndex + 1, 0)
49+
);
50+
51+
// Underline the whole import specifier
52+
const startIndex =
53+
line.substring(0, position.character).lastIndexOf('"') + 1;
54+
let endIndex =
55+
line.substring(position.character).indexOf('"') + position.character;
56+
if (endIndex < position.character) {
57+
// No closing " found, select until the end of the line
58+
endIndex = line.length;
59+
}
60+
61+
const link: vscode.LocationLink = {
62+
originSelectionRange: new vscode.Range(
63+
position.line,
64+
startIndex,
65+
position.line,
66+
endIndex
67+
),
68+
targetUri: file,
69+
targetRange,
70+
// targetRange: new vscode.Range(
71+
// templateLineIndex,
72+
// 0,
73+
// templateLineIndex,
74+
// fileLines[templateLineIndex].length
75+
// ),
76+
};
77+
78+
return [link];
79+
},
80+
}
81+
);
82+
}

src/importHoverProvider.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import * as vscode from "vscode";
22

3-
import { readJSON, resolveTemplateFile } from "./shared";
3+
import {
4+
getImportSpecifierFromLine,
5+
readJSON,
6+
resolveTemplate,
7+
resolveTemplateFile,
8+
} from "./shared";
49

510
export function register(
611
workspace: vscode.WorkspaceFolder,
@@ -17,27 +22,22 @@ export function register(
1722
{
1823
async provideHover(document, position, token) {
1924
let line = document.lineAt(position.line).text.trim();
20-
if (!line.startsWith(`"$import":`)) {
21-
return undefined;
22-
}
2325

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#")) {
26+
const imp = getImportSpecifierFromLine(line);
27+
if (!imp) {
2828
return undefined;
2929
}
3030

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) {
31+
const template = await resolveTemplate(
32+
workspace,
33+
imp.filename,
34+
imp.importSpecifier
35+
);
36+
if (!template) {
3737
return undefined;
3838
}
3939

40-
const { $label, $description, ...$definition } = $import;
40+
const { $label, $description, ...$definition } = template;
4141

4242
let documentation = `\`\`\`json
4343
${JSON.stringify($definition, null, 2)}

src/shared.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,105 @@ export async function readJSON(uri: vscode.Uri): Promise<Record<string, any>> {
99
return JSON5.parse(fileContentString);
1010
}
1111

12+
export async function readTextFile(uri: vscode.Uri): Promise<string> {
13+
const fileContentRaw = await vscode.workspace.fs.readFile(uri);
14+
return Buffer.from(fileContentRaw).toString("utf8");
15+
}
16+
1217
export function resolveTemplateFile(
1318
workspace: vscode.WorkspaceFolder,
1419
filename: string
1520
): vscode.Uri {
1621
const actualFilename = filename.replace(/^~\//, configRoot + "/");
1722
return vscode.Uri.joinPath(workspace.uri, actualFilename);
1823
}
24+
25+
export function getImportSpecifierFromLine(line: string):
26+
| {
27+
filename: string;
28+
importSpecifier: string;
29+
}
30+
| undefined {
31+
line = line.trim();
32+
if (!line.startsWith(`"$import":`)) {
33+
return undefined;
34+
}
35+
36+
line = line.substring(line.indexOf(":") + 1).trim();
37+
line = line.substring(line.indexOf('"') + 1);
38+
if (line.includes('"')) {
39+
line = line.substring(0, line.indexOf('"'));
40+
}
41+
42+
if (!line.includes(".json#")) {
43+
return undefined;
44+
}
45+
46+
const [filename, importSpecifier] = line.split("#");
47+
return { filename, importSpecifier };
48+
}
49+
50+
export async function resolveTemplate(
51+
workspace: vscode.WorkspaceFolder,
52+
filename: string,
53+
importSpecifier: string
54+
): Promise<Record<string, any> | undefined> {
55+
const uri = resolveTemplateFile(workspace, filename);
56+
const fileContent = await readJSON(uri);
57+
return fileContent[importSpecifier];
58+
}
59+
60+
export function formatTemplateDefinition(
61+
template: Record<string, any>,
62+
label: string | undefined,
63+
description: string | undefined
64+
): string {
65+
let ret = `\`\`\`json
66+
${JSON.stringify(template, null, 2)}
67+
\`\`\``;
68+
if (description) {
69+
ret = description + "\n\n" + ret;
70+
}
71+
if (label) {
72+
ret = `**${label}**\n\n${ret}`;
73+
}
74+
return ret;
75+
}
76+
77+
function getLineIndentation(line: string): string {
78+
return line.match(/^(\s*)/)?.[1] ?? "";
79+
}
80+
81+
export function getBlockRange(
82+
jsonDoc: string,
83+
position: vscode.Position
84+
): vscode.Range {
85+
const lines = jsonDoc.split("\n");
86+
let start = position.line;
87+
let end = position.line;
88+
const initialLineIndent = getLineIndentation(lines[start]).length;
89+
while (start > 0) {
90+
const line = lines[start];
91+
if (
92+
getLineIndentation(line).length < initialLineIndent &&
93+
line.trim().endsWith("{")
94+
) {
95+
break;
96+
}
97+
start--;
98+
}
99+
while (end < lines.length) {
100+
const line = lines[end];
101+
if (
102+
getLineIndentation(line).length < initialLineIndent &&
103+
line.trim().startsWith("}")
104+
) {
105+
break;
106+
}
107+
end++;
108+
}
109+
return new vscode.Range(
110+
new vscode.Position(start, getLineIndentation(lines[start]).length),
111+
new vscode.Position(end, getLineIndentation(lines[end]).length + 1)
112+
);
113+
}

0 commit comments

Comments
 (0)