'use strict' ; import { IPCMessageReader , IPCMessageWriter , createConnection , IConnection , TextDocuments , TextDocument , Position , InitializeResult , Hover , MarkedString , Files , TextDocumentChangeEvent , RequestType , TextDocumentPositionParams } from 'vscode-languageserver' ; let uriToFilePath = Files . uriToFilePath ; import { basename } from 'path' ; import { IGhcMod , IGhcModProvider , LogLevel , ILogger , CheckTrigger } from './interfaces' ; import { InteractiveGhcModProcess , InteractiveGhcModProcessOptions } from './ghcModProviders/interactiveGhcMod' ; import { GhcModProvider } from './ghcModProviders/ghcModProvider' ; let ghcMod ; let ghcModProvider ; import { ThrottledDelayer } from './utils/async' ; let dirtyDocuments = new Set ( ) ; let documentChangedDelayers = Object . create ( null ) ; let hoverDelayer = new ThrottledDelayer < Hover > ( 100 ) ; import { RemoteConnectionAdapter } from './utils/remoteConnectionAdapter' ; let logger ; import { FastTagsSymbolProvider } from './symbolProviders/fastTagsSymbolProvider' ; import { HaskTagsSymbolProvider } from './symbolProviders/haskTagsSymbolProvider' ; let connection = createConnection ( new IPCMessageReader ( process ) , new IPCMessageWriter ( process ) ) ; let documents = new TextDocuments ( ) ; documents . listen ( connection ) ; let workspaceRoot ; connection . onInitialize ( ( params ) : InitializeResult => { logger = new RemoteConnectionAdapter ( connection ) ; workspaceRoot = params . rootPath ; return { capabilities : { hoverProvider : true , textDocumentSync : documents . syncKind , definitionProvider : true , documentSymbolProvider : true , workspaceSymbolProvider : true } } ; } ) ; interface ExtensionSettings { ghcMod : { maxNumberOfProblems : number ; executablePath : string ; onHover : string ; check : string ; logLevel : string ; } ; symbols : { executablePath : string ; provider : string ; } ; } let haskellConfig = < ExtensionSettings > Object . create ( { ghcMod : { } , symbols : { } } ) ; let mapFiles = false ; connection . onDidChangeConfiguration ( ( change ) => { logger . log ( 'haskell configuration changed' ) ; let oldSettings = haskellConfig ; haskellConfig = change . settings . haskell ; logger . setLogLevel ( LogLevel [ haskellConfig . ghcMod . logLevel ] ) ; mapFiles = CheckTrigger [ haskellConfig . ghcMod . check ] === CheckTrigger . onChange ; if ( oldSettings . ghcMod . executablePath !== haskellConfig . ghcMod . executablePath ) { initializeGhcMod ( ) ; } else if ( oldSettings . ghcMod . check !== haskellConfig . ghcMod . check || oldSettings . ghcMod . maxNumberOfProblems !== haskellConfig . ghcMod . maxNumberOfProblems ) { documents . all ( ) . forEach ( ghcCheck ) ; } initializeSymbolProvider ( ) ; } ) ; function initializeGhcMod ( ) { if ( ghcModProvider ) { ghcModProvider . shutdown ( ) ; ghcModProvider = null ; } ghcMod = createGhcMod ( ) ; if ( ghcMod ) { ghcModProvider = new GhcModProvider ( ghcMod , workspaceRoot , logger ) ; } else { logger . error ( 'Try configuring user or workspace settings for haskell.ghcMod.executablePath' , [ 'Workspace' , 'User' ] , ( action ) => { connection . sendRequest < string , string , void > ( OpenSettingsRequest . type , action ) ; } ) ; } initializeDocumentSync ( ) ; initializeOnHover ( ) ; initializeOnDefinition ( ) ; initializeOnCommand ( ) ; } function initializeSymbolProvider ( ) { let symbolProvider = null ; switch ( haskellConfig . symbols . provider ) { case 'fast-tags' : symbolProvider = new FastTagsSymbolProvider ( haskellConfig . symbols . executablePath , workspaceRoot , logger ) ; break ; case 'hasktags' : symbolProvider = new HaskTagsSymbolProvider ( haskellConfig . symbols . executablePath , workspaceRoot , logger ) ; break ; default : break ; } connection . onDocumentSymbol ( ( uri ) => { if ( ! symbolProvider || ! ghcMod || ! ghcModProvider ) { return null ; } return symbolProvider . getSymbolsForFile ( uri ) ; } ) ; connection . onWorkspaceSymbol ( ( query , cancellationToken ) => { if ( ! symbolProvider || ! ghcMod || ! ghcModProvider ) { return null ; } return symbolProvider . getSymbolsForWorkspace ( query , cancellationToken ) ; } ) ; } function initializeDocumentSync ( ) { documents . onDidSave ( ( change ) => { dirtyDocuments . delete ( change . document . uri ) ; if ( CheckTrigger [ haskellConfig . ghcMod . check ] === CheckTrigger . onSave || haskellConfig . ghcMod . check === 'true' ) { handleChangeEvent ( change ) ; } } ) ; documents . onDidChangeContent ( ( change ) => { dirtyDocuments . add ( change . document . uri ) ; if ( CheckTrigger [ haskellConfig . ghcMod . check ] === CheckTrigger . onChange ) { handleChangeEvent ( change ) ; } } ) ; } function handleChangeEvent ( change ) { let key = uriToFilePath ( change . document . uri ) ; let delayer = documentChangedDelayers [ key ] ; if ( ! delayer ) { delayer = new ThrottledDelayer < void > ( 1000 ) ; documentChangedDelayers [ key ] = delayer ; } delayer . trigger ( ( ) => ghcCheck ( change . document ) ) ; } function initializeOnHover ( ) { connection . onHover ( ( documentInfo ) => { return hoverDelayer . trigger ( ( ) => { return getInfoOrTypeHover ( documents . get ( documentInfo . textDocument . uri ) , documentInfo . position ) ; } ) . then ( ( hover ) => { return hover ; } ) ; } ) ; } function initializeOnDefinition ( ) { connection . onDefinition ( ( documentInfo ) : any => { let document = documents . get ( documentInfo . textDocument . uri ) ; return ghcModProvider . getDefinitionLocation ( document , uriToFilePath ( document . uri ) , documentInfo . position , workspaceRoot ) ; } ) ; } namespace InsertTypeRequest { 'use strict' ; export const type = { get method ( ) : string { return 'insertType' ; } } ; } namespace OpenSettingsRequest { 'use strict' ; export const type = { get method ( ) : string { return 'openSettings' ; } } ; } function initializeOnCommand ( ) { connection . onRequest < TextDocumentPositionParams , string , void > ( InsertTypeRequest . type , ( documentInfo ) : Promise < string > => { if ( ! ghcMod || ! ghcModProvider ) { return null ; } let filename = basename ( documentInfo . textDocument . uri ) ; let line = documentInfo . position . line ; let character = documentInfo . position . character ; logger . log ( `Received InsertType request for ${ filename } at ${ line }:${ character }` ) ; let document = documents . get ( documentInfo . textDocument . uri ) ; let mapFile = mapFiles && dirtyDocuments . has ( document . uri ) ; return ghcModProvider . getInfo ( document , uriToFilePath ( document . uri ) , documentInfo . position , mapFile ) ; } ) ; } connection . onShutdown ( ( ) => { if ( ghcModProvider ) { ghcModProvider . shutdown ( ) ; } } ) ; function createGhcMod ( ) { let options = { executable : haskellConfig . ghcMod . executablePath , rootPath : workspaceRoot } ; return InteractiveGhcModProcess . create ( options , logger ) ; } function getInfoOrTypeHover ( document , position ) { if ( haskellConfig . ghcMod . onHover === 'none' || ! ghcMod || ! ghcModProvider ) { return null ; } let mapFile = mapFiles && dirtyDocuments . has ( document . uri ) ; return Promise . resolve ( ) . then ( ( ) => { if ( haskellConfig . ghcMod . onHover === 'info' || haskellConfig . ghcMod . onHover === 'fallback' ) { return ghcModProvider . getInfo ( document , uriToFilePath ( document . uri ) , position , mapFile ) ; } else { return null ; } } , ( reason ) => { logger . warn ( 'ghcModProvider.getInfo rejected: ' + reason ) ; } ) . then ( ( info ) => { if ( haskellConfig . ghcMod . onHover === 'info' || info ) { return info ; } else { return ghcModProvider . getType ( document . getText ( ) , uriToFilePath ( document . uri ) , position , mapFile ) ; } } , ( reason ) => { logger . warn ( 'ghcModProvider.getType rejected: ' + reason ) ; } ) . then ( ( type ) => { return type ? < Hover > { contents : < MarkedString > { language : 'haskell' , value : type } } : null ; } ) ; } function ghcCheck ( document ) { let mapFile = mapFiles && dirtyDocuments . has ( document . uri ) ; return Promise . resolve ( ) . then ( ( ) => { if ( ! ghcMod || ! ghcModProvider || CheckTrigger [ haskellConfig . ghcMod . check ] === CheckTrigger . off ) { connection . sendDiagnostics ( { uri : document . uri , diagnostics : [ ] } ) ; } else { ghcModProvider . doCheck ( document . getText ( ) , uriToFilePath ( document . uri ) , mapFile ) . then ( ( diagnostics ) => { connection . sendDiagnostics ( { uri : document . uri , diagnostics : diagnostics . slice ( 0 , haskellConfig . ghcMod . maxNumberOfProblems ) } ) ; } ) ; } } ) ; } connection . listen ( ) ;