import { commands , ConfigurationTarget , Disposable , ExtensionContext , IndentAction , languages , TextEditor , Uri , window , workspace , WorkspaceFolder , WorkspaceFoldersChangeEvent , } from 'vscode' ; import * as lc from 'vscode-languageclient' ; import { RLSConfiguration } from './configuration' ; import * as rls from './rls' ; import * as rustAnalyzer from './rustAnalyzer' ; import { rustupUpdate } from './rustup' ; import { startSpinner , stopSpinner } from './spinner' ; import { activateTaskProvider , Execution , runRlsCommand } from './tasks' ; import { Observable } from './utils/observable' ; import { nearestParentWorkspace } from './utils/workspace' ; /**
 * External API as exposed by the extension. Can be queried by other extensions
 * or by the integration test runner for VSCode extensions.
 */ export interface Api { activeWorkspace : typeof activeWorkspace ; } export async function activate ( context ) { context . subscriptions . push ( ... [ configureLanguage ( ) , ... registerCommands ( ) , workspace . onDidChangeWorkspaceFolders ( whenChangingWorkspaceFolders ) , window . onDidChangeActiveTextEditor ( onDidChangeActiveTextEditor ) , ] ) ; onDidChangeActiveTextEditor ( window . activeTextEditor ) ; const config = workspace . getConfiguration ( ) ; if ( typeof config . get < boolean | null > ( 'rust-client.enableMultiProjectSetup' , null ) === 'boolean' ) { window . showWarningMessage ( 'The multi-project setup for RLS is always enabled, so the `rust-client.enableMultiProjectSetup` setting is now redundant' , { modal : false } , { title : 'Remove' } ) . then ( value => { if ( value && value . title === 'Remove' ) { return config . update ( 'rust-client.enableMultiProjectSetup' , null , ConfigurationTarget . Global ) ; } return ; } ) ; } return { activeWorkspace } ; } export async function deactivate ( ) { return Promise . all ( [ ... workspaces . values ( ) ] . map ( ws => ws . stop ( ) ) ) ; } /** Tracks dynamically updated progress for the active client workspace for UI purposes. */ let progressObserver ; function onDidChangeActiveTextEditor ( editor ) { if ( ! editor || ! editor . document ) { return ; } const { languageId , uri } = editor . document ; const workspace = clientWorkspaceForUri ( uri , { initializeIfMissing : languageId === 'rust' || languageId === 'toml' , } ) ; if ( ! workspace ) { return ; } activeWorkspace . value = workspace ; const updateProgress = ( progress ) => { if ( progress . state === 'progress' ) { startSpinner ( `[${ workspace . folder . name }] ${ progress . message }` ) ; } else { const readySymbol = progress . state === 'standby' ? '$(debug-stop)' : '$(debug-start)' ; stopSpinner ( `[${ workspace . folder . name }] ${ readySymbol }` ) ; } } ; if ( progressObserver ) { progressObserver . dispose ( ) ; } progressObserver = workspace . progress . observe ( updateProgress ) ; updateProgress ( workspace . progress . value ) ; } function whenChangingWorkspaceFolders ( e ) { for ( const folder of e . removed ) { const ws = workspaces . get ( folder . uri . toString ( ) ) ; if ( ws ) { workspaces . delete ( folder . uri . toString ( ) ) ; ws . stop ( ) ; } } } const workspaces = new Map ( ) ; /**
 * Fetches a `ClientWorkspace` for a given URI. If missing and `initializeIfMissing`
 * option was provided, it is additionally initialized beforehand, if applicable.
 */ function clientWorkspaceForUri ( uri , options ? ) { const rootFolder = workspace . getWorkspaceFolder ( uri ) ; if ( ! rootFolder ) { return ; } const folder = nearestParentWorkspace ( rootFolder , uri . fsPath ) ; if ( ! folder ) { return undefined ; } const existing = workspaces . get ( folder . uri . toString ( ) ) ; if ( ! existing && options && options . initializeIfMissing ) { const workspace = new ClientWorkspace ( folder ) ; workspaces . set ( folder . uri . toString ( ) , workspace ) ; workspace . autoStart ( ) ; } return workspaces . get ( folder . uri . toString ( ) ) ; } /** Denotes the state or progress the workspace is currently in. */ export type WorkspaceProgress = { state : 'progress' ; message : string ; } | { state : 'ready' | 'standby' ; } ; export class ClientWorkspace { public readonly folder : WorkspaceFolder ; private readonly config : RLSConfiguration ; private lc : lc . LanguageClient | null = null ; private disposables : Disposable [ ] ; private _progress : Observable < WorkspaceProgress > ; get progress ( ) { return this . _progress ; } constructor ( folder ) { this . config = RLSConfiguration . loadFromWorkspace ( folder . uri . fsPath ) ; this . folder = folder ; this . disposables = [ ] ; this . _progress = new Observable < WorkspaceProgress > ( { state : 'standby' } ) ; } returns public async autoStart ( ) { return this . config . autoStartRls && this . start ( ) . then ( ( ) => true ) ; } public async start ( ) { const { createLanguageClient , setupClient , setupProgress } = this . config . engine === 'rls' ? rls : rustAnalyzer ; const client = await createLanguageClient ( this . folder , { updateOnStartup : this . config . updateOnStartup , revealOutputChannelOn : this . config . revealOutputChannelOn , logToFile : this . config . logToFile , rustup : { channel : this . config . channel , path : this . config . rustupPath , disabled : this . config . rustupDisabled , } , rls : { path : this . config . rlsPath } , rustAnalyzer : this . config . rustAnalyzer , } ) ; client . onDidChangeState ( ( { newState } ) => { if ( newState === lc . State . Starting ) { this . _progress . value = { state : 'progress' , message : 'Starting' } ; } if ( newState === lc . State . Stopped ) { this . _progress . value = { state : 'standby' } ; } } ) ; setupProgress ( client , this . _progress ) ; this . disposables . push ( activateTaskProvider ( this . folder ) ) ; this . disposables . push ( ... setupClient ( client , this . folder ) ) ; if ( client . needsStart ( ) ) { this . disposables . push ( client . start ( ) ) ; } } public async stop ( ) { if ( this . lc ) { await this . lc . stop ( ) ; } this . disposables . forEach ( d => void d . dispose ( ) ) ; } public async restart ( ) { await this . stop ( ) ; return this . start ( ) ; } public runRlsCommand ( cmd ) { return runRlsCommand ( this . folder , cmd ) ; } public rustupUpdate ( ) { return rustupUpdate ( this . config . rustupConfig ( ) ) ; } } /**
 * Tracks the most current VSCode workspace as opened by the user. Used by the
 * commands to know in which workspace these should be executed.
 */ const activeWorkspace = new Observable < ClientWorkspace | null > ( null ) ; /**
 * Registers the VSCode [commands] used by the extension.
 *
 * [commands]: https://code.visualstudio.com/api/extension-guides/command
 */ function registerCommands ( ) { return [ commands . registerCommand ( 'rls.update' , ( ) => activeWorkspace . value ?. rustupUpdate ( ) ) , commands . registerCommand ( 'rls.restart' , async ( ) => activeWorkspace . value ?. restart ( ) ) , commands . registerCommand ( 'rls.run' , ( cmd ) => activeWorkspace . value ?. runRlsCommand ( cmd ) ) , commands . registerCommand ( 'rls.start' , ( ) => activeWorkspace . value ?. start ( ) ) , commands . registerCommand ( 'rls.stop' , ( ) => activeWorkspace . value ?. stop ( ) ) , ] ; } /**
 * Sets up additional language configuration that's impossible to do via a
 * separate language-configuration.json file. See [1] for more information.
 *
 * [1]: https://github.com/Microsoft/vscode/issues/11514#issuecomment-244707076
 */ function configureLanguage ( ) { return languages . setLanguageConfiguration ( 'rust' , { onEnterRules : [ { beforeText : /^\s*\/{3}.*$/ , action : { indentAction : IndentAction . None , appendText : '/// ' } , } , { beforeText : /^\s*\/{2}\!.*$/ , action : { indentAction : IndentAction . None , appendText : '//! ' } , } , { beforeText : /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/ , afterText : /^\s*\*\/$/ , action : { indentAction : IndentAction . IndentOutdent , appendText : ' * ' } , } , { beforeText : /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/ , action : { indentAction : IndentAction . None , appendText : ' * ' } , } , { beforeText : /^(\ \ )*\ \*(\ ([^\*]|\*(?!\/))*)?$/ , action : { indentAction : IndentAction . None , appendText : '* ' } , } , { beforeText : /^(\ \ )*\ \*\/\s*$/ , action : { indentAction : IndentAction . None , removeText : 1 } , } , ] , } ) ; }