import { commands , events , ExtensionContext , FloatFactory , LanguageClient , LanguageClientOptions , RevealOutputChannelOn , StatusBarItem , workspace , WorkspaceConfiguration , } from "coc.nvim" ; import { checkDottyIde , fetchMetals , getJavaConfig , getJavaHome , getServerOptions , JavaConfig , restartServer , MetalsInitializationOptions , ClientCommands , ServerCommands , MetalsStatus , ExecuteClientCommand , MetalsDidFocus , MetalsInputBox , InputBoxOptions , MetalsQuickPick , MetalsQuickPickParams , } from "metals-languageclient" ; import * as path from "path" ; import { ExecuteCommandRequest , Location , } from "vscode-languageserver-protocol" ; import { Commands } from "./commands" ; import DecorationProvider from "./decoration" ; import { makeVimDoctor } from "./embeddedDoctor" ; import { DecorationsRangesDidChange , PublishDecorationsParams , } from "./metalsProtocol" ; import { TreeViewController } from "./tvp/controller" ; import { TreeViewFeature } from "./tvp/feature" ; import { TreeViewsManager } from "./tvp/treeviews" ; import { checkServerVersion , detectLaunchConfigurationChanges , toggleLogs , trackDownloadProgress , wait , } from "./utils" ; import { DebuggingFeature } from "./DebuggingFeature" ; import WannaBeStatusBarItem from "./WannaBeStatusBarItem" ; export async function activate ( context ) { const config = workspace . getConfiguration ( "metals" ) ; if ( config . get < boolean > ( "enable" ) ) { detectLaunchConfigurationChanges ( ) ; checkServerVersion ( config ) ; getJavaHome ( config . get < string > ( "javaHome" ) ) . then ( ( javaHome ) => fetchAndLaunchMetals ( config , context , javaHome ) , ( ) => { const message = "Unable to find a Java 8 or Java 11 installation on this computer. " + "To fix this problem, update the 'metals.javaHome' setting to point to a Java 8 or Java 11 home directory" ; const openSettings = "Open Settings" ; const ignore = "Ignore for now" ; workspace . showQuickpick ( [ openSettings , ignore ] , message ) . then ( ( choice ) => { if ( choice === 0 ) { workspace . nvim . command ( Commands . OPEN_COC_CONFIG , true ) ; } } ) ; } ) ; } } function fetchAndLaunchMetals ( config , context , javaHome ) { const dottyIde = checkDottyIde ( workspace . workspaceFolder ?. uri ) ; if ( dottyIde . enabled ) { workspace . showMessage ( `Metals will not start since Dotty is enabled for this workspace. ` + `To enable Metals, remove the file ${ dottyIde . path } and run ':CocCommand metals.restartServer'` , "warning" ) ; return ; } const serverVersionConfig = config . get < string > ( "serverVersion" ) ; const defaultServerVersion = config . inspect < string > ( "serverVersion" ) ! . defaultValue ! ; const serverVersion = serverVersionConfig ? serverVersionConfig : defaultServerVersion ; const serverProperties = config . get < string [ ] > ( "serverProperties" ) ! ; const customRepositories = config . get < string [ ] > ( "customRepositories" ) ! ; const javaConfig = getJavaConfig ( { workspaceRoot : workspace . workspaceFolder ?. uri , javaHome , customRepositories , extensionPath : context . extensionPath , } ) ; const fetchProcess = fetchMetals ( { serverVersion , serverProperties , javaConfig , } ) ; const statusBarEnabled = config . get < boolean > ( "statusBarEnabled" ) ; const progress = statusBarEnabled ? workspace . createStatusBarItem ( 0 , { progress : true } ) : new WannaBeStatusBarItem ( 0 , true , "Preparing Metals" ) ; const title = `Downloading Metals v${ serverVersion }` ; trackDownloadProgress ( title , fetchProcess , progress ) . then ( ( classpath ) => { launchMetals ( context , classpath , serverProperties , javaConfig , progress , statusBarEnabled ) ; } , ( ) => { const msg = ( ( ) => { const proxy = `See https://scalameta.org/metals/docs/build-tools/proxy.html for instructions ` + `if you are using an HTTP proxy.` ; if ( serverVersion === defaultServerVersion ) { return ( `Failed to download Metals, make sure you have an internet connection and ` + `the Java Home '${ javaHome }' is valid. You can configure the Java Home in the settings.` + proxy ) ; } else { return ( `Failed to download Metals, make sure you have an internet connection, ` + `the Metals version '${ serverVersion }' is correct and the Java Home '${ javaHome }' is valid. ` + `You can configure the Metals version and Java Home in the settings.` + proxy ) ; } } ) ( ) ; workspace . showPrompt ( `${ msg }\n Open Settings?` ) . then ( ( choice ) => { if ( choice ) workspace . nvim . command ( Commands . OPEN_COC_CONFIG , true ) ; } ) ; } ) ; } async function launchMetals ( context , metalsClasspath , serverProperties , javaConfig , progress , statusBarEnabled ) { const serverOptions = getServerOptions ( { metalsClasspath , serverProperties , javaConfig , } ) ; const initializationOptions = { compilerOptions : { completionCommand : "editor.action.triggerSuggest" , overrideDefFormat : "unicode" , parameterHintsCommand : "editor.action.triggerParameterHints" , } , debuggingProvider : workspace . isNvim && ( await workspace . nvim . getVar ( "loaded_vimpector" ) ) === 1 , decorationProvider : workspace . isNvim , didFocusProvider : true , doctorProvider : "json" , executeClientCommandProvider : true , inputBoxProvider : true , quickPickProvider : true , slowTaskProvider : true , statusBarProvider : statusBarEnabled ? "on" : "show-message" , treeViewProvider : true , } ; const clientOptions = { documentSelector : [ { scheme : "file" , language : "scala" } ] , synchronize : { configurationSection : "metals" , } , revealOutputChannelOn : RevealOutputChannelOn . Never , initializationOptions , } ; const client = new LanguageClient ( "metals" , "Metals" , serverOptions , clientOptions ) ; const treeViewFeature = new TreeViewFeature ( client ) ; client . registerFeature ( treeViewFeature ) ; if ( clientOptions . initializationOptions . debuggingProvider ) { const debuggingFeature = new DebuggingFeature ( workspace . nvim , client ) ; client . registerFeature ( debuggingFeature ) ; } const floatFactory = new FloatFactory ( workspace . nvim , workspace . env , false , 100 , 100 , true ) ; const decorationProvider = new DecorationProvider ( floatFactory ) ; const treeViewsManager = new TreeViewsManager ( workspace . nvim , context . logger ) ; const treeViewController = new TreeViewController ( treeViewFeature , treeViewsManager ) ; context . subscriptions . push ( treeViewFeature , treeViewsManager , treeViewController ) ; function registerCommand ( command , callback ) { context . subscriptions . push ( commands . registerCommand ( command , callback ) ) ; } registerCommand ( "metals.restartServer" , restartServer ( client , workspace ) ) ; context . subscriptions . push ( client . start ( ) ) ; client . onReady ( ) . then ( ( _ ) => { progress . isProgress = false ; progress . text = "Metals is Ready!" ; progress . show ( ) ; const commands = [ ServerCommands . AmmoniteStart , ServerCommands . AmmoniteStop , ServerCommands . BspSwitch , ServerCommands . BuildConnect , ServerCommands . BuildDisconnect , ServerCommands . BuildImport , ServerCommands . BuildRestart , ServerCommands . CancelCompilation , ServerCommands . CascadeCompile , ServerCommands . CleanCompile , ServerCommands . DoctorRun , ServerCommands . GenerateBspConfig , ServerCommands . NewScalaProject , ServerCommands . ResetChoice , ServerCommands . SourcesScan , ] ; commands . forEach ( ( command ) => { registerCommand ( "metals." + command , async ( ) => { client . sendRequest ( ExecuteCommandRequest . type , { command } ) ; } ) ; } ) ; registerCommand ( `metals.${ ServerCommands . AnalyzeStacktrace }` , async ( ) => { if ( workspace . isVim ) { workspace . showMessage ( "Analyze stacktrace functionality isn't support in Vim. Please use Nvim if you'd like to use this." , "warning" ) ; } else { const trace = await workspace . nvim . call ( "getreg" , "*" ) ; if ( trace . trim ( ) . length < 1 ) { workspace . showMessage ( "No text found in your register. Copy your stacktrace to your register and retry." , "warning" ) ; } else { client . sendRequest ( ExecuteCommandRequest . type , { command : ServerCommands . AnalyzeStacktrace , arguments : [ trace ] , } ) ; } } } ) ; registerCommand ( "metals.logs-toggle" , ( ) => { toggleLogs ( ) ; } ) ; if ( workspace . isNvim ) { context . subscriptions . push ( workspace . registerKeymap ( [ "n" ] , "metals-expand-decoration" , ( ) => { decorationProvider . showHover ( ) ; } , { sync : false } ) ) ; } registerCommand ( `metals.${ ServerCommands . NewScalaFile }` , async ( ) => { const currentDoc = await workspace . document ; const currentPath = currentDoc . uri ; const parentDir = path . dirname ( currentPath ) ; client . sendRequest ( ExecuteCommandRequest . type , { command : ServerCommands . NewScalaFile , arguments : [ parentDir ] , } ) ; } ) ; registerCommand ( `metals.quick-worksheet` , async ( ) => { const currentDoc = await workspace . document ; const currentPath = currentDoc . uri ; const parentDir = path . dirname ( currentPath ) ; const name = path . basename ( parentDir ) ; client . sendRequest ( ExecuteCommandRequest . type , { command : ServerCommands . NewScalaFile , arguments : [ parentDir , name , "worksheet" ] , } ) ; } ) ; registerCommand ( `metals.${ ServerCommands . CopyWorksheetOutput }` , async ( ) => { const currentDoc = await workspace . document ; const currentPath = currentDoc . uri ; const response = await client . sendRequest ( ExecuteCommandRequest . type , { command : ServerCommands . CopyWorksheetOutput , arguments : [ currentPath ] , } ) ; if ( response . value ) { workspace . nvim . call ( "setreg" , [ "+" , response . value ] ) ; workspace . showMessage ( "Copied worksheet to to your +register" ) ; } } ) ; registerCommand ( `metals.${ ServerCommands . GotoSuperMethod }` , async ( ) => { const currentDoc = await workspace . document ; const position = await workspace . getCursorPosition ( ) ; client . sendRequest ( ExecuteCommandRequest . type , { command : ServerCommands . GotoSuperMethod , arguments : [ { textDocument : { uri : currentDoc . uri , } , position , } , ] , } ) ; } ) ; registerCommand ( `metals.${ ServerCommands . SuperMethodHierarchy }` , async ( ) => { const currentDoc = await workspace . document ; const position = await workspace . getCursorPosition ( ) ; client . sendRequest ( ExecuteCommandRequest . type , { command : ServerCommands . SuperMethodHierarchy , arguments : [ { textDocument : { uri : currentDoc . uri , } , position , } , ] , } ) ; } ) ; client . onNotification ( ExecuteClientCommand . type , async ( params ) => { switch ( params . command ) { case ClientCommands . GotoLocation : const location = params . arguments && ( params . arguments [ 0 ] as Location ) ; if ( location ) { await treeViewsManager . prepareWindowForGoto ( ) ; await wait ( 10 ) ; await workspace . jumpTo ( location . uri , location . range . start ) ; } break ; case ClientCommands . RunDoctor : const doctorJson = params . arguments && params . arguments [ 0 ] ; makeVimDoctor ( JSON . parse ( doctorJson ) ) ; break ; case ClientCommands . ReloadDoctor : workspace . nvim . call ( Commands . HAS_PREVIEW ) . then ( ( preview ) => { if ( preview > 0 ) { const doctorJson = params . arguments && params . arguments [ 0 ] ; makeVimDoctor ( JSON . parse ( doctorJson ) ) ; } } ) ; break ; case ClientCommands . FocusDiagnostics : workspace . nvim . command ( "CocList diagnostics" ) ; break ; case ClientCommands . ToggleLogs : toggleLogs ( ) ; break ; case ClientCommands . RefreshModel : events . fire ( "BufEnter" , [ workspace . bufnr ] ) ; break ; default : workspace . showMessage ( `Recieved unknown command: ${ params . command }` ) ; } } ) ; events . on ( "BufWinEnter" , ( bufnr ) => { const currentDocument = workspace . documents . find ( ( document ) => document . bufnr === bufnr ) ; if ( currentDocument && currentDocument . filetype === "scala" ) { client . sendNotification ( MetalsDidFocus . type , currentDocument . uri ) ; } } ) ; client . onRequest ( MetalsInputBox . type , ( options ) => { return workspace . callAsync < string > ( "input" , [ `${ options . prompt }: ` , options . value ? options . value : "" , ] ) . then ( MetalsInputBox . handleInput ) ; } ) ; client . onRequest ( MetalsQuickPick . type , ( params , _ ) => workspace . showQuickpick ( params . items . map ( ( item ) => item . label ) , params . placeHolder ) . then ( ( answer ) => { if ( answer === - 1 ) { return { cancelled : true } ; } else { return { itemId : params . items [ answer ] . id } ; } } ) ) ; if ( statusBarEnabled ) { const statusItem = workspace . createStatusBarItem ( 0 ) ; client . onNotification ( MetalsStatus . type , ( params ) => { if ( params . show ) { statusItem . text = params . text ; } else if ( params . hide ) { statusItem . text = "Metals" ; } statusItem . show ( ) ; } ) ; } if ( workspace . isNvim ) { client . onNotification ( DecorationsRangesDidChange . type , ( params ) => { if ( workspace . uri && workspace . uri === params . uri ) { decorationProvider . setDecorations ( params ) ; } } ) ; events . on ( "BufWinLeave" , ( bufnr ) => { const previousDocument = workspace . documents . find ( ( document ) => document . bufnr === bufnr ) ; if ( previousDocument && previousDocument . uri . endsWith ( ".worksheet.sc" ) ) { decorationProvider . clearDecorations ( bufnr ) ; } } ) ; } } ) ; }