Skip to content

Commit 487543b

Browse files
authored
Support module definitions by looking for file name (#470)
* support module/interface definitions and hovers by searching for file name * clean up, search for parent sym if there * more logs cleanup --------- Co-authored-by: Andrew Nolte <[email protected]>
1 parent 45bc2f4 commit 487543b

File tree

7 files changed

+195
-169
lines changed

7 files changed

+195
-169
lines changed

src/commands/ModuleInstantiation.ts

Lines changed: 65 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -14,71 +14,63 @@ export function instantiateModuleInteract() {
1414
});
1515
}
1616

17-
function instantiateModule(srcpath: string): Thenable<vscode.SnippetString> {
18-
return new Promise<vscode.SnippetString>((resolve, _reject) => {
17+
async function instantiateModule(srcpath: string): Promise<vscode.SnippetString | undefined> {
1918
// Using Ctags to get all the modules in the file
2019
let moduleName: string = '';
2120
let portsName: string[] = [];
2221
let parametersName: string[] = [];
23-
let ctags: ModuleTags = new ModuleTags(logger);
22+
let file: vscode.TextDocument = vscode.window.activeTextEditor.document;
23+
let ctags: ModuleTags = new ModuleTags(logger, file);
2424
logger.info('Executing ctags for module instantiation');
25-
ctags
26-
.execCtags(srcpath)
27-
.then((output) => {
28-
ctags.buildSymbolsList(output);
29-
})
30-
.then(async () => {
31-
let module: Symbol;
32-
let modules: Symbol[] = ctags.symbols.filter((tag) => tag.type === 'module');
33-
// No modules found
34-
if (modules.length <= 0) {
35-
vscode.window.showErrorMessage('Verilog-HDL/SystemVerilog: No modules found in the file');
36-
return;
37-
}
38-
// Only one module found
39-
else if (modules.length === 1) {
40-
module = modules[0];
41-
}
42-
// many modules found
43-
else if (modules.length > 1) {
44-
moduleName = await vscode.window.showQuickPick(
45-
ctags.symbols.filter((tag) => tag.type === 'module').map((tag) => tag.name),
46-
{
47-
placeHolder: 'Choose a module to instantiate',
48-
}
49-
);
50-
if (moduleName === undefined) {
51-
return;
52-
}
53-
module = modules.filter((tag) => tag.name === moduleName)[0];
54-
}
55-
let scope = module.parentScope != '' ? module.parentScope + '.' + module.name : module.name;
56-
let ports: Symbol[] = ctags.symbols.filter(
57-
(tag) => tag.type === 'port' && tag.parentType === 'module' && tag.parentScope === scope
58-
);
59-
portsName = ports.map((tag) => tag.name);
60-
let params: Symbol[] = ctags.symbols.filter(
61-
(tag) =>
62-
tag.type === 'parameter' && tag.parentType === 'module' && tag.parentScope === scope
63-
);
64-
parametersName = params.map((tag) => tag.name);
65-
logger.info('Module name: ' + module.name);
66-
let paramString = ``;
67-
if (parametersName.length > 0) {
68-
paramString = `\n#(\n${instantiatePort(parametersName)})\n`;
25+
let output = await ctags.execCtags(srcpath);
26+
await ctags.buildSymbolsList(output);
27+
let module: Symbol;
28+
let modules: Symbol[] = ctags.symbols.filter((tag) => tag.type === 'module');
29+
// No modules found
30+
if (modules.length <= 0) {
31+
vscode.window.showErrorMessage('Verilog-HDL/SystemVerilog: No modules found in the file');
32+
return undefined;
33+
}
34+
// Only one module found
35+
else if (modules.length === 1) {
36+
module = modules[0];
37+
}
38+
// many modules found
39+
else if (modules.length > 1) {
40+
moduleName = await vscode.window.showQuickPick(
41+
ctags.symbols.filter((tag) => tag.type === 'module').map((tag) => tag.name),
42+
{
43+
placeHolder: 'Choose a module to instantiate',
6944
}
70-
logger.info('portsName: ' + portsName.toString());
71-
resolve(
72-
new vscode.SnippetString()
73-
.appendText(module.name + ' ')
74-
.appendText(paramString)
75-
.appendPlaceholder('u_')
76-
.appendPlaceholder(`${module.name}(\n`)
77-
.appendText(instantiatePort(portsName))
78-
.appendText(');\n')
79-
);
80-
});
81-
});
45+
);
46+
if (moduleName === undefined) {
47+
return undefined;
48+
}
49+
module = modules.filter((tag) => tag.name === moduleName)[0];
50+
}
51+
let scope = module.parentScope != '' ? module.parentScope + '.' + module.name : module.name;
52+
let ports: Symbol[] = ctags.symbols.filter(
53+
(tag) => tag.type === 'port' && tag.parentType === 'module' && tag.parentScope === scope
54+
);
55+
portsName = ports.map((tag) => tag.name);
56+
let params: Symbol[] = ctags.symbols.filter(
57+
(tag) =>
58+
tag.type === 'parameter' && tag.parentType === 'module' && tag.parentScope === scope
59+
);
60+
parametersName = params.map((tag) => tag.name);
61+
logger.info('Module name: ' + module.name);
62+
let paramString = ``;
63+
if (parametersName.length > 0) {
64+
paramString = `\n#(\n${instantiatePort(parametersName)})\n`;
65+
}
66+
logger.info('portsName: ' + portsName.toString());
67+
return new vscode.SnippetString()
68+
.appendText(module.name + ' ')
69+
.appendText(paramString)
70+
.appendPlaceholder('u_')
71+
.appendPlaceholder(`${module.name}(\n`)
72+
.appendText(instantiatePort(portsName))
73+
.appendText(');\n');
8274
}
8375

8476
function instantiatePort(ports: string[]): string {
@@ -103,7 +95,7 @@ function instantiatePort(ports: string[]): string {
10395
return port;
10496
}
10597

106-
function selectFile(currentDir?: string): Thenable<string> {
98+
async function selectFile(currentDir?: string): Promise<string | undefined> {
10799
currentDir = currentDir || vscode.workspace.rootPath;
108100

109101
let dirs = getDirectories(currentDir);
@@ -129,24 +121,22 @@ function selectFile(currentDir?: string): Thenable<string> {
129121
});
130122
});
131123

132-
return vscode.window
124+
let selected = await vscode.window
133125
.showQuickPick(items, {
134126
placeHolder: 'Choose the module file',
135-
})
136-
.then((selected) => {
137-
if (!selected) {
138-
return undefined;
139-
}
127+
});
128+
if (!selected) {
129+
return undefined;
130+
}
140131

141-
// if is a directory
142-
let location = path.join(currentDir, selected.label);
143-
if (fs.statSync(location).isDirectory()) {
144-
return selectFile(location);
145-
}
132+
// if is a directory
133+
let location = path.join(currentDir, selected.label);
134+
if (fs.statSync(location).isDirectory()) {
135+
return selectFile(location);
136+
}
146137

147-
// return file path
148-
return location;
149-
});
138+
// return file path
139+
return location;
150140
}
151141

152142
function getDirectories(srcpath: string): string[] {
@@ -160,7 +150,7 @@ function getFiles(srcpath: string): string[] {
160150
}
161151

162152
class ModuleTags extends Ctags {
163-
buildSymbolsList(tags: string): Thenable<void> {
153+
buildSymbolsList(tags: string): Promise<void> {
164154
if (tags === '') {
165155
return undefined;
166156
}

src/ctags.ts

Lines changed: 87 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import * as vscode from 'vscode';
33
import * as child_process from 'child_process';
44
import { Logger } from './logger';
5+
import { text } from 'stream/consumers';
56

67
// Internal representation of a symbol
78
export class Symbol {
@@ -136,21 +137,19 @@ export class Symbol {
136137

137138
// TODO: add a user setting to enable/disable all ctags based operations
138139
export class Ctags {
140+
/// Symbol definitions (no rhs)
139141
symbols: Symbol[];
140142
doc: vscode.TextDocument;
141143
isDirty: boolean;
142144
private logger: Logger;
143145

144-
constructor(logger: Logger) {
146+
constructor(logger: Logger, document: vscode.TextDocument) {
145147
this.symbols = [];
146148
this.isDirty = true;
147149
this.logger = logger;
150+
this.doc = document;
148151
}
149152

150-
setDocument(doc: vscode.TextDocument) {
151-
this.doc = doc;
152-
this.clearSymbols();
153-
}
154153

155154
clearSymbols() {
156155
this.isDirty = true;
@@ -161,7 +160,7 @@ export class Ctags {
161160
return this.symbols;
162161
}
163162

164-
execCtags(filepath: string): Thenable<string> {
163+
async execCtags(filepath: string): Promise<string> {
165164
this.logger.info('executing ctags');
166165

167166
let binPath: string = <string>(
@@ -211,13 +210,13 @@ export class Ctags {
211210
return undefined;
212211
}
213212

214-
buildSymbolsList(tags: string): Thenable<void> {
213+
async buildSymbolsList(tags: string): Promise<void> {
215214
try {
216215
if (this.isDirty) {
217216
this.logger.info('building symbols');
218217
if (tags === '') {
219218
this.logger.error('No output from ctags');
220-
return undefined;
219+
return;
221220
}
222221
// Parse ctags output
223222
let lines: string[] = tags.split(/\r?\n/);
@@ -259,57 +258,112 @@ export class Ctags {
259258
}
260259
}
261260
}
262-
this.logger.info('Symbols: ' + this.symbols.toString());
263261
this.isDirty = false;
264262
}
265-
return Promise.resolve();
266263
} catch (e) {
267264
this.logger.error(e.toString());
268265
}
269-
return undefined;
270266
}
271267

272-
index(): Thenable<void> {
273-
this.logger.info('indexing...');
274-
return new Promise((resolve, _reject) => {
275-
this.execCtags(this.doc.uri.fsPath)
276-
.then((output) => this.buildSymbolsList(output))
277-
.then(() => resolve());
278-
});
268+
async index(): Promise<void> {
269+
this.logger.info('indexing ', this.doc.uri.fsPath);
270+
271+
let output = await this.execCtags(this.doc.uri.fsPath)
272+
await this.buildSymbolsList(output);
279273
}
280274
}
281275

282276
export class CtagsManager {
283-
private static ctags: Ctags;
277+
private filemap: Map<vscode.TextDocument, Ctags> = new Map();
284278
private logger: Logger;
285279

286-
constructor(logger: Logger) {
280+
configure(logger: Logger) {
287281
this.logger = logger;
288-
CtagsManager.ctags = new Ctags(logger.getChild('Ctags'));
289-
}
290-
291-
configure() {
292282
this.logger.info('ctags manager configure');
293283
vscode.workspace.onDidSaveTextDocument(this.onSave.bind(this));
284+
vscode.workspace.onDidCloseTextDocument(this.onClose.bind(this));
285+
}
286+
287+
getCtags(doc: vscode.TextDocument): Ctags {
288+
let ctags: Ctags = this.filemap.get(doc);
289+
if (ctags === undefined) {
290+
ctags = new Ctags(this.logger, doc);
291+
this.filemap.set(doc, ctags);
292+
}
293+
return ctags;
294+
}
295+
onClose(doc: vscode.TextDocument) {
296+
this.filemap.delete(doc);
294297
}
295298

296299
onSave(doc: vscode.TextDocument) {
297300
this.logger.info('on save');
298-
let ctags: Ctags = CtagsManager.ctags;
299-
if (ctags.doc === undefined || ctags.doc.uri.fsPath === doc.uri.fsPath) {
300-
CtagsManager.ctags.clearSymbols();
301-
}
301+
let ctags: Ctags = this.getCtags(doc);
302+
ctags.clearSymbols();
302303
}
303304

304-
static async getSymbols(doc: vscode.TextDocument): Promise<Symbol[]> {
305-
let ctags: Ctags = CtagsManager.ctags;
306-
if (ctags.doc === undefined || ctags.doc.uri.fsPath !== doc.uri.fsPath) {
307-
ctags.setDocument(doc);
308-
}
305+
async getSymbols(doc: vscode.TextDocument): Promise<Symbol[]> {
306+
let ctags: Ctags = this.getCtags(doc);
309307
// If dirty, re index and then build symbols
310308
if (ctags.isDirty) {
311309
await ctags.index();
312310
}
313311
return ctags.symbols;
314312
}
313+
314+
315+
316+
/// find a matching symbol in a single document
317+
async findDefinition(document: vscode.TextDocument, targetText: string): Promise<vscode.DefinitionLink[]> {
318+
let symbols: Symbol[] = await this.getSymbols(document);
319+
let matchingSymbols = symbols.filter((sym) => sym.name === targetText);
320+
321+
return matchingSymbols.map((i) => {
322+
return {
323+
targetUri: document.uri,
324+
targetRange: new vscode.Range(
325+
i.startPosition,
326+
new vscode.Position(i.startPosition.line, Number.MAX_VALUE)
327+
),
328+
targetSelectionRange: new vscode.Range(i.startPosition, i.endPosition),
329+
};
330+
});
331+
}
332+
333+
/// Finds a symbols definition, but also looks in targetText.sv to get module/interface defs
334+
async findSymbol(document: vscode.TextDocument, position: vscode.Position): Promise<vscode.DefinitionLink[]> {
335+
336+
let textRange = document.getWordRangeAtPosition(position);
337+
if (!textRange || textRange.isEmpty) {
338+
return undefined;
339+
}
340+
let targetText = document.getText(textRange);
341+
342+
// always search the current doc
343+
let tasks = [this.findDefinition(document, targetText)];
344+
345+
// if the previous character is :: or ., look up prev word
346+
let prevChar = textRange.start.character - 1;
347+
let prevCharRange = new vscode.Range(position.line, prevChar, position.line, prevChar+1);
348+
let prevCharText = document.getText(prevCharRange);
349+
let moduleToFind: string = targetText;
350+
if (prevCharText === '.' || prevCharText === ':') {
351+
let prevWordRange = document.getWordRangeAtPosition(new vscode.Position(position.line, prevChar - 2));
352+
if (prevWordRange) {
353+
moduleToFind = document.getText(prevWordRange);
354+
}
355+
}
356+
357+
// kick off async job for indexing for module.sv
358+
let searchPattern = new vscode.RelativePattern(vscode.workspace.workspaceFolders[0], `**/${moduleToFind}.sv`);
359+
let files = await vscode.workspace.findFiles(searchPattern);
360+
if (files.length !== 0) {
361+
let file = await vscode.workspace.openTextDocument(files[0]);
362+
tasks.push(this.findDefinition(file, targetText));
363+
}
364+
365+
// TODO: use promise.race
366+
const results: vscode.DefinitionLink[][] = await Promise.all(tasks);
367+
return results.reduce((acc, val) => acc.concat(val), []);
368+
}
315369
}

0 commit comments

Comments
 (0)