'use strict' ; import { exec , ChildProcess } from 'child_process' ; import * as path from 'path' ; import * as semver from 'semver' ; import * as vscode from 'vscode' ; import { LanguageClient , ServerOptions , LanguageClientOptions , ErrorAction , CloseAction , RevealOutputChannelOn } from 'vscode-languageclient' ; import { Trace } from 'vscode-jsonrpc/lib/main' ; import * as dotnet from './utils/dotnet' ; import * as executables from './utils/executables' ; import { handleBusyNotifications } from './notifications' ; import { registerCommands } from './commands' ; import { registerInternalCommands } from './internal-commands' ; import { Settings , upgradeConfigurationSchema , readVSCodeSettings } from './settings' ; let configuration ; let languageClient ; let statusBarItem ; let outputChannel ; const featureFlags = new Set < string > ( ) ; const languageServerEnvironment = Object . assign ( { } , process . env ) ; const projectDocumentSelector = [ { language : 'xml' , pattern : '**/*.*proj' } , { language : 'xml' , pattern : '**/*.props' } , { language : 'xml' , pattern : '**/*.targets' } , { language : 'xml' , pattern : '**/*.tasks' } , { language : 'msbuild' , pattern : '**/*.*' } ] ; param context export async function activate ( context ) { outputChannel = vscode . window . createOutputChannel ( 'MSBuild Project Tools' ) ; const progressOptions = { location : vscode . ProgressLocation . Window } ; await vscode . window . withProgress ( progressOptions , async ( progress ) => { progress . report ( { message : 'Initialising MSBuild project tools...' } ) ; await loadConfiguration ( ) ; const dotnetVersion = await dotnet . getHostVersion ( ) ; const canEnableLanguageService = dotnetVersion && semver . gte ( dotnetVersion , '5.0.0' ) ; if ( canEnableLanguageService ) { await createLanguageClient ( context ) ; context . subscriptions . push ( handleExpressionAutoClose ( ) ) ; registerCommands ( context , statusBarItem ) ; registerInternalCommands ( context ) ; } else { outputChannel . appendLine ( `Cannot enable the MSBuild language service ('${ dotnetVersion }' is not a supported version of the .NET Core runtime).` ) ; } } ) ; context . subscriptions . push ( vscode . workspace . onDidChangeConfiguration ( async ( args ) => { await loadConfiguration ( ) ; if ( languageClient ) { if ( configuration . logging . trace ) { languageClient . trace = Trace . Verbose ; } else { languageClient . trace = Trace . Off ; } } } ) ) ; } /**
 * Called when the extension is deactivated.
 */ export function deactivate ( ) { } /**
 * Load extension configuration from the workspace.
 */ async function loadConfiguration ( ) { const workspaceConfiguration = vscode . workspace . getConfiguration ( ) ; configuration = workspaceConfiguration . get ( 'msbuildProjectTools' ) ; await upgradeConfigurationSchema ( configuration ) ; configuration = readVSCodeSettings ( configuration ) ; featureFlags . clear ( ) ; if ( configuration . experimentalFeatures ) { configuration . experimentalFeatures . forEach ( featureFlag => featureFlags . add ( featureFlag ) ) ; } } param context returns async function createLanguageClient ( context ) { statusBarItem = vscode . window . createStatusBarItem ( vscode . StatusBarAlignment . Right , 50 ) ; context . subscriptions . push ( statusBarItem ) ; statusBarItem . text = '$(check) MSBuild Project' ; statusBarItem . tooltip = 'MSBuild Project Tools' ; statusBarItem . hide ( ) ; outputChannel . appendLine ( 'Starting MSBuild language service...' ) ; const clientOptions = { synchronize : { configurationSection : 'msbuildProjectTools' } , diagnosticCollectionName : 'MSBuild Project' , errorHandler : { error : ( error , message , count ) => { if ( count > 2 ) return ErrorAction . Shutdown ; console . log ( message ) ; console . log ( error ) ; if ( message ) outputChannel . appendLine ( `The MSBuild language server encountered an unexpected error: ${ message }\n\n${ error }` ) ; else outputChannel . appendLine ( `The MSBuild language server encountered an unexpected error.\n\n${ error }` ) ; return ErrorAction . Continue ; } , closed : ( ) => CloseAction . DoNotRestart } , initializationFailedHandler ( error ) { console . log ( error ) ; outputChannel . appendLine ( `Failed to initialise the MSBuild language server.\n\n${ error }` ) ; vscode . window . showErrorMessage ( `Failed to initialise MSBuild language server.\n\n${ error }` ) ; return false ; } , revealOutputChannelOn : RevealOutputChannelOn . Never } ; languageServerEnvironment [ 'MSBUILD_PROJECT_TOOLS_DIR' ] = context . extensionPath ; const seqLoggingSettings = configuration . logging . seq ; if ( seqLoggingSettings && seqLoggingSettings . url ) { languageServerEnvironment [ 'MSBUILD_PROJECT_TOOLS_SEQ_URL' ] = seqLoggingSettings . url ; languageServerEnvironment [ 'MSBUILD_PROJECT_TOOLS_SEQ_API_KEY' ] = seqLoggingSettings . apiKey ; } if ( configuration . logging . file ) { languageServerEnvironment [ 'MSBUILD_PROJECT_TOOLS_LOG_FILE' ] = configuration . logging . file ; } if ( configuration . logging . level === 'Verbose' ) { languageServerEnvironment [ 'MSBUILD_PROJECT_TOOLS_VERBOSE_LOGGING' ] = '1' ; } const dotNetExecutable = await executables . find ( 'dotnet' ) ; const serverAssembly = context . asAbsolutePath ( 'out/language-server/MSBuildProjectTools.LanguageServer.Host.dll' ) ; const serverProbeSuccess = await probeLanguageServer ( dotNetExecutable , serverAssembly ) ; if ( ! serverProbeSuccess ) { vscode . window . showErrorMessage ( 'Unable to start MSBuild language server (see the output window for details).' ) ; return ; } const serverOptions = { command : dotNetExecutable , args : [ serverAssembly ] , options : { env : languageServerEnvironment } } ; languageClient = new LanguageClient ( 'MSBuild Language Service' , serverOptions , clientOptions ) ; if ( configuration . logging . trace ) { languageClient . trace = Trace . Verbose ; } else { languageClient . trace = Trace . Off ; } handleBusyNotifications ( languageClient , statusBarItem ) ; let languageClientDisposal ; try { languageClientDisposal = languageClient . start ( ) ; context . subscriptions . push ( languageClientDisposal ) ; } catch ( startFailed ) { outputChannel . appendLine ( `Failed to start MSBuild language service.\n\n${ startFailed }` ) ; languageClientDisposal . dispose ( ) ; return ; } try { await languageClient . onReady ( ) ; } catch ( onReadyFailed ) { outputChannel . appendLine ( `Failed to start MSBuild language server.\n\n${ onReadyFailed }` ) ; languageClientDisposal . dispose ( ) ; return ; } outputChannel . appendLine ( 'MSBuild language service is running.' ) ; } param dotNetExecutable param serverAssembly function probeLanguageServer ( dotNetExecutable , serverAssembly ) { return new Promise ( resolve => { let serverError ; let serverStdOut ; let serverStdErr ; const languageServerProcess = exec ( `"${ dotNetExecutable }" "${ serverAssembly }" --probe` , ( error , stdout , stderr ) => { serverError = error ; serverStdOut = stdout ; serverStdErr = stderr ; } ) ; languageServerProcess . on ( 'close' , exitCode => { if ( ! serverError && exitCode === 0 ) { resolve ( true ) ; return ; } console . log ( "Failed to start language server." ) ; outputChannel . appendLine ( 'Failed to start the MSBuild language server.' ) ; if ( serverError ) { console . log ( serverError ) ; outputChannel . appendLine ( serverError . toString ( ) ) ; } if ( serverStdOut ) { console . log ( serverStdOut ) ; outputChannel . appendLine ( serverStdOut ) ; } if ( serverStdErr ) { console . log ( serverStdErr ) ; outputChannel . appendLine ( serverStdErr ) ; } resolve ( false ) ; } ) ; } ) ; } /**
 * Handle document-change events to automatically insert a closing parenthesis for common MSBuild expressions.
 */ function handleExpressionAutoClose ( ) { return vscode . workspace . onDidChangeTextDocument ( async ( args ) => { if ( ! vscode . languages . match ( projectDocumentSelector , args . document ) ) return ; if ( ! featureFlags . has ( 'expressions' ) ) return ; if ( args . contentChanges . length !== 1 ) return ; const contentChange = args . contentChanges [ 0 ] ; if ( isOriginPosition ( contentChange . range . start ) ) return ; if ( contentChange . text === '(' ) { const range = contentChange . range . with ( contentChange . range . start . translate ( 0 , - 1 ) , contentChange . range . end . translate ( 0 , 1 ) ) ; const openExpression = args . document . getText ( range ) ; switch ( openExpression ) { case '$(' : case '@(' : case '%(' : { break ; } default : { return ; } } const closedExpression = openExpression + ')' ; await vscode . window . activeTextEditor . edit ( edit => edit . replace ( range , closedExpression ) ) ; await vscode . commands . executeCommand ( 'msbuildProjectTools.internal.moveAndSuggest' , 'left' , 'character' , 1 ) ; } } ) ; } /**
 * Determine whether the specified  vscode . Position represents the origin position.
 *
 *  param position The  vscode . Position to examine.
  function isOriginPosition ( position ) { return position . line === 0 && position . character === 0 ; }