'use strict' ; import { IPCMessageReader , IPCMessageWriter , createConnection , IConnection , TextDocuments , TextDocument , InitializeParams , InitializeResult , NotificationType , RequestType , DiagnosticSeverity , Diagnostic } from 'vscode-languageserver' ; import { xhr , XHRResponse , configure as configureHttpRequests , getErrorStatusDescription } from 'request-light' ; import path = require ( 'path' ) ; import fs = require ( 'fs' ) ; import URI from './utils/uri' ; import * as URL from 'url' ; import Strings = require ( './utils/strings' ) ; import { JSONDocument , JSONSchema , LanguageSettings , LanguageServiceParams , LanguageService , getLanguageService } from 'vscode-json-languageservice' ; import { FileAssociationContribution } from './jsoncontributions/fileAssociationContribution' ; import { getLanguageModelCache } from './languageModelCache' ; import * as bombadil from '@sgarciac/bombadil' ; import * as ct from 'ct' ; interface ISchemaAssociations { [ pattern ] : string [ ] ; } namespace SchemaAssociationNotification { export const type = new NotificationType ( 'json/schemaAssociations' ) ; } namespace VSCodeContentRequest { export const type = new RequestType ( 'vscode/content' ) ; } let connection = createConnection ( new IPCMessageReader ( process ) , new IPCMessageWriter ( process ) ) ; console . log = connection . console . log . bind ( connection . console ) ; console . error = connection . console . error . bind ( connection . console ) ; let documents = new TextDocuments ( ) ; documents . listen ( connection ) ; const filesAssociationContribution = new FileAssociationContribution ( ) ; let workspaceRoot ; connection . onInitialize ( ( params ) : InitializeResult => { workspaceRoot = URI . parse ( params . rootPath ) ; if ( params . initializationOptions ) { filesAssociationContribution . setLanguageIds ( params . initializationOptions . languageIds ) ; } return { capabilities : { textDocumentSync : documents . syncKind , completionProvider : { resolveProvider : true , triggerCharacters : [ '"' , '=' ] } , hoverProvider : true , documentSymbolProvider : true , } } ; } ) ; let workspaceContext = { resolveRelativePath : ( relativePath , resource ) => { return URL . resolve ( resource , relativePath ) ; } } ; let schemaRequestService = ( uri ) : Thenable < string > => { if ( Strings . startsWith ( uri , 'file://' ) ) { let fsPath = URI . parse ( uri ) . fsPath ; return new Promise < string > ( ( c , e ) => { fs . readFile ( fsPath , 'UTF-8' , ( err , result ) => { err ? e ( '' ) : c ( result . toString ( ) ) ; } ) ; } ) ; } else if ( Strings . startsWith ( uri , 'vscode://' ) ) { return connection . sendRequest ( VSCodeContentRequest . type , uri ) . then ( responseText => { return responseText ; } , error => { return error . message ; } ) ; } if ( uri . indexOf ( '//schema.management.azure.com/' ) !== - 1 ) { connection . telemetry . logEvent ( { key : 'json.schema' , value : { schemaURL : uri } } ) ; } return xhr ( { url : uri , followRedirects : 5 } ) . then ( response => { return response . responseText ; } , ( error ) => { return Promise . reject ( error . responseText || getErrorStatusDescription ( error . status ) || error . toString ( ) ) ; } ) ; } ; type TOMLDocument = { json : Object ; errors : [ bombadil . TomlError ] ; } ; interface TomlLanguageService extends LanguageService { parseTOMLDocument ( document ) : TOMLDocument ; doValidation ( textDocument , tomlDocument ) : Thenable < Diagnostic [ ] > ; } function parseTOML ( input ) { let tomlDocument = { } ; let reader = new bombadil . TomlReader ; reader . readToml ( input ) ; if ( reader . result != null ) { tomlDocument [ 'json' ] = reader . result ; tomlDocument [ 'errors' ] = [ ] ; } else { tomlDocument [ 'json' ] = { } ; tomlDocument [ 'errors' ] = reader . errors ; } return tomlDocument as TOMLDocument ; } function getTomlLanguageService ( params ) { let jsonLs = getLanguageService ( params ) ; let tomlLs = Object . assign ( { } , jsonLs , { parseTOMLDocument : ( document ) => parseTOML ( document . getText ( ) ) , doValidation : ( textDocument , tomlDocument ) => { let diagnostics = [ ] ; if ( tomlDocument . errors . length > 0 ) { let ex = tomlDocument . errors [ 0 ] ; let start = { line : 0 , column : 0 } ; let end = { line : 0 , column : 1 } ; let message = ex . message ; if ( ex . hasOwnProperty ( 'line' ) ) { start = { line : ex [ 'line' ] - 1 , column : ex [ 'column' ] - 1 } ; end = { line : ex [ 'line' ] - 1 , column : ex [ 'column' ] + ex [ 'length' ] } ; } else if ( ex . hasOwnProperty ( 'token' ) ) { let token = ex [ 'token' ] ; start = { line : token . line - 1 , column : token . column - 1 } ; end = { line : token . line - 1 , column : token . column - 1 + token . length } ; } else { } diagnostics . push ( { severity : DiagnosticSeverity . Error , range : { start , end } , message : message , source : 'Toml Parser' } ) ; return Promise . resolve ( diagnostics ) ; } return Promise . resolve ( ) ; } , } ) ; return tomlLs ; } let languageService = getTomlLanguageService ( { schemaRequestService , workspaceContext , contributions : [ ] } ) ; interface Settings { toml : { schemas : JSONSchemaSettings [ ] ; } ; http : { proxy : string ; proxyStrictSSL : boolean ; } ; } interface JSONSchemaSettings { fileMatch ? : string [ ] ; url ? : string ; schema ? : JSONSchema ; } let tomlConfigurationSettings = void 0 ; let schemaAssociations = void 0 ; connection . onDidChangeConfiguration ( ( change ) => { var settings = < Settings > change . settings ; configureHttpRequests ( settings . http && settings . http . proxy , settings . http && settings . http . proxyStrictSSL ) ; tomlConfigurationSettings = settings . toml && settings . toml . schemas ; updateConfiguration ( ) ; } ) ; connection . onNotification ( SchemaAssociationNotification . type , associations => { schemaAssociations = associations ; updateConfiguration ( ) ; } ) ; function updateConfiguration ( ) { let languageSettings = { validate : true , allowComments : true , schemas : [ ] } ; if ( schemaAssociations ) { for ( var pattern in schemaAssociations ) { let association = schemaAssociations [ pattern ] ; if ( Array . isArray ( association ) ) { association . forEach ( uri => { languageSettings . schemas . push ( { uri , fileMatch : [ pattern ] } ) ; } ) ; } } } if ( tomlConfigurationSettings ) { tomlConfigurationSettings . forEach ( schema => { let uri = schema . url ; if ( ! uri && schema . schema ) { uri = schema . schema . id ; } if ( ! uri && schema . fileMatch ) { uri = 'vscode://schemas/custom/' + encodeURIComponent ( schema . fileMatch . join ( '&' ) ) ; } if ( uri ) { if ( uri [ 0 ] === '.' && workspaceRoot ) { uri = URI . file ( path . normalize ( path . join ( workspaceRoot . fsPath , uri ) ) ) . toString ( ) ; } languageSettings . schemas . push ( { uri , fileMatch : schema . fileMatch , schema : schema . schema } ) ; } } ) ; } languageService . configure ( languageSettings ) ; documents . all ( ) . forEach ( triggerValidation ) ; } documents . onDidChangeContent ( ( change ) => { triggerValidation ( change . document ) ; } ) ; documents . onDidClose ( event => { cleanPendingValidation ( event . document ) ; connection . sendDiagnostics ( { uri : event . document . uri , diagnostics : [ ] } ) ; } ) ; let pendingValidationRequests = { } ; const validationDelayMs = 200 ; function cleanPendingValidation ( textDocument ) { let request = pendingValidationRequests [ textDocument . uri ] ; if ( request ) { clearTimeout ( request ) ; delete pendingValidationRequests [ textDocument . uri ] ; } } function triggerValidation ( textDocument ) { cleanPendingValidation ( textDocument ) ; pendingValidationRequests [ textDocument . uri ] = setTimeout ( ( ) => { delete pendingValidationRequests [ textDocument . uri ] ; validateTextDocument ( textDocument ) ; } , validationDelayMs ) ; } function validateTextDocument ( textDocument ) { if ( textDocument . getText ( ) . length === 0 ) { connection . sendDiagnostics ( { uri : textDocument . uri , diagnostics : [ ] } ) ; return ; } let tomlDocument = getTOMLDocument ( textDocument ) ; languageService . doValidation ( textDocument , tomlDocument ) . then ( diagnostics => { connection . sendDiagnostics ( { uri : textDocument . uri , diagnostics } ) ; } ) ; } connection . onDidChangeWatchedFiles ( ( change ) => { let hasChanges = false ; change . changes . forEach ( c => { if ( languageService . resetSchema ( c . uri ) ) { hasChanges = true ; } } ) ; if ( hasChanges ) { documents . all ( ) . forEach ( validateTextDocument ) ; } } ) ; let tomlDocuments = getLanguageModelCache < TOMLDocument > ( 10 , 60 , document => languageService . parseTOMLDocument ( document ) ) ; function getTOMLDocument ( document ) { return tomlDocuments . get ( document ) ; } connection . onCompletion ( textDocumentPosition => { let document = documents . get ( textDocumentPosition . textDocument . uri ) ; let tomlDocument = getTOMLDocument ( document ) ; return languageService . doComplete ( document , textDocumentPosition . position , tomlDocument ) ; } ) ; connection . onCompletionResolve ( completionItem => { return languageService . doResolve ( completionItem ) ; } ) ; connection . onHover ( textDocumentPositionParams => { let document = documents . get ( textDocumentPositionParams . textDocument . uri ) ; let tomlDocument = getTOMLDocument ( document ) ; return languageService . doHover ( document , textDocumentPositionParams . position , tomlDocument ) ; } ) ; connection . onDocumentSymbol ( documentSymbolParams => { let document = documents . get ( documentSymbolParams . textDocument . uri ) ; let tomlDocument = getTOMLDocument ( document ) ; return languageService . findDocumentSymbols ( document , tomlDocument ) ; } ) ; connection . onDocumentRangeFormatting ( formatParams => { let document = documents . get ( formatParams . textDocument . uri ) ; return languageService . format ( document , formatParams . range , formatParams . options ) ; } ) ; connection . listen ( ) ;