2
2
import * as vscode from 'vscode' ;
3
3
import * as child_process from 'child_process' ;
4
4
import { Logger } from './logger' ;
5
+ import { text } from 'stream/consumers' ;
5
6
6
7
// Internal representation of a symbol
7
8
export class Symbol {
@@ -136,21 +137,19 @@ export class Symbol {
136
137
137
138
// TODO: add a user setting to enable/disable all ctags based operations
138
139
export class Ctags {
140
+ /// Symbol definitions (no rhs)
139
141
symbols : Symbol [ ] ;
140
142
doc : vscode . TextDocument ;
141
143
isDirty : boolean ;
142
144
private logger : Logger ;
143
145
144
- constructor ( logger : Logger ) {
146
+ constructor ( logger : Logger , document : vscode . TextDocument ) {
145
147
this . symbols = [ ] ;
146
148
this . isDirty = true ;
147
149
this . logger = logger ;
150
+ this . doc = document ;
148
151
}
149
152
150
- setDocument ( doc : vscode . TextDocument ) {
151
- this . doc = doc ;
152
- this . clearSymbols ( ) ;
153
- }
154
153
155
154
clearSymbols ( ) {
156
155
this . isDirty = true ;
@@ -161,7 +160,7 @@ export class Ctags {
161
160
return this . symbols ;
162
161
}
163
162
164
- execCtags ( filepath : string ) : Thenable < string > {
163
+ async execCtags ( filepath : string ) : Promise < string > {
165
164
this . logger . info ( 'executing ctags' ) ;
166
165
167
166
let binPath : string = < string > (
@@ -211,13 +210,13 @@ export class Ctags {
211
210
return undefined ;
212
211
}
213
212
214
- buildSymbolsList ( tags : string ) : Thenable < void > {
213
+ async buildSymbolsList ( tags : string ) : Promise < void > {
215
214
try {
216
215
if ( this . isDirty ) {
217
216
this . logger . info ( 'building symbols' ) ;
218
217
if ( tags === '' ) {
219
218
this . logger . error ( 'No output from ctags' ) ;
220
- return undefined ;
219
+ return ;
221
220
}
222
221
// Parse ctags output
223
222
let lines : string [ ] = tags . split ( / \r ? \n / ) ;
@@ -259,57 +258,112 @@ export class Ctags {
259
258
}
260
259
}
261
260
}
262
- this . logger . info ( 'Symbols: ' + this . symbols . toString ( ) ) ;
263
261
this . isDirty = false ;
264
262
}
265
- return Promise . resolve ( ) ;
266
263
} catch ( e ) {
267
264
this . logger . error ( e . toString ( ) ) ;
268
265
}
269
- return undefined ;
270
266
}
271
267
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 ) ;
279
273
}
280
274
}
281
275
282
276
export class CtagsManager {
283
- private static ctags : Ctags ;
277
+ private filemap : Map < vscode . TextDocument , Ctags > = new Map ( ) ;
284
278
private logger : Logger ;
285
279
286
- constructor ( logger : Logger ) {
280
+ configure ( logger : Logger ) {
287
281
this . logger = logger ;
288
- CtagsManager . ctags = new Ctags ( logger . getChild ( 'Ctags' ) ) ;
289
- }
290
-
291
- configure ( ) {
292
282
this . logger . info ( 'ctags manager configure' ) ;
293
283
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 ) ;
294
297
}
295
298
296
299
onSave ( doc : vscode . TextDocument ) {
297
300
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 ( ) ;
302
303
}
303
304
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 ) ;
309
307
// If dirty, re index and then build symbols
310
308
if ( ctags . isDirty ) {
311
309
await ctags . index ( ) ;
312
310
}
313
311
return ctags . symbols ;
314
312
}
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
+ }
315
369
}
0 commit comments