3
3
4
4
import { inject , injectable } from 'inversify' ;
5
5
import * as path from 'path' ;
6
- import { ExtensionContext , OutputChannel } from 'vscode' ;
6
+ import { OutputChannel , Uri } from 'vscode' ;
7
7
import { Disposable , LanguageClient , LanguageClientOptions , ServerOptions } from 'vscode-languageclient' ;
8
- import { IApplicationShell , ICommandManager } from '../common/application/types' ;
8
+ import { IApplicationShell , ICommandManager , IWorkspaceService } from '../common/application/types' ;
9
9
import { isTestExecution , STANDARD_OUTPUT_CHANNEL } from '../common/constants' ;
10
10
import { createDeferred , Deferred } from '../common/helpers' ;
11
11
import { IFileSystem , IPlatformService } from '../common/platform/types' ;
12
12
import { StopWatch } from '../common/stopWatch' ;
13
13
import { IConfigurationService , IExtensionContext , IOutputChannel } from '../common/types' ;
14
- import { IEnvironmentVariablesProvider } from '../common/variables/types' ;
15
14
import { IInterpreterService } from '../interpreter/contracts' ;
16
15
import { IServiceContainer } from '../ioc/types' ;
17
16
import {
@@ -21,7 +20,7 @@ import {
21
20
} from '../telemetry/constants' ;
22
21
import { getTelemetryReporter } from '../telemetry/telemetry' ;
23
22
import { AnalysisEngineDownloader } from './downloader' ;
24
- import { InterpreterDataService } from './interpreterDataService' ;
23
+ import { InterpreterData , InterpreterDataService } from './interpreterDataService' ;
25
24
import { PlatformData } from './platformData' ;
26
25
import { IExtensionActivator } from './types' ;
27
26
@@ -42,10 +41,13 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
42
41
private readonly interpreterService : IInterpreterService ;
43
42
private readonly startupCompleted : Deferred < void > ;
44
43
private readonly disposables : Disposable [ ] = [ ] ;
44
+ private readonly context : IExtensionContext ;
45
+ private readonly workspace : IWorkspaceService ;
46
+ private readonly root : Uri | undefined ;
45
47
46
48
private languageClient : LanguageClient | undefined ;
47
- private readonly context : ExtensionContext ;
48
49
private interpreterHash : string = '' ;
50
+ private loadExtensionArgs : { } | undefined ;
49
51
50
52
constructor ( @inject ( IServiceContainer ) private readonly services : IServiceContainer ) {
51
53
this . context = this . services . get < IExtensionContext > ( IExtensionContext ) ;
@@ -55,32 +57,41 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
55
57
this . fs = this . services . get < IFileSystem > ( IFileSystem ) ;
56
58
this . platformData = new PlatformData ( services . get < IPlatformService > ( IPlatformService ) , this . fs ) ;
57
59
this . interpreterService = this . services . get < IInterpreterService > ( IInterpreterService ) ;
60
+ this . workspace = this . services . get < IWorkspaceService > ( IWorkspaceService ) ;
61
+
62
+ // Currently only a single root. Multi-root support is future.
63
+ this . root = this . workspace && this . workspace . hasWorkspaceFolders
64
+ ? this . workspace . workspaceFolders ! [ 0 ] ! . uri : undefined ;
58
65
59
66
this . startupCompleted = createDeferred < void > ( ) ;
60
67
const commandManager = this . services . get < ICommandManager > ( ICommandManager ) ;
68
+
61
69
this . disposables . push ( commandManager . registerCommand ( loadExtensionCommand ,
62
70
async ( args ) => {
63
71
if ( this . languageClient ) {
64
72
await this . startupCompleted . promise ;
65
73
this . languageClient . sendRequest ( 'python/loadExtension' , args ) ;
74
+ } else {
75
+ this . loadExtensionArgs = args ;
66
76
}
67
77
}
68
78
) ) ;
69
79
}
70
80
71
81
public async activate ( ) : Promise < boolean > {
72
82
this . sw . reset ( ) ;
73
- const clientOptions = await this . getAnalysisOptions ( this . context ) ;
83
+ const clientOptions = await this . getAnalysisOptions ( ) ;
74
84
if ( ! clientOptions ) {
75
85
return false ;
76
86
}
77
87
this . disposables . push ( this . interpreterService . onDidChangeInterpreter ( ( ) => this . restartLanguageServer ( ) ) ) ;
78
- return this . startLanguageServer ( this . context , clientOptions ) ;
88
+ return this . startLanguageServer ( clientOptions ) ;
79
89
}
80
90
81
91
public async deactivate ( ) : Promise < void > {
82
92
if ( this . languageClient ) {
83
- await this . languageClient . stop ( ) ;
93
+ // Do not await on this
94
+ this . languageClient . stop ( ) ;
84
95
}
85
96
for ( const d of this . disposables ) {
86
97
d . dispose ( ) ;
@@ -100,7 +111,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
100
111
}
101
112
}
102
113
103
- private async startLanguageServer ( context : ExtensionContext , clientOptions : LanguageClientOptions ) : Promise < boolean > {
114
+ private async startLanguageServer ( clientOptions : LanguageClientOptions ) : Promise < boolean > {
104
115
// Determine if we are running MSIL/Universal via dotnet or self-contained app.
105
116
106
117
const reporter = getTelemetryReporter ( ) ;
@@ -109,22 +120,22 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
109
120
const settings = this . configuration . getSettings ( ) ;
110
121
if ( ! settings . downloadCodeAnalysis ) {
111
122
// Depends on .NET Runtime or SDK. Typically development-only case.
112
- this . languageClient = this . createSimpleLanguageClient ( context , clientOptions ) ;
113
- await this . startLanguageClient ( context ) ;
123
+ this . languageClient = this . createSimpleLanguageClient ( clientOptions ) ;
124
+ await this . startLanguageClient ( ) ;
114
125
return true ;
115
126
}
116
127
117
- const mscorlib = path . join ( context . extensionPath , analysisEngineFolder , 'mscorlib.dll' ) ;
128
+ const mscorlib = path . join ( this . context . extensionPath , analysisEngineFolder , 'mscorlib.dll' ) ;
118
129
if ( ! await this . fs . fileExists ( mscorlib ) ) {
119
130
const downloader = new AnalysisEngineDownloader ( this . services , analysisEngineFolder ) ;
120
- await downloader . downloadAnalysisEngine ( context ) ;
131
+ await downloader . downloadAnalysisEngine ( this . context ) ;
121
132
reporter . sendTelemetryEvent ( PYTHON_ANALYSIS_ENGINE_DOWNLOADED ) ;
122
133
}
123
134
124
- const serverModule = path . join ( context . extensionPath , analysisEngineFolder , this . platformData . getEngineExecutableName ( ) ) ;
125
- this . languageClient = this . createSelfContainedLanguageClient ( context , serverModule , clientOptions ) ;
135
+ const serverModule = path . join ( this . context . extensionPath , analysisEngineFolder , this . platformData . getEngineExecutableName ( ) ) ;
136
+ this . languageClient = this . createSelfContainedLanguageClient ( serverModule , clientOptions ) ;
126
137
try {
127
- await this . startLanguageClient ( context ) ;
138
+ await this . startLanguageClient ( ) ;
128
139
return true ;
129
140
} catch ( ex ) {
130
141
this . appShell . showErrorMessage ( `Language server failed to start. Error ${ ex } ` ) ;
@@ -133,30 +144,34 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
133
144
}
134
145
}
135
146
136
- private async startLanguageClient ( context : ExtensionContext ) : Promise < void > {
147
+ private async startLanguageClient ( ) : Promise < void > {
137
148
this . languageClient ! . onReady ( )
138
149
. then ( ( ) => {
139
150
this . startupCompleted . resolve ( ) ;
151
+ if ( this . loadExtensionArgs ) {
152
+ this . languageClient ! . sendRequest ( 'python/loadExtension' , this . loadExtensionArgs ) ;
153
+ this . loadExtensionArgs = undefined ;
154
+ }
140
155
} )
141
156
. catch ( error => this . startupCompleted . reject ( error ) ) ;
142
157
143
- context . subscriptions . push ( this . languageClient ! . start ( ) ) ;
158
+ this . context . subscriptions . push ( this . languageClient ! . start ( ) ) ;
144
159
if ( isTestExecution ( ) ) {
145
160
await this . startupCompleted . promise ;
146
161
}
147
162
}
148
163
149
- private createSimpleLanguageClient ( context : ExtensionContext , clientOptions : LanguageClientOptions ) : LanguageClient {
164
+ private createSimpleLanguageClient ( clientOptions : LanguageClientOptions ) : LanguageClient {
150
165
const commandOptions = { stdio : 'pipe' } ;
151
- const serverModule = path . join ( context . extensionPath , analysisEngineFolder , this . platformData . getEngineDllName ( ) ) ;
166
+ const serverModule = path . join ( this . context . extensionPath , analysisEngineFolder , this . platformData . getEngineDllName ( ) ) ;
152
167
const serverOptions : ServerOptions = {
153
168
run : { command : dotNetCommand , args : [ serverModule ] , options : commandOptions } ,
154
169
debug : { command : dotNetCommand , args : [ serverModule , '--debug' ] , options : commandOptions }
155
170
} ;
156
171
return new LanguageClient ( PYTHON , languageClientName , serverOptions , clientOptions ) ;
157
172
}
158
173
159
- private createSelfContainedLanguageClient ( context : ExtensionContext , serverModule : string , clientOptions : LanguageClientOptions ) : LanguageClient {
174
+ private createSelfContainedLanguageClient ( serverModule : string , clientOptions : LanguageClientOptions ) : LanguageClient {
160
175
const options = { stdio : 'pipe' } ;
161
176
const serverOptions : ServerOptions = {
162
177
run : { command : serverModule , rgs : [ ] , options : options } ,
@@ -165,19 +180,22 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
165
180
return new LanguageClient ( PYTHON , languageClientName , serverOptions , clientOptions ) ;
166
181
}
167
182
168
- private async getAnalysisOptions ( context : ExtensionContext ) : Promise < LanguageClientOptions | undefined > {
183
+ private async getAnalysisOptions ( ) : Promise < LanguageClientOptions | undefined > {
169
184
// tslint:disable-next-line:no-any
170
185
const properties = new Map < string , any > ( ) ;
186
+ let interpreterData : InterpreterData | undefined ;
187
+ let pythonPath = '' ;
171
188
172
- // Microsoft Python code analysis engine needs full path to the interpreter
173
- const interpreterDataService = new InterpreterDataService ( context , this . services ) ;
174
- const interpreterData = await interpreterDataService . getInterpreterData ( ) ;
175
- if ( ! interpreterData ) {
176
- const appShell = this . services . get < IApplicationShell > ( IApplicationShell ) ;
177
- appShell . showWarningMessage ( 'Unable to determine path to Python interpreter. IntelliSense will be limited.' ) ;
189
+ try {
190
+ const interpreterDataService = new InterpreterDataService ( this . context , this . services ) ;
191
+ interpreterData = await interpreterDataService . getInterpreterData ( ) ;
192
+ } catch ( ex ) {
193
+ this . appShell . showErrorMessage ( 'Unable to determine path to the Python interpreter. IntelliSense will be limited.' ) ;
178
194
}
179
195
196
+ this . interpreterHash = interpreterData ? interpreterData . hash : '' ;
180
197
if ( interpreterData ) {
198
+ pythonPath = path . dirname ( interpreterData . path ) ;
181
199
// tslint:disable-next-line:no-string-literal
182
200
properties [ 'InterpreterPath' ] = interpreterData . path ;
183
201
// tslint:disable-next-line:no-string-literal
@@ -196,25 +214,17 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
196
214
}
197
215
198
216
// tslint:disable-next-line:no-string-literal
199
- properties [ 'DatabasePath' ] = path . join ( context . extensionPath , analysisEngineFolder ) ;
200
-
201
- const envProvider = this . services . get < IEnvironmentVariablesProvider > ( IEnvironmentVariablesProvider ) ;
202
- let pythonPath = ( await envProvider . getEnvironmentVariables ( ) ) . PYTHONPATH ;
203
- this . interpreterHash = interpreterData ? interpreterData . hash : '' ;
217
+ properties [ 'DatabasePath' ] = path . join ( this . context . extensionPath , analysisEngineFolder ) ;
204
218
205
219
// Make sure paths do not contain multiple slashes so file URIs
206
220
// in VS Code (Node.js) and in the language server (.NET) match.
207
221
// Note: for the language server paths separator is always ;
208
222
searchPaths = searchPaths . split ( path . delimiter ) . map ( p => path . normalize ( p ) ) . join ( ';' ) ;
209
- pythonPath = pythonPath ? path . normalize ( pythonPath ) : '' ;
210
-
211
223
// tslint:disable-next-line:no-string-literal
212
224
properties [ 'SearchPaths' ] = `${ searchPaths } ;${ pythonPath } ` ;
213
- const selector : string [ ] = [ PYTHON ] ;
214
225
215
- // const searchExcludes = workspace.getConfiguration('search').get('exclude', null);
216
- // const filesExcludes = workspace.getConfiguration('files').get('exclude', null);
217
- // const watcherExcludes = workspace.getConfiguration('files').get('watcherExclude', null);
226
+ const selector = [ { language : PYTHON , scheme : 'file' } ] ;
227
+ const excludeFiles = this . getExcludedFiles ( ) ;
218
228
219
229
// Options to control the language client
220
230
return {
@@ -236,8 +246,38 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
236
246
maxDocumentationTextLength : 0
237
247
} ,
238
248
asyncStartup : true ,
249
+ excludeFiles : excludeFiles ,
239
250
testEnvironment : isTestExecution ( )
240
251
}
241
252
} ;
242
253
}
254
+
255
+ private getExcludedFiles ( ) : string [ ] {
256
+ const list : string [ ] = [ '**/Lib/**' , '**/site-packages/**' ] ;
257
+ this . getVsCodeExcludeSection ( 'search.exclude' , list ) ;
258
+ this . getVsCodeExcludeSection ( 'files.exclude' , list ) ;
259
+ this . getVsCodeExcludeSection ( 'files.watcherExclude' , list ) ;
260
+ this . getPythonExcludeSection ( 'linting.ignorePatterns' , list ) ;
261
+ this . getPythonExcludeSection ( 'workspaceSymbols.exclusionPattern' , list ) ;
262
+ return list ;
263
+ }
264
+
265
+ private getVsCodeExcludeSection ( setting : string , list : string [ ] ) : void {
266
+ const states = this . workspace . getConfiguration ( setting , this . root ) ;
267
+ if ( states ) {
268
+ Object . keys ( states )
269
+ . filter ( k => ( k . indexOf ( '*' ) >= 0 || k . indexOf ( '/' ) >= 0 ) && states [ k ] )
270
+ . forEach ( p => list . push ( p ) ) ;
271
+ }
272
+ }
273
+
274
+ private getPythonExcludeSection ( setting : string , list : string [ ] ) : void {
275
+ const pythonSettings = this . configuration . getSettings ( this . root ) ;
276
+ const paths = pythonSettings && pythonSettings . linting ? pythonSettings . linting . ignorePatterns : undefined ;
277
+ if ( paths && Array . isArray ( paths ) ) {
278
+ paths
279
+ . filter ( p => p && p . length > 0 )
280
+ . forEach ( p => list . push ( p ) ) ;
281
+ }
282
+ }
243
283
}
0 commit comments