Skip to content

Commit

Permalink
added nested hover, file index, improved provider performance
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewNolte committed Apr 10, 2024
1 parent 509df3b commit c1ff930
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 80 deletions.
102 changes: 66 additions & 36 deletions src/ctags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
import * as vscode from 'vscode';
import { Logger } from './logger';
import { CtagsParser, Symbol } from './parsers/ctagsParser';
import { getParentText, getPrevChar, getWorkspaceFolder } from './utils';
import { getParentText, getPrevChar, getWorkspaceFolder, raceArrays } from './utils';
import { Config } from './config';

// Internal representation of a symbol

export class CtagsManager {
private filemap: Map<vscode.TextDocument, CtagsParser> = new Map();
private logger: Logger;
logger: Logger;
private searchPrefix: string;

// top level
/// symbols from include files
private symbolMap: Map<string, Symbol[]> = new Map();
/// module name to file, we assumed name.sv containes name module
private moduleMap: Map<string, vscode.Uri> = new Map();

constructor(logger: Logger, hdlDir: string) {
this.logger = logger;
Expand All @@ -26,34 +26,62 @@ export class CtagsManager {

vscode.workspace.onDidSaveTextDocument(this.onSave.bind(this));
vscode.workspace.onDidCloseTextDocument(this.onClose.bind(this));

this.index();
this.indexIncludes();
vscode.workspace.onDidChangeConfiguration(async (e) => {
if (e.affectsConfiguration('verilog.includes')) {
this.indexIncludes();
}
});

}

async indexIncludes(): Promise<void> {
this.logger.info('indexIncludes');
Config.getIncludePaths().forEach(async (path: string) => {
let files: vscode.Uri[] = await this.findFiles(`${path}/*.{sv,svh}`);

files.forEach(async (file: vscode.Uri) => {
this.logger.info(`indexing ${file}`);
let doc = await vscode.workspace.openTextDocument(file);
let syms = await this.getCtags(doc).getSymbols();
syms.forEach((element: Symbol) => {
// this.logger.info(`adding ${element.name}`);
if(this.symbolMap.has(element.name)) {
this.symbolMap.get(element.name)?.push(element);
} else {
this.symbolMap.set(element.name, [element]);
}
vscode.window.withProgress(
{
location: vscode.ProgressLocation.Window,
title: 'Indexing Verilog Includes',
cancellable: true,
},
async () => {
Config.getIncludePaths().forEach(async (path: string) => {
let files: vscode.Uri[] = await this.findFiles(`${path}/*.{svh}`);

files.forEach(async (file: vscode.Uri) => {
let doc = await vscode.workspace.openTextDocument(file);
let syms = await this.getCtags(doc).getPackageSymbols();
syms.forEach((element: Symbol) => {
if (this.symbolMap.has(element.name)) {
this.symbolMap.get(element.name)?.push(element);
} else {
this.symbolMap.set(element.name, [element]);
}
});
});
});
});
});
this.logger.info(`indexed ${Config.getIncludePaths().length} include paths`);
}
);
}

async index(): Promise<void> {
vscode.window.withProgress(
{
location: vscode.ProgressLocation.Window,
title: 'Indexing Verilog',
cancellable: true,
},
async () => {
this.logger.info('indexing');
// We want to do a shallow index
let files: vscode.Uri[] = await this.findFiles(`${this.searchPrefix}/*.{sv,v}`);
files.forEach(async (file: vscode.Uri) => {
let name = file.path.split('/').pop()?.split('.').shift() ?? '';
this.moduleMap.set(name, file);
});
this.logger.info(`indexed ${this.moduleMap.size} verilog files`);
}
);
}

getCtags(doc: vscode.TextDocument): CtagsParser {
Expand Down Expand Up @@ -84,11 +112,11 @@ export class CtagsManager {
}

async findModule(moduleName: string): Promise<vscode.TextDocument | undefined> {
let files = await this.findFiles(`${this.searchPrefix}/${moduleName}.{sv,v}`);
if (files.length === 0) {
let file = this.moduleMap.get(moduleName);
if (file === undefined) {
return undefined;
}
return await vscode.workspace.openTextDocument(files[0]);
return await vscode.workspace.openTextDocument(file);
}

async findDefinitionByName(moduleName: string, targetText: string): Promise<Symbol[]> {
Expand All @@ -106,13 +134,18 @@ export class CtagsManager {
if (!textRange || textRange.isEmpty) {
return [];
}
let parser = this.getCtags(document);
let targetText = document.getText(textRange);
let symPromise = parser.getSymbols({ name: targetText });
let syms: Symbol[] | undefined = undefined;

let parentScope = getParentText(document, textRange);
// let modulePromise = this.findDefinitionByName(parentScope, targetText);
// If we're at a port, .<text> plus no parent
let parser = this.getCtags(document);
if (getPrevChar(document, textRange) === '.' && parentScope === targetText) {
let insts = await parser.getSymbols({ type: 'instance' });
syms = await symPromise;

let insts = syms.filter((sym) => sym.type === 'instance');
if (insts.length > 0) {
let latestInst = insts.reduce((latest, inst) => {
if (
Expand All @@ -131,16 +164,13 @@ export class CtagsManager {
}
}

if (this.symbolMap.has(targetText)){
if (this.symbolMap.has(targetText)) {
return this.symbolMap.get(targetText) ?? [];
}

const results: Symbol[][] = await Promise.all([
// find def in current document
parser.getSymbols({ name: targetText }),
// search for module.sv or interface.sv
this.findDefinitionByName(parentScope, targetText),
]);
return results.reduce((acc, val) => acc.concat(val), []);
if (this.moduleMap.has(targetText)) {
return await this.findDefinitionByName(parentScope, targetText);
}
return await symPromise;
}
}
126 changes: 96 additions & 30 deletions src/parsers/ctagsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,19 @@ export class Symbol {
new vscode.Position(this.line, this.doc.lineAt(this.line).text.indexOf(this.name))
);
if (this.idRange === undefined) {
this.idRange = new vscode.Range(this.line, 0, this.line, Number.MAX_VALUE);
this.idRange = this.getFullRange();
}
}
return this.idRange;
}

getFullRange(): vscode.Range {
if (this.fullRange === undefined) {
this.fullRange = this.doc.lineAt(this.line).range;
}
return this.fullRange;
}

setEndPosition(endLine: number) {
this.fullRange = new vscode.Range(this.line, 0, endLine, Number.MAX_VALUE);
this.isValid = true;
Expand All @@ -81,14 +88,38 @@ export class Symbol {
return false;
}
let firstchar = this.doc.lineAt(this.line).firstNonWhitespaceCharacterIndex;
if(this.doc.getText(new vscode.Range(this.line, firstchar, this.line, firstchar+7)) === '`define'){
if (
this.doc.getText(new vscode.Range(this.line, firstchar, this.line, firstchar + 7)) ===
'`define'
) {
return true;
}
return false;
}


isModuleType(): boolean {
return this.type === 'module' || this.type === 'interface';
}

getHoverRange(): vscode.Range {
if (this.isMacro()) {
let range = this.getFullRange();
let endline = range.end.line;
for (; endline < this.doc.lineCount; endline++) {
if (!this.doc.lineAt(endline).text.endsWith('\\')) {
break;
}
}
this.fullRange = new vscode.Range(
range.start,
new vscode.Position(endline, Number.MAX_VALUE)
);
}
return this.getFullRange();
}

getHoverText(): string {
if (this.isMacro()){
if (this.isMacro()) {
// macro definitions should show the whole macro
let code = '';
for (let i = this.line; i < this.doc.lineCount; i++) {
Expand All @@ -112,7 +143,6 @@ export class Symbol {
return code;
}


getDefinitionLink(): vscode.DefinitionLink {
return {
targetUri: this.doc.uri,
Expand Down Expand Up @@ -253,6 +283,11 @@ export class CtagsParser {
return syms.filter((tag) => tag.type === 'module' || tag.type === 'interface');
}

async getPackageSymbols(): Promise<Symbol[]> {
let syms = await this.getSymbols();
return syms.filter((tag) => tag.type !== 'member' && tag.type !== 'register');
}

async execCtags(filepath: string): Promise<string> {
let command: string =
this.binPath +
Expand All @@ -261,12 +296,9 @@ export class CtagsParser {
'"';
this.logger.info('Executing Command: ' + command);
return new Promise((resolve, _reject) => {
child.exec(
command,
(_error: child.ExecException | null, stdout: string, _stderr: string) => {
resolve(stdout);
}
);
child.exec(command, (_error: child.ExecException | null, stdout: string, _stderr: string) => {
resolve(stdout);
});
});
}

Expand Down Expand Up @@ -300,16 +332,7 @@ export class CtagsParser {
lineNoStr = parts[2];
lineNo = Number(lineNoStr.slice(0, -2)) - 1;
// pretty print symbol
return new Symbol(
this.doc,
name,
type,
lineNo,
parentScope,
parentType,
typeref,
false
);
return new Symbol(this.doc, name, type, lineNo, parentScope, parentType, typeref, false);
} catch (e) {
this.logger.error('Line Parser: ' + e);
this.logger.error('Line: ' + line);
Expand Down Expand Up @@ -384,11 +407,37 @@ export class CtagsParser {
await this.buildSymbolsList(output);
}

async getNestedHoverText(sym: Symbol): Promise<Array<string>> {
// TODO: maybe go deeper
let ret: string[] = [];
ret.push(sym.getHoverText());
if (sym.isModuleType()) {
return ret;
}
// find other words with definitions in the hover text, and add them
let range = sym.getHoverRange();
let words = new Set(this.doc.getText(range).match(/\b[a-zA-Z_]\w*\b/g) || []);
words.delete(sym.name);

let docsyms = await this.getSymbols();
let syms = docsyms.filter((sym) => words.has(sym.name));
let defs = await Promise.all(
syms.map(async (s: Symbol) => {
return s.getHoverText();
})
);

if (defs.length > 0) {
ret.push(defs.join('\n'));
}

return ret;
}

getModuleSnippet(module: Symbol, fullModule: boolean) {
let portsName: string[] = [];
let parametersName: string[] = [];

let scope = module.parentScope !== '' ? module.parentScope + '.' + module.name : module.name;
let ports: Symbol[] = this.symbols.filter(
(tag) => tag.type === 'port' && tag.parentScope === scope
Expand All @@ -402,15 +451,15 @@ export class CtagsParser {
if (parametersName.length > 0) {
paramString = `\n${instantiatePort(parametersName)}`;
}

if (fullModule) {
return new vscode.SnippetString()
.appendText(module.name + ' ')
.appendText('#(')
.appendText(paramString)
.appendText(') ')
.appendPlaceholder('u_')
.appendPlaceholder(`${module.name} (\n`)
.appendPlaceholder(`${module.name.toLowerCase()}`)
.appendText(' (\n')
.appendText(instantiatePort(portsName))
.appendText(');\n');
}
Expand All @@ -419,15 +468,13 @@ export class CtagsParser {
// .appendText('#(')
.appendText(paramString)
.appendText(') ')
.appendPlaceholder('u_')
.appendPlaceholder(`${module.name} (\n`)
.appendPlaceholder(`${module.name.toLowerCase()}`)
.appendText(' (\n')
.appendText(instantiatePort(portsName))
);
}
}



function instantiatePort(ports: string[]): string {
let port = '';
let maxLen = 0;
Expand All @@ -448,4 +495,23 @@ function instantiatePort(ports: string[]): string {
port += '\n';
}
return port;
}
}

function positionsFromRange(doc: vscode.TextDocument, range: vscode.Range): vscode.Position[] {
let ret: vscode.Position[] = [];
for (let i = range.start.line; i <= range.end.line; i++) {
const line = doc.lineAt(i);
const words = line.text.match(/\b(\w+)\b/g);
let match;

if (words) {
// Use a regex to find words and their indices within the line
const regex = /\b(\w+)\b/g;
while ((match = regex.exec(line.text)) !== null) {
// Calculate the start and end positions of each word
ret.push(new vscode.Position(i, match.index));
}
}
}
return ret;
}
Loading

0 comments on commit c1ff930

Please sign in to comment.