22import * as vscode from 'vscode' ;
33import * as child_process from 'child_process' ;
44import { Logger } from './logger' ;
5+ import { text } from 'stream/consumers' ;
56
67// Internal representation of a symbol
78export class Symbol {
@@ -136,21 +137,19 @@ export class Symbol {
136137
137138// TODO: add a user setting to enable/disable all ctags based operations
138139export 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
282276export 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