'use strict' ; import { ILogger , IGhcModProvider , IGhcMod , GhcModCmdOpts } from '../interfaces' ; import { DocumentUtils } from '../utils/document' ; import { Diagnostic , DiagnosticSeverity , Range , Position , Location , TextDocument } from 'vscode-languageserver' ; const path = require ( 'path' ) ; const fileUrl = require ( 'file-url' ) ; export class GhcModProvider implements IGhcModProvider { private ghcMod : IGhcMod ; private logger : ILogger ; private workspaceRoot : string ; constructor ( ghcMod , workspaceRoot , logger ) { this . ghcMod = ghcMod ; this . workspaceRoot = workspaceRoot ; this . logger = logger ; } public doCheck ( text , uri , mapFile ) { return this . ghcMod . runGhcModCommand ( < GhcModCmdOpts > { command : 'check' , text : mapFile ? text : null , uri : this . getRelativePath ( uri ) } ) . then ( ( lines ) => { return this . parseCheckDiagnostics ( lines ) ; } ) ; } public getType ( text , uri , position , mapFile ) { return this . ghcMod . runGhcModCommand ( < GhcModCmdOpts > { command : 'type' , text : mapFile ? text : null , uri : this . getRelativePath ( uri ) , args : [ ( position . line + 1 ) . toString ( ) , ( position . character + 1 ) . toString ( ) ] } ) . then ( ( lines ) => { return lines . reduce ( ( acc , line ) => { if ( acc ) { return acc ; } return this . parseTypeInfo ( line , position ) ; } , '' ) ; } ) ; } public getInfo ( document , uri , position , mapFile ) { return this . getInfoHelper ( document , uri , position , mapFile ) . then ( ( info ) => { let tooltip = info . replace ( /-- Defined at (.+?):(\d+):(\d+)/g , '' ) ; if ( tooltip . indexOf ( 'Cannot show info' ) === - 1 ) { return tooltip ; } else { return '' ; } } ) ; } public shutdown ( ) { this . ghcMod . killProcess ( ) ; } public getDefinitionLocation ( document , uri , position , root ) { return this . getInfoHelper ( document , uri , position , false ) . then ( ( info ) => { return this . parseInfoForDefinition ( info , root ) ; } ) ; } private getInfoHelper ( document , uri , position , mapFile ) { let symbol = null ; try { symbol = DocumentUtils . getSymbolAtOffset ( document . getText ( ) , document . offsetAt ( position ) ) ; } catch ( error ) { this . logger . warn ( error ) ; } if ( symbol && ! this . isBlacklisted ( symbol ) ) { return this . ghcMod . runGhcModCommand ( < GhcModCmdOpts > { command : 'info' , text : mapFile ? document . getText ( ) : null , uri : this . getRelativePath ( uri ) , args : [ symbol ] } ) . then ( ( lines ) => { if ( ! lines ) { return '' ; } return lines . join ( '\n' ) ; } ) ; } else { return Promise . resolve ( '' ) ; } } private isBlacklisted ( symbol ) { if ( symbol . charAt ( 0 ) === '-' && symbol !== '-' ) { return true ; } return false ; } private parseInfoForDefinition ( text , root ) { let regex = /-- Defined at (.+?):(\d+):(\d+)/g ; let match ; let locations = [ ] ; do { match = regex . exec ( text ) ; if ( match ) { let uri = fileUrl ( < string > match [ 1 ] , { resolve : true } ) ; let range = Range . create ( parseInt ( match [ 2 ] , 10 ) - 1 , parseInt ( match [ 3 ] , 10 ) - 1 , parseInt ( match [ 2 ] , 10 ) - 1 , parseInt ( match [ 3 ] , 10 ) - 1 ) ; locations . push ( Location . create ( uri , range ) ) ; } } while ( match ) ; return locations ; } private getRelativePath ( filepath ) { return path . relative ( this . workspaceRoot || '' , filepath || '' ) ; } private parseTypeInfo ( line , position ) { let tokens = line . split ( '"' ) ; let type = tokens [ 1 ] || '' ; let pos = tokens [ 0 ] . trim ( ) . split ( ' ' ) . map ( ( i ) => { return parseInt ( i , 10 ) - 1 ; } ) ; let typeRange ; try { typeRange = Range . create ( pos [ 0 ] , pos [ 1 ] , pos [ 2 ] , pos [ 3 ] ) ; } catch ( error ) { return '' ; } if ( DocumentUtils . isPositionInRange ( position , typeRange ) ) { return type ; } else { return '' ; } } private parseCheckDiagnostics ( lines ) { let diagnostics = [ ] ; lines . forEach ( ( line ) => { let match = line . match ( /^(.*?):([0-9]+):([0-9]+): *(?:(Warning|Error): *)?/ ) ; if ( match ) { diagnostics . push ( { severity : match [ 4 ] === 'Warning' ? DiagnosticSeverity . Warning : DiagnosticSeverity . Error , range : { start : { line : parseInt ( match [ 2 ] , 10 ) - 1 , character : parseInt ( match [ 3 ] , 10 ) - 1 } , end : { line : parseInt ( match [ 2 ] , 10 ) - 1 , character : parseInt ( match [ 3 ] , 10 ) - 1 } } , message : line . replace ( match [ 0 ] , '' ) } ) ; } } ) ; return diagnostics ; } }