import './common/extensions' ; import { CallHierarchyIncomingCallsParams , CallHierarchyItem , CallHierarchyOutgoingCall , CallHierarchyOutgoingCallsParams , CallHierarchyPrepareParams , CancellationToken , CancellationTokenSource , CodeAction , CodeActionParams , Command , CompletionItem , CompletionList , CompletionParams , CompletionTriggerKind , ConfigurationItem , Connection , Declaration , DeclarationLink , Definition , DefinitionLink , Diagnostic , DiagnosticRelatedInformation , DiagnosticSeverity , DiagnosticTag , DidChangeConfigurationParams , DidChangeTextDocumentParams , DidChangeWatchedFilesNotification , DidChangeWatchedFilesParams , DidCloseTextDocumentParams , DidOpenTextDocumentParams , DocumentHighlight , DocumentHighlightParams , DocumentSymbol , DocumentSymbolParams , ExecuteCommandParams , HoverParams , InitializeParams , InitializeResult , Location , MarkupKind , ParameterInformation , ReferenceParams , RemoteWindow , RenameParams , SignatureHelp , SignatureHelpParams , SignatureHelpTriggerKind , SignatureInformation , SymbolInformation , TextDocumentPositionParams , TextDocumentSyncKind , WatchKind , WorkDoneProgressReporter , WorkspaceEdit , WorkspaceFolder , WorkspaceSymbolParams , } from 'vscode-languageserver' ; import { attachWorkDone , ResultProgressReporter } from 'vscode-languageserver/lib/common/progress' ; import { AnalysisResults } from './analyzer/analysis' ; import { BackgroundAnalysisProgram } from './analyzer/backgroundAnalysisProgram' ; import { ImportResolver } from './analyzer/importResolver' ; import { MaxAnalysisTime } from './analyzer/program' ; import { AnalyzerService , configFileNames } from './analyzer/service' ; import type { BackgroundAnalysisBase } from './backgroundAnalysisBase' ; import { CommandResult } from './commands/commandResult' ; import { CancelAfter , CancellationProvider } from './common/cancellationUtils' ; import { getNestedProperty } from './common/collectionUtils' ; import { DiagnosticSeverityOverrides , DiagnosticSeverityOverridesMap , getDiagnosticSeverityOverrides , } from './common/commandLineOptions' ; import { ConfigOptions , getDiagLevelDiagnosticRules } from './common/configOptions' ; import { ConsoleInterface , ConsoleWithLogLevel , LogLevel } from './common/console' ; import { createDeferred , Deferred } from './common/deferred' ; import { Diagnostic as AnalyzerDiagnostic , DiagnosticCategory } from './common/diagnostic' ; import { DiagnosticRule } from './common/diagnosticRules' ; import { LanguageServiceExtension } from './common/extensibility' ; import { FileSystem , FileWatcherEventType , FileWatcherProvider } from './common/fileSystem' ; import { Host } from './common/host' ; import { convertPathToUri } from './common/pathUtils' ; import { ProgressReporter , ProgressReportTracker } from './common/progressReporter' ; import { DocumentRange , Position } from './common/textRange' ; import { UriParser } from './common/uriParser' ; import { convertWorkspaceEdits } from './common/workspaceEditUtils' ; import { AnalyzerServiceExecutor } from './languageService/analyzerServiceExecutor' ; import { CompletionItemData , CompletionResults } from './languageService/completionProvider' ; import { DefinitionFilter } from './languageService/definitionProvider' ; import { convertToFlatSymbols , WorkspaceSymbolCallback } from './languageService/documentSymbolProvider' ; import { convertHoverResults } from './languageService/hoverProvider' ; import { ReferenceCallback } from './languageService/referencesProvider' ; import { Localizer , setLocaleOverride } from './localization/localize' ; import { PyrightFileSystem } from './pyrightFileSystem' ; import { WorkspaceMap } from './workspaceMap' ; export interface ServerSettings { venvPath ? : string | undefined ; pythonPath ? : string | undefined ; typeshedPath ? : string | undefined ; stubPath ? : string | undefined ; openFilesOnly ? : boolean | undefined ; typeCheckingMode ? : string | undefined ; useLibraryCodeForTypes ? : boolean | undefined ; disableLanguageServices ? : boolean | undefined ; disableOrganizeImports ? : boolean | undefined ; autoSearchPaths ? : boolean | undefined ; extraPaths ? : string [ ] | undefined ; watchForSourceChanges ? : boolean | undefined ; watchForLibraryChanges ? : boolean | undefined ; watchForConfigChanges ? : boolean | undefined ; diagnosticSeverityOverrides ? : DiagnosticSeverityOverridesMap | undefined ; logLevel ? : LogLevel | undefined ; autoImportCompletions ? : boolean | undefined ; indexing ? : boolean | undefined ; logTypeEvaluationTime ? : boolean | undefined ; typeEvaluationTimeThreshold ? : number | undefined ; } export interface WorkspaceServiceInstance { workspaceName : string ; rootPath : string ; rootUri : string ; serviceInstance : AnalyzerService ; disableLanguageServices : boolean ; disableOrganizeImports : boolean ; isInitialized : Deferred < boolean > ; } export interface MessageAction { title : string ; id : string ; } export interface WindowInterface { showErrorMessage ( message ) : void ; showErrorMessage ( message , ... actions ) : Promise < MessageAction | undefined > ; showWarningMessage ( message ) : void ; showWarningMessage ( message , ... actions ) : Promise < MessageAction | undefined > ; showInformationMessage ( message ) : void ; showInformationMessage ( message , ... actions ) : Promise < MessageAction | undefined > ; } export interface LanguageServerInterface { getWorkspaceForFile ( filePath ) : Promise < WorkspaceServiceInstance > ; getSettings ( workspace ) : Promise < ServerSettings > ; createBackgroundAnalysis ( ) : BackgroundAnalysisBase | undefined ; reanalyze ( ) : void ; restart ( ) : void ; decodeTextDocumentUri ( uriString ) : string ; readonly rootPath : string ; readonly console : ConsoleInterface ; readonly window : WindowInterface ; readonly fs : FileSystem ; readonly supportAdvancedEdits : boolean ; } export interface ServerOptions { productName : string ; rootDirectory : string ; version : string ; workspaceMap : WorkspaceMap ; fileSystem : FileSystem ; fileWatcherProvider : FileWatcherProvider ; cancellationProvider : CancellationProvider ; extension ? : LanguageServiceExtension ; maxAnalysisTimeInForeground ? : MaxAnalysisTime ; disableChecker ? : boolean ; supportedCommands ? : string [ ] ; supportedCodeActions ? : string [ ] ; uriParser : UriParser ; } interface ClientCapabilities { hasConfigurationCapability : boolean ; hasVisualStudioExtensionsCapability : boolean ; hasWorkspaceFoldersCapability : boolean ; hasWatchFileCapability : boolean ; hasActiveParameterCapability : boolean ; hasSignatureLabelOffsetCapability : boolean ; hasHierarchicalDocumentSymbolCapability : boolean ; hasWindowProgressCapability : boolean ; hasGoToDeclarationCapability : boolean ; hasDocumentChangeCapability : boolean ; hasDocumentAnnotationCapability : boolean ; hoverContentFormat : MarkupKind ; completionDocFormat : MarkupKind ; completionSupportsSnippet : boolean ; signatureDocFormat : MarkupKind ; supportsDeprecatedDiagnosticTag : boolean ; supportsUnnecessaryDiagnosticTag : boolean ; completionItemResolveSupportsAdditionalTextEdits : boolean ; } const nullProgressReporter = attachWorkDone ( undefined as any , undefined ) ; export abstract class LanguageServerBase implements LanguageServerInterface { protected _defaultClientConfig : any ; protected _workspaceMap : WorkspaceMap ; protected _fileWatcherProvider : FileWatcherProvider ; private _pendingFindAllRefsCancellationSource : CancellationTokenSource | undefined ; private _pendingCommandCancellationSource : CancellationTokenSource | undefined ; private _progressReporter : ProgressReporter ; private _lastTriggerKind : CompletionTriggerKind | undefined = CompletionTriggerKind . Invoked ; rootPath = '' ; protected client : ClientCapabilities = { hasConfigurationCapability : false , hasVisualStudioExtensionsCapability : false , hasWorkspaceFoldersCapability : false , hasWatchFileCapability : false , hasActiveParameterCapability : false , hasSignatureLabelOffsetCapability : false , hasHierarchicalDocumentSymbolCapability : false , hasWindowProgressCapability : false , hasGoToDeclarationCapability : false , hasDocumentChangeCapability : false , hasDocumentAnnotationCapability : false , hoverContentFormat : MarkupKind . PlainText , completionDocFormat : MarkupKind . PlainText , completionSupportsSnippet : false , signatureDocFormat : MarkupKind . PlainText , supportsDeprecatedDiagnosticTag : false , supportsUnnecessaryDiagnosticTag : false , completionItemResolveSupportsAdditionalTextEdits : false , } ; fs : FileSystem ; protected _uriParser : UriParser ; constructor ( protected _serverOptions , protected _connection , readonly console ) { ( global as any ) . __rootDirectory = _serverOptions . rootDirectory ; this . console . info ( `${ _serverOptions . productName } language server ${ _serverOptions . version && _serverOptions . version + ' ' }starting` ) ; this . console . info ( `Server root directory: ${ _serverOptions . rootDirectory }` ) ; this . _workspaceMap = this . _serverOptions . workspaceMap ; this . _fileWatcherProvider = this . _serverOptions . fileWatcherProvider ; this . fs = this . _serverOptions . fileSystem ; this . _uriParser = this . _serverOptions . uriParser ; const moduleDirectory = this . fs . getModulePath ( ) ; if ( moduleDirectory ) { this . fs . chdir ( moduleDirectory ) ; } this . setupConnection ( _serverOptions . supportedCommands ?? [ ] , _serverOptions . supportedCodeActions ?? [ ] ) ; this . _progressReporter = new ProgressReportTracker ( this . createProgressReporter ( ) ) ; this . _connection . listen ( ) ; } decodeTextDocumentUri ( uriString ) { return this . _uriParser . decodeTextDocumentUri ( uriString ) ; } abstract createBackgroundAnalysis ( ) ; protected abstract executeCommand ( params , token ) ; protected abstract isLongRunningCommand ( command ) ; protected abstract executeCodeAction ( params , token ) ; abstract getSettings ( workspace ) ; protected async getConfiguration ( scopeUri , section ) { if ( this . client . hasConfigurationCapability ) { const item = { } ; if ( scopeUri !== undefined ) { item . scopeUri = scopeUri ; } if ( section !== undefined ) { item . section = section ; } return this . _connection . workspace . getConfiguration ( item ) ; } if ( this . _defaultClientConfig ) { return getNestedProperty ( this . _defaultClientConfig , section ) ; } return undefined ; } protected isOpenFilesOnly ( diagnosticMode ) { return diagnosticMode !== 'workspace' ; } protected getSeverityOverrides ( value ) { const enumValue = value as DiagnosticSeverityOverrides ; if ( getDiagnosticSeverityOverrides ( ) . includes ( enumValue ) ) { return enumValue ; } return undefined ; } protected getDiagnosticRuleName ( value ) { const enumValue = value as DiagnosticRule ; if ( getDiagLevelDiagnosticRules ( ) . includes ( enumValue ) ) { return enumValue ; } return undefined ; } protected abstract createHost ( ) ; protected abstract createImportResolver ( fs , options , host ) ; protected createBackgroundAnalysisProgram ( console , configOptions , importResolver , extension ? , backgroundAnalysis ? , maxAnalysisTime ? ) { return new BackgroundAnalysisProgram ( console , configOptions , importResolver , extension , backgroundAnalysis , maxAnalysisTime ) ; } protected setExtension ( extension ) { this . _serverOptions . extension = extension ; } get window ( ) : RemoteWindow { return this . _connection . window ; } get supportAdvancedEdits ( ) : boolean { return this . client . hasDocumentChangeCapability && this . client . hasDocumentAnnotationCapability ; } createAnalyzerService ( name ) { this . console . log ( `Starting service instance "${ name }"` ) ; const service = new AnalyzerService ( name , this . fs , this . console , this . createHost . bind ( this ) , this . createImportResolver . bind ( this ) , undefined , this . _serverOptions . extension , this . createBackgroundAnalysis ( ) , this . _serverOptions . maxAnalysisTimeInForeground , this . createBackgroundAnalysisProgram . bind ( this ) , this . _serverOptions . cancellationProvider ) ; service . setCompletionCallback ( ( results ) => this . onAnalysisCompletedHandler ( results ) ) ; return service ; } async getWorkspaceForFile ( filePath ) { const workspace = this . _workspaceMap . getWorkspaceForFile ( this , filePath ) ; await workspace . isInitialized . promise ; return workspace ; } reanalyze ( ) { this . _workspaceMap . forEach ( ( workspace ) => { workspace . serviceInstance . invalidateAndForceReanalysis ( ) ; } ) ; } restart ( ) { this . _workspaceMap . forEach ( ( workspace ) => { workspace . serviceInstance . restart ( ) ; } ) ; } protected setupConnection ( supportedCommands , supportedCodeActions ) { this . _connection . onInitialize ( ( params ) => this . initialize ( params , supportedCommands , supportedCodeActions ) ) ; this . _connection . onInitialized ( ( ) => this . onInitialized ( ) ) ; this . _connection . onDidChangeConfiguration ( ( params ) => this . onDidChangeConfiguration ( params ) ) ; this . _connection . onCodeAction ( ( params , token ) => this . executeCodeAction ( params , token ) ) ; this . _connection . onDefinition ( async ( params , token ) => this . onDefinition ( params , token ) ) ; this . _connection . onDeclaration ( async ( params , token ) => this . onDeclaration ( params , token ) ) ; this . _connection . onTypeDefinition ( async ( params , token ) => this . onTypeDefinition ( params , token ) ) ; this . _connection . onReferences ( async ( params , token , workDoneReporter , resultReporter ) => this . onReferences ( params , token , workDoneReporter , resultReporter ) ) ; this . _connection . onDocumentSymbol ( async ( params , token ) => this . onDocumentSymbol ( params , token ) ) ; this . _connection . onWorkspaceSymbol ( async ( params , token , _ , resultReporter ) => this . onWorkspaceSymbol ( params , token , resultReporter ) ) ; this . _connection . onHover ( async ( params , token ) => this . onHover ( params , token ) ) ; this . _connection . onDocumentHighlight ( async ( params , token ) => this . onDocumentHighlight ( params , token ) ) ; this . _connection . onSignatureHelp ( async ( params , token ) => this . onSignatureHelp ( params , token ) ) ; this . _connection . onCompletion ( ( params , token ) => this . onCompletion ( params , token ) ) ; this . _connection . onCompletionResolve ( async ( params , token ) => this . onCompletionResolve ( params , token ) ) ; this . _connection . onRenameRequest ( async ( params , token ) => this . onRenameRequest ( params , token ) ) ; const callHierarchy = this . _connection . languages . callHierarchy ; callHierarchy . onPrepare ( async ( params , token ) => this . onPrepare ( params , token ) ) ; callHierarchy . onIncomingCalls ( async ( params , token ) => this . onIncomingCalls ( params , token ) ) ; callHierarchy . onOutgoingCalls ( async ( params , token ) => this . onOutgoingCalls ( params , token ) ) ; this . _connection . onDidOpenTextDocument ( async ( params ) => this . onDidOpenTextDocument ( params ) ) ; this . _connection . onDidChangeTextDocument ( async ( params ) => this . onDidChangeTextDocument ( params ) ) ; this . _connection . onDidCloseTextDocument ( async ( params ) => this . onDidCloseTextDocument ( params ) ) ; this . _connection . onDidChangeWatchedFiles ( ( params ) => this . onDidChangeWatchedFiles ( params ) ) ; this . _connection . onExecuteCommand ( async ( params , token , reporter ) => this . onExecuteCommand ( params , token , reporter ) ) ; } protected initialize ( params , supportedCommands , supportedCodeActions ) { if ( params . locale ) { setLocaleOverride ( params . locale ) ; } this . rootPath = params . rootPath || '' ; const capabilities = params . capabilities ; this . client . hasConfigurationCapability = ! ! capabilities . workspace ?. configuration ; this . client . hasWatchFileCapability = ! ! capabilities . workspace ?. didChangeWatchedFiles ?. dynamicRegistration ; this . client . hasWorkspaceFoldersCapability = ! ! capabilities . workspace ?. workspaceFolders ; this . client . hasVisualStudioExtensionsCapability = ! ! ( capabilities as any ) . supportsVisualStudioExtensions ; this . client . hasActiveParameterCapability = ! ! capabilities . textDocument ?. signatureHelp ?. signatureInformation ?. activeParameterSupport ; this . client . hasSignatureLabelOffsetCapability = ! ! capabilities . textDocument ?. signatureHelp ?. signatureInformation ?. parameterInformation ?. labelOffsetSupport ; this . client . hasHierarchicalDocumentSymbolCapability = ! ! capabilities . textDocument ?. documentSymbol ?. hierarchicalDocumentSymbolSupport ; this . client . hasDocumentChangeCapability = ! ! capabilities . workspace ?. workspaceEdit ?. documentChanges && ! ! capabilities . workspace . workspaceEdit ?. resourceOperations ; this . client . hasDocumentAnnotationCapability = ! ! capabilities . workspace ?. workspaceEdit ?. changeAnnotationSupport ; this . client . hoverContentFormat = this . _getCompatibleMarkupKind ( capabilities . textDocument ?. hover ?. contentFormat ) ; this . client . completionDocFormat = this . _getCompatibleMarkupKind ( capabilities . textDocument ?. completion ?. completionItem ?. documentationFormat ) ; this . client . completionSupportsSnippet = ! ! capabilities . textDocument ?. completion ?. completionItem ?. snippetSupport ; this . client . signatureDocFormat = this . _getCompatibleMarkupKind ( capabilities . textDocument ?. signatureHelp ?. signatureInformation ?. documentationFormat ) ; const supportedDiagnosticTags = capabilities . textDocument ?. publishDiagnostics ?. tagSupport ?. valueSet || [ ] ; this . client . supportsUnnecessaryDiagnosticTag = supportedDiagnosticTags . some ( ( tag ) => tag === DiagnosticTag . Unnecessary ) ; this . client . supportsDeprecatedDiagnosticTag = supportedDiagnosticTags . some ( ( tag ) => tag === DiagnosticTag . Deprecated ) ; this . client . hasWindowProgressCapability = ! ! capabilities . window ?. workDoneProgress ; this . client . hasGoToDeclarationCapability = ! ! capabilities . textDocument ?. declaration ; this . client . completionItemResolveSupportsAdditionalTextEdits = ! ! capabilities . textDocument ?. completion ?. completionItem ?. resolveSupport ?. properties . some ( ( p ) => p === 'additionalTextEdits' ) ; if ( params . workspaceFolders ) { params . workspaceFolders . forEach ( ( folder ) => { const path = this . _uriParser . decodeTextDocumentUri ( folder . uri ) ; this . _workspaceMap . set ( path , this . createWorkspaceServiceInstance ( folder , path ) ) ; } ) ; } else if ( params . rootPath ) { this . _workspaceMap . set ( params . rootPath , this . createWorkspaceServiceInstance ( undefined , params . rootPath ) ) ; } const result = { capabilities : { textDocumentSync : TextDocumentSyncKind . Incremental , definitionProvider : { workDoneProgress : true } , declarationProvider : { workDoneProgress : true } , typeDefinitionProvider : { workDoneProgress : true } , referencesProvider : { workDoneProgress : true } , documentSymbolProvider : { workDoneProgress : true } , workspaceSymbolProvider : { workDoneProgress : true } , hoverProvider : { workDoneProgress : true } , documentHighlightProvider : { workDoneProgress : true } , renameProvider : { workDoneProgress : true } , completionProvider : { triggerCharacters : this . client . hasVisualStudioExtensionsCapability ? [ '.' , '[' , '@' ] : [ '.' , '[' ] , resolveProvider : true , workDoneProgress : true , } , signatureHelpProvider : { triggerCharacters : [ '(' , ',' , ')' ] , workDoneProgress : true , } , codeActionProvider : { codeActionKinds : supportedCodeActions , workDoneProgress : true , } , executeCommandProvider : { commands : supportedCommands , workDoneProgress : true , } , callHierarchyProvider : true , } , } ; return result ; } protected onInitialized ( ) { if ( this . client . hasWorkspaceFoldersCapability ) { this . _connection . workspace . onDidChangeWorkspaceFolders ( ( event ) => { event . removed . forEach ( ( workspace ) => { const rootPath = this . _uriParser . decodeTextDocumentUri ( workspace . uri ) ; this . _workspaceMap . delete ( rootPath ) ; } ) ; event . added . forEach ( async ( workspace ) => { const rootPath = this . _uriParser . decodeTextDocumentUri ( workspace . uri ) ; const newWorkspace = this . createWorkspaceServiceInstance ( workspace , rootPath ) ; this . _workspaceMap . set ( rootPath , newWorkspace ) ; await this . updateSettingsForWorkspace ( newWorkspace ) ; } ) ; } ) ; } if ( this . client . hasWatchFileCapability ) { this . _connection . client . register ( DidChangeWatchedFilesNotification . type , { watchers : [ ... configFileNames . map ( ( fileName ) => { return { globPattern : `**/${ fileName }` , kind : WatchKind . Create | WatchKind . Change | WatchKind . Delete , } ; } ) , { globPattern : '**' , kind : WatchKind . Create | WatchKind . Change | WatchKind . Delete , } , ] , } ) ; } } protected onDidChangeConfiguration ( params ) { this . console . log ( `Received updated settings` ) ; if ( params ?. settings ) { this . _defaultClientConfig = params ?. settings ; } this . updateSettingsForAllWorkspaces ( ) ; } protected async onDefinition ( params , token ) { return this . getDefinitions ( params , token , this . client . hasGoToDeclarationCapability ? DefinitionFilter . PreferSource : DefinitionFilter . All , ( workspace , filePath , position , filter , token ) => workspace . serviceInstance . getDefinitionForPosition ( filePath , position , filter , token ) ) ; } protected async onDeclaration ( params , token ) { return this . getDefinitions ( params , token , this . client . hasGoToDeclarationCapability ? DefinitionFilter . PreferStubs : DefinitionFilter . All , ( workspace , filePath , position , filter , token ) => workspace . serviceInstance . getDefinitionForPosition ( filePath , position , filter , token ) ) ; } protected async onTypeDefinition ( params , token ) { return this . getDefinitions ( params , token , DefinitionFilter . All , ( workspace , filePath , position , _ , token ) => workspace . serviceInstance . getTypeDefinitionForPosition ( filePath , position , token ) ) ; } protected async getDefinitions ( params , token , filter , getDefinitionsFunc ) { this . recordUserInteractionTime ( ) ; const { filePath , position } = this . _uriParser . decodeTextDocumentPosition ( params . textDocument , params . position ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; if ( workspace . disableLanguageServices ) { return undefined ; } const locations = getDefinitionsFunc ( workspace , filePath , position , filter , token ) ; if ( ! locations ) { return undefined ; } return locations . filter ( ( loc ) => ! this . fs . isInZipOrEgg ( loc . path ) ) . map ( ( loc ) => Location . create ( convertPathToUri ( this . fs , loc . path ) , loc . range ) ) ; } protected async onReferences ( params , token , workDoneReporter , resultReporter ) { if ( this . _pendingFindAllRefsCancellationSource ) { this . _pendingFindAllRefsCancellationSource . cancel ( ) ; this . _pendingFindAllRefsCancellationSource = undefined ; } const progress = await this . _getProgressReporter ( workDoneReporter , Localizer . CodeAction . findingReferences ( ) , token ) ; const source = progress . source ; this . _pendingFindAllRefsCancellationSource = source ; try { const { filePath , position } = this . _uriParser . decodeTextDocumentPosition ( params . textDocument , params . position ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; if ( workspace . disableLanguageServices ) { return ; } const convert = ( locs ) : Location [ ] => { return locs . filter ( ( loc ) => ! this . fs . isInZipOrEgg ( loc . path ) ) . map ( ( loc ) => Location . create ( convertPathToUri ( this . fs , loc . path ) , loc . range ) ) ; } ; const locations = [ ] ; const reporter = resultReporter ? ( locs ) => resultReporter . report ( convert ( locs ) ) : ( locs ) => locations . push ( ... convert ( locs ) ) ; workspace . serviceInstance . reportReferencesForPosition ( filePath , position , params . context . includeDeclaration , reporter , source . token ) ; return locations ; } finally { progress . reporter . done ( ) ; source . dispose ( ) ; } } protected async onDocumentSymbol ( params , token ) { this . recordUserInteractionTime ( ) ; const filePath = this . _uriParser . decodeTextDocumentUri ( params . textDocument . uri ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; if ( workspace . disableLanguageServices ) { return undefined ; } const symbolList = [ ] ; workspace . serviceInstance . addSymbolsForDocument ( filePath , symbolList , token ) ; if ( this . client . hasHierarchicalDocumentSymbolCapability ) { return symbolList ; } return convertToFlatSymbols ( params . textDocument . uri , symbolList ) ; } protected async onWorkspaceSymbol ( params , token , resultReporter ) { const symbolList = [ ] ; const reporter = resultReporter ? ( symbols ) => resultReporter . report ( symbols ) : ( symbols ) => symbolList . push ( ... symbols ) ; for ( const workspace of this . _workspaceMap . values ( ) ) { await workspace . isInitialized . promise ; if ( ! workspace . disableLanguageServices ) { workspace . serviceInstance . reportSymbolsForWorkspace ( params . query , reporter , token ) ; } } return symbolList ; } protected async onHover ( params , token ) { const { filePath , position } = this . _uriParser . decodeTextDocumentPosition ( params . textDocument , params . position ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; const hoverResults = workspace . serviceInstance . getHoverForPosition ( filePath , position , this . client . hoverContentFormat , token ) ; return convertHoverResults ( this . client . hoverContentFormat , hoverResults ) ; } protected async onDocumentHighlight ( params , token ) { const { filePath , position } = this . _uriParser . decodeTextDocumentPosition ( params . textDocument , params . position ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; return workspace . serviceInstance . getDocumentHighlight ( filePath , position , token ) ; } protected async onSignatureHelp ( params , token ) { const { filePath , position } = this . _uriParser . decodeTextDocumentPosition ( params . textDocument , params . position ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; if ( workspace . disableLanguageServices ) { return ; } const signatureHelpResults = workspace . serviceInstance . getSignatureHelpForPosition ( filePath , position , this . client . signatureDocFormat , token ) ; if ( ! signatureHelpResults ) { return undefined ; } const signatures = signatureHelpResults . signatures . map ( ( sig ) => { let paramInfo = [ ] ; if ( sig . parameters ) { paramInfo = sig . parameters . map ( ( param ) => ParameterInformation . create ( this . client . hasSignatureLabelOffsetCapability ? [ param . startOffset , param . endOffset ] : param . text , param . documentation ) ) ; } const sigInfo = SignatureInformation . create ( sig . label , undefined , ... paramInfo ) ; if ( sig . documentation !== undefined ) { sigInfo . documentation = sig . documentation ; } if ( sig . activeParameter !== undefined ) { sigInfo . activeParameter = sig . activeParameter ; } return sigInfo ; } ) ; const isActive = ( sig ) => sig . activeParameter !== undefined || ( ! signatureHelpResults . callHasParameters && ! sig . parameters ?. length ) ; let activeSignature = signatures . findIndex ( isActive ) ; if ( activeSignature === - 1 ) { activeSignature = null ; } let activeParameter = activeSignature !== null ? signatures [ activeSignature ] . activeParameter ! : null ; if ( params . context ?. isRetrigger && params . context . triggerKind !== SignatureHelpTriggerKind . Invoked ) { const prevActiveSignature = params . context . activeSignatureHelp ?. activeSignature ?? null ; if ( prevActiveSignature !== null && prevActiveSignature < signatures . length ) { const sig = signatures [ prevActiveSignature ] ; if ( isActive ( sig ) ) { activeSignature = prevActiveSignature ; activeParameter = sig . activeParameter ?? null ; } } } if ( this . client . hasActiveParameterCapability || activeSignature === null ) { activeParameter = Math . max ( ... signatures . map ( ( s ) => s . parameters ?. length ?? 0 ) ) ; } return { signatures , activeSignature , activeParameter } ; } protected async onCompletion ( params , token ) { const completionIncomplete = this . _lastTriggerKind !== CompletionTriggerKind . TriggerForIncompleteCompletions || params . context ?. triggerKind !== CompletionTriggerKind . TriggerForIncompleteCompletions ; this . _lastTriggerKind = params . context ?. triggerKind ; const { filePath , position } = this . _uriParser . decodeTextDocumentPosition ( params . textDocument , params . position ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; if ( workspace . disableLanguageServices ) { return ; } const completions = await this . getWorkspaceCompletionsForPosition ( workspace , filePath , position , workspace . rootPath , token ) ; if ( completions && completions . completionList ) { completions . completionList . isIncomplete = completionIncomplete ; } return completions ?. completionList ; } protected async onCompletionResolve ( params , token ) { const completionItemData = params . data as CompletionItemData ; if ( completionItemData && completionItemData . filePath ) { const workspace = await this . getWorkspaceForFile ( completionItemData . workspacePath ) ; this . resolveWorkspaceCompletionItem ( workspace , completionItemData . filePath , params , token ) ; } return params ; } protected async onRenameRequest ( params , token ) { const { filePath , position } = this . _uriParser . decodeTextDocumentPosition ( params . textDocument , params . position ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; if ( workspace . disableLanguageServices ) { return ; } const editActions = workspace . serviceInstance . renameSymbolAtPosition ( filePath , position , params . newName , workspace . rootPath === '' , token ) ; if ( ! editActions ) { return undefined ; } return convertWorkspaceEdits ( this . fs , editActions ) ; } protected async onPrepare ( params , token ) { const { filePath , position } = this . _uriParser . decodeTextDocumentPosition ( params . textDocument , params . position ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; if ( workspace . disableLanguageServices ) { return null ; } const callItem = workspace . serviceInstance . getCallForPosition ( filePath , position , token ) || null ; if ( ! callItem ) { return null ; } if ( this . fs . isInZipOrEgg ( callItem . uri ) ) { return null ; } callItem . uri = convertPathToUri ( this . fs , callItem . uri ) ; return [ callItem ] ; } protected async onIncomingCalls ( params , token ) { const { filePath , position } = this . _uriParser . decodeTextDocumentPosition ( params . item , params . item . range . start ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; if ( workspace . disableLanguageServices ) { return null ; } let callItems = workspace . serviceInstance . getIncomingCallsForPosition ( filePath , position , token ) || null ; if ( ! callItems || callItems . length === 0 ) { return null ; } callItems = callItems . filter ( ( item ) => ! this . fs . isInZipOrEgg ( item . from . uri ) ) ; callItems . forEach ( ( item ) => { item . from . uri = convertPathToUri ( this . fs , item . from . uri ) ; } ) ; return callItems ; } protected async onOutgoingCalls ( params , token ) { const { filePath , position } = this . _uriParser . decodeTextDocumentPosition ( params . item , params . item . range . start ) ; const workspace = await this . getWorkspaceForFile ( filePath ) ; if ( workspace . disableLanguageServices ) { return null ; } let callItems = workspace . serviceInstance . getOutgoingCallsForPosition ( filePath , position , token ) || null ; if ( ! callItems || callItems . length === 0 ) { return null ; } callItems = callItems . filter ( ( item ) => ! this . fs . isInZipOrEgg ( item . to . uri ) ) ; callItems . forEach ( ( item ) => { item . to . uri = convertPathToUri ( this . fs , item . to . uri ) ; } ) ; return callItems ; } protected async onDidOpenTextDocument ( params ) { const filePath = this . _uriParser . decodeTextDocumentUri ( params . textDocument . uri ) ; if ( ! ( this . fs as PyrightFileSystem ) . addUriMap ( params . textDocument . uri , filePath ) ) { return ; } const workspace = await this . getWorkspaceForFile ( filePath ) ; workspace . serviceInstance . setFileOpened ( filePath , params . textDocument . version , params . textDocument . text ) ; } protected async onDidChangeTextDocument ( params ) { this . recordUserInteractionTime ( ) ; const filePath = this . _uriParser . decodeTextDocumentUri ( params . textDocument . uri ) ; if ( ! ( this . fs as PyrightFileSystem ) . hasUriMapEntry ( params . textDocument . uri , filePath ) ) { return ; } const workspace = await this . getWorkspaceForFile ( filePath ) ; workspace . serviceInstance . updateOpenFileContents ( filePath , params . textDocument . version , params . contentChanges ) ; } protected async onDidCloseTextDocument ( params ) { const filePath = this . _uriParser . decodeTextDocumentUri ( params . textDocument . uri ) ; if ( ! ( this . fs as PyrightFileSystem ) . removeUriMap ( params . textDocument . uri , filePath ) ) { return ; } const workspace = await this . getWorkspaceForFile ( filePath ) ; workspace . serviceInstance . setFileClosed ( filePath ) ; } protected onDidChangeWatchedFiles ( params ) { params . changes . forEach ( ( change ) => { const filePath = this . _uriParser . decodeTextDocumentUri ( change . uri ) ; const eventType = change . type === 1 ? 'add' : 'change' ; this . _fileWatcherProvider . onFileChange ( eventType , filePath ) ; } ) ; } protected async onExecuteCommand ( params , token , reporter ) { if ( this . _pendingCommandCancellationSource ) { this . _pendingCommandCancellationSource . cancel ( ) ; this . _pendingCommandCancellationSource = undefined ; } const executeCommand = async ( token ) => { const result = await this . executeCommand ( params , token ) ; if ( WorkspaceEdit . is ( result ) ) { this . _connection . workspace . applyEdit ( { label : `Command '${ params . command }'` , edit : result } ) ; } if ( CommandResult . is ( result ) ) { await this . _connection . workspace . applyEdit ( { label : result . label , edit : result . edits } ) ; } return result ; } ; if ( this . isLongRunningCommand ( params . command ) ) { const progress = await this . _getProgressReporter ( reporter , Localizer . CodeAction . executingCommand ( ) , token ) ; const source = progress . source ; this . _pendingCommandCancellationSource = source ; try { const result = await executeCommand ( source . token ) ; return result ; } finally { progress . reporter . done ( ) ; source . dispose ( ) ; } } else { const result = await executeCommand ( token ) ; return result ; } } protected resolveWorkspaceCompletionItem ( workspace , filePath , item , token ) { workspace . serviceInstance . resolveCompletionItem ( filePath , item , this . getCompletionOptions ( ) , undefined , token ) ; } protected getWorkspaceCompletionsForPosition ( workspace , filePath , position , workspacePath , token ) { return workspace . serviceInstance . getCompletionsForPosition ( filePath , position , workspacePath , this . getCompletionOptions ( ) , undefined , token ) ; } updateSettingsForAllWorkspaces ( ) { this . _workspaceMap . forEach ( ( workspace ) => { this . updateSettingsForWorkspace ( workspace ) . ignoreErrors ( ) ; } ) ; } protected getCompletionOptions ( ) { return { format : this . client . completionDocFormat , snippet : this . client . completionSupportsSnippet , lazyEdit : this . client . completionItemResolveSupportsAdditionalTextEdits , } ; } protected createWorkspaceServiceInstance ( workspace , rootPath ) { return { workspaceName : workspace ?. name ?? '' , rootPath , rootUri : workspace ?. uri ?? '' , serviceInstance : this . createAnalyzerService ( workspace ?. name ?? rootPath ) , disableLanguageServices : false , disableOrganizeImports : false , isInitialized : createDeferred < boolean > ( ) , } ; } protected onAnalysisCompletedHandler ( results ) { results . diagnostics . forEach ( ( fileDiag ) => { if ( this . fs . isInZipOrEgg ( fileDiag . filePath ) ) { return ; } this . _connection . sendDiagnostics ( { uri : convertPathToUri ( this . fs , fileDiag . filePath ) , version : fileDiag . version , diagnostics : this . _convertDiagnostics ( fileDiag . diagnostics ) , } ) ; ( this . fs as PyrightFileSystem ) . pendingRequest ( fileDiag . filePath , fileDiag . diagnostics . length > 0 ) ; } ) ; if ( ! this . _progressReporter . isEnabled ( results ) ) { this . _progressReporter . end ( ) ; return ; } if ( results . filesRequiringAnalysis > 0 ) { this . _progressReporter . begin ( ) ; const progressMessage = results . filesRequiringAnalysis === 1 ? Localizer . CodeAction . filesToAnalyzeOne ( ) : Localizer . CodeAction . filesToAnalyzeCount ( ) . format ( { count : results . filesRequiringAnalysis , } ) ; this . _progressReporter . report ( progressMessage ) ; } else { this . _progressReporter . end ( ) ; } } async updateSettingsForWorkspace ( workspace , serverSettings ? ) { serverSettings = serverSettings ?? ( await this . getSettings ( workspace ) ) ; ( this . console as ConsoleWithLogLevel ) . level = serverSettings . logLevel ?? LogLevel . Info ; this . updateOptionsAndRestartService ( workspace , serverSettings ) ; workspace . disableLanguageServices = ! ! serverSettings . disableLanguageServices ; workspace . disableOrganizeImports = ! ! serverSettings . disableOrganizeImports ; workspace . isInitialized . resolve ( true ) ; } updateOptionsAndRestartService ( workspace , serverSettings , typeStubTargetImportName ? ) { AnalyzerServiceExecutor . runWithOptions ( this . rootPath , workspace , serverSettings , typeStubTargetImportName ) ; } protected convertLogLevel ( logLevelValue ? ) { if ( ! logLevelValue ) { return LogLevel . Info ; } switch ( logLevelValue . toLowerCase ( ) ) { case 'error' : return LogLevel . Error ; case 'warning' : return LogLevel . Warn ; case 'information' : return LogLevel . Info ; case 'trace' : return LogLevel . Log ; default : return LogLevel . Info ; } } private _getCompatibleMarkupKind ( clientSupportedFormats ) { const serverSupportedFormats = [ MarkupKind . PlainText , MarkupKind . Markdown ] ; for ( const format of clientSupportedFormats ?? [ ] ) { if ( serverSupportedFormats . includes ( format ) ) { return format ; } } return MarkupKind . PlainText ; } private async _getProgressReporter ( reporter , title , token ) { if ( reporter . constructor !== nullProgressReporter . constructor ) { return { reporter : reporter , source : CancelAfter ( token ) } ; } const serverInitiatedReporter = await this . _connection . window . createWorkDoneProgress ( ) ; serverInitiatedReporter . begin ( title , undefined , undefined , true ) ; return { reporter : serverInitiatedReporter , source : CancelAfter ( token , serverInitiatedReporter . token ) , } ; } private _convertDiagnostics ( diags ) { const convertedDiags = [ ] ; diags . forEach ( ( diag ) => { const severity = convertCategoryToSeverity ( diag . category ) ; const rule = diag . getRule ( ) ; const vsDiag = Diagnostic . create ( diag . range , diag . message , severity , rule , this . _serverOptions . productName ) ; if ( diag . category === DiagnosticCategory . UnusedCode ) { vsDiag . tags = [ DiagnosticTag . Unnecessary ] ; vsDiag . severity = DiagnosticSeverity . Hint ; if ( ! this . client . supportsUnnecessaryDiagnosticTag ) { return ; } } else if ( diag . category === DiagnosticCategory . Deprecated ) { vsDiag . tags = [ DiagnosticTag . Deprecated ] ; vsDiag . severity = DiagnosticSeverity . Hint ; if ( ! this . client . supportsDeprecatedDiagnosticTag ) { return ; } } if ( rule ) { const ruleDocUrl = this . getDocumentationUrlForDiagnosticRule ( rule ) ; if ( ruleDocUrl ) { vsDiag . codeDescription = { href : ruleDocUrl , } ; } } const relatedInfo = diag . getRelatedInfo ( ) ; if ( relatedInfo . length > 0 ) { vsDiag . relatedInformation = relatedInfo . filter ( ( info ) => ! this . fs . isInZipOrEgg ( info . filePath ) ) . map ( ( info ) => DiagnosticRelatedInformation . create ( Location . create ( convertPathToUri ( this . fs , info . filePath ) , info . range ) , info . message ) ) ; } convertedDiags . push ( vsDiag ) ; } ) ; function convertCategoryToSeverity ( category ) { switch ( category ) { case DiagnosticCategory . Error : return DiagnosticSeverity . Error ; case DiagnosticCategory . Warning : return DiagnosticSeverity . Warning ; case DiagnosticCategory . Information : return DiagnosticSeverity . Information ; case DiagnosticCategory . UnusedCode : case DiagnosticCategory . Deprecated : return DiagnosticSeverity . Hint ; } } return convertedDiags ; } protected recordUserInteractionTime ( ) { this . _workspaceMap . forEach ( ( workspace ) => { workspace . serviceInstance . recordUserInteractionTime ( ) ; } ) ; } protected getDocumentationUrlForDiagnosticRule ( rule ) { return 'https://github.com/microsoft/pyright/blob/main/docs/configuration.md' ; } protected abstract createProgressReporter ( ) ; protected expandPathVariables ( rootPath , value ) { const regexp = /\$\{(.*?)\}/g ; return value . replace ( regexp , ( match , name ) => { const trimmedName = name . trim ( ) ; if ( trimmedName === 'workspaceFolder' ) { return rootPath ; } if ( trimmedName === 'env:HOME' && process . env . HOME !== undefined ) { return process . env . HOME ; } return match ; } ) ; } }