@@ -8,8 +8,9 @@ import * as path from 'path';
88import { TextDocument } from 'vscode' ;
99
1010import { sendTelemetryEvent } from '.' ;
11- import { noop , sleep } from '../../test/core' ;
11+ import { noop } from '../../test/core' ;
1212import { IDocumentManager } from '../common/application/types' ;
13+ import { isTestExecution } from '../common/constants' ;
1314import { IHistoryProvider } from '../datascience/types' ;
1415import { ICodeExecutionManager } from '../terminals/types' ;
1516import { EventName } from './constants' ;
@@ -18,9 +19,14 @@ import { IImportTracker } from './types';
1819const ImportRegEx = / ^ (? ! [ ' " # ] ) .* f r o m \s + ( [ a - z A - Z 0 - 9 _ \. ] + ) \s + i m p o r t .* (? ! [ ' " ] ) | ^ (? ! [ ' " # ] ) .* i m p o r t \s + ( [ a - z A - Z 0 - 9 _ \. , ] + ) .* (? ! [ ' " ] ) / ;
1920const MAX_DOCUMENT_LINES = 1000 ;
2021
22+ // Capture isTestExecution on module load so that a test can turn it off and still
23+ // have this value set.
24+ const testExecution = isTestExecution ( ) ;
25+
2126@injectable ( )
2227export class ImportTracker implements IImportTracker {
2328
29+ private pendingDocs = new Map < string , NodeJS . Timer > ( ) ;
2430 private sentMatches : Set < string > = new Set < string > ( ) ;
2531 // tslint:disable-next-line:no-require-imports
2632 private hashFn = require ( 'hash.js' ) . sha256 ;
@@ -44,21 +50,51 @@ export class ImportTracker implements IImportTracker {
4450 }
4551
4652 public async activate ( ) : Promise < void > {
47- // Act like all of our open documents just opened. Don't do this now though. We don't want
48- // to hold up the activate.
49- await sleep ( 1000 ) ;
53+ // Act like all of our open documents just opened. Our timeout will make sure this is delayed
5054 this . documentManager . textDocuments . forEach ( d => this . onOpenedOrSavedDocument ( d ) ) ;
5155 }
5256
57+ private getDocumentLines ( document : TextDocument ) : string [ ] {
58+ return Array . apply ( null , { length : Math . min ( document . lineCount , MAX_DOCUMENT_LINES ) } ) . map ( ( a , i ) => {
59+ const line = document . lineAt ( i ) ;
60+ if ( line && ! line . isEmptyOrWhitespace ) {
61+ return line . text ;
62+ }
63+ return undefined ;
64+ } ) . filter ( ( f : string | undefined ) => f ) ;
65+ }
66+
5367 private onOpenedOrSavedDocument ( document : TextDocument ) {
5468 // Make sure this is a python file.
5569 if ( path . extname ( document . fileName ) === '.py' ) {
5670 // Parse the contents of the document, looking for import matches on each line
57- const lines = document . getText ( ) . splitLines ( { trim : true , removeEmptyEntries : true } ) ;
58- this . lookForImports ( lines . slice ( 0 , Math . min ( lines . length , MAX_DOCUMENT_LINES ) ) , EventName . KNOWN_IMPORT_FROM_FILE ) ;
71+ this . scheduleDocument ( document ) ;
5972 }
6073 }
6174
75+ private scheduleDocument ( document : TextDocument ) {
76+ // If already scheduled, cancel.
77+ if ( this . pendingDocs . has ( document . fileName ) ) {
78+ clearTimeout ( this . pendingDocs . get ( document . fileName ) ) ;
79+ this . pendingDocs . delete ( document . fileName ) ;
80+ }
81+
82+ // Now schedule a new one.
83+ if ( testExecution ) {
84+ // During a test, check right away. It needs to be synchronous.
85+ this . checkDocument ( document ) ;
86+ } else {
87+ // Wait five seconds to make sure we don't already have this document pending.
88+ this . pendingDocs . set ( document . fileName , setTimeout ( ( ) => this . checkDocument ( document ) , 5000 ) ) ;
89+ }
90+ }
91+
92+ private checkDocument ( document : TextDocument ) {
93+ this . pendingDocs . delete ( document . fileName ) ;
94+ const lines = this . getDocumentLines ( document ) ;
95+ this . lookForImports ( lines , EventName . KNOWN_IMPORT_FROM_FILE ) ;
96+ }
97+
6298 private onExecutedCode ( code : string ) {
6399 const lines = code . splitLines ( { trim : true , removeEmptyEntries : true } ) ;
64100 this . lookForImports ( lines , EventName . KNOWN_IMPORT_FROM_EXECUTION ) ;
0 commit comments