import * as path from 'path' ; import { ExecutionPathProps } from 'pytea/service/executionPaths' ; import { commands , ExtensionContext , extensions , OutputChannel , TextEditor , TextEditorEdit , Uri } from 'vscode' ; import { CancellationToken , ConfigurationParams , ConfigurationRequest , DidChangeConfigurationNotification , HandlerResult , LanguageClient , LanguageClientOptions , ResponseError , ServerOptions , TransportKind , } from 'vscode-languageclient/node' ; import { isThenable } from 'pyright-internal/common/core' ; import { FileBasedCancellationStrategy } from './cancellationUtils' ; import { PyteaCommands } from './commands' ; import { PathManager } from './pathTreeProvider' ; let cancellationStrategy ; const pythonPathChangedListenerMap = new Map < string , string > ( ) ; export function activate ( context ) { cancellationStrategy = new FileBasedCancellationStrategy ( ) ; const bundlePath = context . asAbsolutePath ( path . join ( 'dist' , 'server.js' ) ) ; const debugOptions = { execArgv : [ '--nolazy' , '--inspect=6800' ] } ; const serverOptions = { run : { module : bundlePath , transport : TransportKind . ipc , args : cancellationStrategy . getCommandLineArguments ( ) } , debug : { module : bundlePath , transport : TransportKind . ipc , args : cancellationStrategy . getCommandLineArguments ( ) , options : debugOptions , } , } ; const clientOptions = { documentSelector : [ { scheme : 'file' , language : 'python' , } , ] , synchronize : { configurationSection : [ 'python' , 'pytea' ] , } , connectionOptions : { cancellationStrategy : cancellationStrategy } , middleware : { workspace : { configuration : ( params , token , next ) : HandlerResult < any [ ] , void > => { const result = next ( params , token ) ; const addPythonPath = ( settings ) : Promise < any [ ] | ResponseError < any > > => { if ( settings instanceof ResponseError ) { return Promise . resolve ( settings ) ; } const pythonPathPromises = params . items . map ( ( item ) => { if ( item . section === 'python' ) { const uri = item . scopeUri ? Uri . parse ( item . scopeUri ) : undefined ; return getPythonPathFromPythonExtension ( languageClient . outputChannel , uri , ( ) => { languageClient . sendNotification ( DidChangeConfigurationNotification . type , { settings : null , } ) ; } ) ; } return Promise . resolve ( undefined ) ; } ) ; return Promise . all ( pythonPathPromises ) . then ( ( pythonPaths ) => { pythonPaths . forEach ( ( pythonPath , i ) => { if ( pythonPath !== undefined ) { settings [ i ] . pythonPath = pythonPath ; } } ) ; return settings ; } ) ; } ; if ( isThenable ( result ) ) { return result . then ( addPythonPath ) ; } return addPythonPath ( result ) ; } , } , } , } ; const languageClient = new LanguageClient ( 'python' , 'Pytea' , serverOptions , clientOptions ) ; const disposable = languageClient . start ( ) ; context . subscriptions . push ( disposable ) ; const restartCommand = PyteaCommands . restartServer ; context . subscriptions . push ( commands . registerCommand ( restartCommand , ( ... args ) => { languageClient . sendRequest < string > ( 'workspace/executeCommand' , { command : restartCommand , arguments : args , } ) ; } ) ) ; const pathManager = new PathManager ( context , { selectPath : ( pathId ) => { console . log ( `send ${ pathId }` ) ; languageClient . sendRequest ( 'workspace/executeCommand' , { command : PyteaCommands . selectPath , arguments : [ pathId ] , } ) ; } , } ) ; const analyzeFileCommand = PyteaCommands . analyzeFile ; context . subscriptions . push ( commands . registerTextEditorCommand ( analyzeFileCommand , ( editor , edit , ... args ) => { const cmd = { command : analyzeFileCommand , arguments : [ editor . document . uri . toString ( ) , ... args ] , } ; console . log ( `execute analyze ${ editor . document . uri . toString ( ) }` ) ; languageClient . sendRequest < ExecutionPathProps [ ] > ( 'workspace/executeCommand' , cmd ) . then ( async ( response ) => { pathManager . applyPathProps ( response ) ; } ) ; } ) ) ; } export function deactivate ( ) { if ( cancellationStrategy ) { cancellationStrategy . dispose ( ) ; cancellationStrategy = undefined ; } return undefined ; } async function getPythonPathFromPythonExtension ( outputChannel , scopeUri , postConfigChanged ) { try { const extension = extensions . getExtension ( 'ms-python.python' ) ; if ( ! extension ) { outputChannel . appendLine ( 'Python extension not found' ) ; } else { if ( extension . packageJSON ?. featureFlags ?. usingNewInterpreterStorage ) { if ( ! extension . isActive ) { outputChannel . appendLine ( 'Waiting for Python extension to load' ) ; await extension . activate ( ) ; outputChannel . appendLine ( 'Python extension loaded' ) ; } const execDetails = await extension . exports . settings . getExecutionDetails ( scopeUri ) ; let result ; if ( execDetails . execCommand && execDetails . execCommand . length > 0 ) { result = execDetails . execCommand [ 0 ] ; } if ( extension . exports . settings . onDidChangeExecutionDetails ) { installPythonPathChangedListener ( extension . exports . settings . onDidChangeExecutionDetails , scopeUri , postConfigChanged ) ; } if ( ! result ) { outputChannel . appendLine ( `No pythonPath provided by Python extension` ) ; } else { outputChannel . appendLine ( `Received pythonPath from Python extension: ${ result }` ) ; } return result ; } } } catch ( error ) { outputChannel . appendLine ( `Exception occurred when attempting to read pythonPath from Python extension: ${ JSON . stringify ( error ) }` ) ; } return undefined ; } function installPythonPathChangedListener ( onDidChangeExecutionDetails , scopeUri , postConfigChanged ) { const uriString = scopeUri ? scopeUri . toString ( ) : '' ; if ( pythonPathChangedListenerMap . has ( uriString ) ) { return ; } onDidChangeExecutionDetails ( ( ) => { postConfigChanged ( ) ; } ) ; pythonPathChangedListenerMap . set ( uriString , uriString ) ; }