'use strict' ; import { IPCMessageReader , IPCMessageWriter , createConnection , IConnection , TextDocumentSyncKind , TextDocuments , TextDocument , Diagnostic , DiagnosticSeverity , InitializeParams , InitializeResult , TextDocumentPositionParams , CompletionItem , CompletionItemKind } from 'vscode-languageserver' ; import Uri from 'vscode-uri' ; import { ModelManager , AclManager , QueryManager , ModelFile , ModelUtil , AclFile , Logger , FileWriter , Writer } from 'composer-common' ; import PlantUMLVisitor = require ( 'composer-common/lib/codegen/fromcto/plantuml/plantumlvisitor' ) ; Logger . setFunctionalLogger ( { log : ( ) => { } } ) ; const modelManager = new ModelManager ( ) ; const aclManager = new AclManager ( modelManager ) ; const queryManager = new QueryManager ( modelManager ) ; const connection = createConnection ( new IPCMessageReader ( process ) , new IPCMessageWriter ( process ) ) ; const documents = new TextDocuments ( ) ; documents . listen ( connection ) ; let workspaceRoot ; connection . onInitialize ( ( params ) : InitializeResult => { workspaceRoot = params . rootPath ; return { capabilities : { textDocumentSync : documents . syncKind , executeCommandProvider : { commands : [ 'composer.generateUML' ] } } } ; } ) ; documents . onDidChangeContent ( ( doc ) => { validateTextDocument ( doc . document ) ; } ) ; documents . onDidOpen ( ( doc ) => { } ) ; documents . onDidClose ( ( doc ) => { closeTextDocument ( doc . document ) ; } ) ; interface Settings { composer : ComposerSettings ; } interface ComposerSettings { contributor : boolean ; maxNumberOfProblems : number ; UML : { keepSourceFileOpen : boolean ; autoShowDiagam : boolean ; includeSystemNamespace : string ; diagramStyle : string ; diagramTheme : string ; } ; } let options ; connection . onDidChangeConfiguration ( ( change ) => { options = change . settings . composer as ComposerSettings ; options . maxNumberOfProblems = options . maxNumberOfProblems || 10 ; documents . all ( ) . forEach ( validateTextDocument ) ; } ) ; param { ExecuteCommandParams } params connection . onExecuteCommand ( ( params ) => { if ( params . command !== 'composer.generateUML' ) { return ; } let diagramTitle = 'Business Network Definition' ; let originatingFileName = '' ; if ( params . arguments . length >= 1 ) { if ( params . arguments [ 0 ] . external ) { originatingFileName = params . arguments [ 0 ] . external ; if ( originatingFileName . length > 0 ) { const originatingFileNameUri = Uri . parse ( originatingFileName ) ; const path = originatingFileNameUri . path ; if ( path && path . length > 0 ) { const end = path . lastIndexOf ( '/' ) ; if ( end !== - 1 ) { diagramTitle += " for '" + path . substring ( end + 1 ) + "'" ; } } } } } handleGenerateUml ( diagramTitle , originatingFileName ) ; } ) ; param { string } diagramTitle param { string } originatingFileName function handleGenerateUml ( diagramTitle , originatingFileName ) { const visitor = new PlantUMLVisitor ( ) ; const writer = new Writer ( ) ; const parameters = { fileWriter : writer } ; let result = [ ] ; const modelFiles = modelManager . getModelFiles ( ) ; for ( let n = 0 ; n < modelFiles . length ; n ++ ) { const modelFile = modelFiles [ n ] ; if ( options . UML . includeSystemNamespace === 'all' ) { result = result . concat ( modelFile . getAllDeclarations ( ) ) ; } else if ( modelFile . getNamespace ( ) !== ModelUtil . getSystemNamespace ( ) ) { result = result . concat ( modelFile . getAllDeclarations ( ) ) ; } } parameters . fileWriter . writeLine ( 0 , '@startuml composer' ) ; parameters . fileWriter . writeLine ( 0 , "'** Auto generated content, any changes may be lost **'" ) ; parameters . fileWriter . writeLine ( 0 , "!define DATE %date[EEE, MMM d, ''yy 'at' HH:mm]%" ) ; if ( options . UML . diagramTheme === 'yellow' ) { parameters . fileWriter . writeLine ( 0 , 'skinparam class {' ) ; parameters . fileWriter . writeLine ( 0 , '  Font {' ) ; parameters . fileWriter . writeLine ( 0 , '    Color Black' ) ; parameters . fileWriter . writeLine ( 0 , '    Style Plain' ) ; parameters . fileWriter . writeLine ( 0 , '    Size 16' ) ; parameters . fileWriter . writeLine ( 0 , '  }' ) ; parameters . fileWriter . writeLine ( 0 , '}' ) ; parameters . fileWriter . writeLine ( 0 , 'skinparam title {' ) ; parameters . fileWriter . writeLine ( 0 , '  BackgroundColor LightYellow' ) ; } else { parameters . fileWriter . writeLine ( 0 , 'skinparam class {' ) ; parameters . fileWriter . writeLine ( 0 , '  BackgroundColor AliceBlue' ) ; parameters . fileWriter . writeLine ( 0 , '  BorderColor SteelBlue' ) ; parameters . fileWriter . writeLine ( 0 , '  BorderColor SteelBlue' ) ; parameters . fileWriter . writeLine ( 0 , '  ArrowColor SteelBlue' ) ; parameters . fileWriter . writeLine ( 0 , '  Font {' ) ; parameters . fileWriter . writeLine ( 0 , '    Color Black' ) ; parameters . fileWriter . writeLine ( 0 , '    Style Plain' ) ; parameters . fileWriter . writeLine ( 0 , '    Size 16' ) ; parameters . fileWriter . writeLine ( 0 , '  }' ) ; parameters . fileWriter . writeLine ( 0 , '}' ) ; parameters . fileWriter . writeLine ( 0 , 'skinparam title {' ) ; parameters . fileWriter . writeLine ( 0 , '  BackgroundColor AliceBlue' ) ; parameters . fileWriter . writeLine ( 0 , '  BorderColor SteelBlue' ) ; } parameters . fileWriter . writeLine ( 0 , '  BorderThickness 0.5' ) ; parameters . fileWriter . writeLine ( 0 , '  BorderRoundCorner 6' ) ; parameters . fileWriter . writeLine ( 0 , '  FontColor Black' ) ; parameters . fileWriter . writeLine ( 0 , '  FontSize 18' ) ; parameters . fileWriter . writeLine ( 0 , '}' ) ; if ( options . UML . diagramStyle === 'handwritten' ) { parameters . fileWriter . writeLine ( 0 , 'skinparam handwritten true' ) ; } else if ( options . UML . diagramStyle === 'monochrome' ) { parameters . fileWriter . writeLine ( 0 , 'skinparam monochrome true' ) ; } else if ( options . UML . diagramStyle === 'monochrome-reverse' ) { parameters . fileWriter . writeLine ( 0 , 'skinparam monochrome reverse' ) ; } parameters . fileWriter . writeLine ( 0 , 'title' ) ; parameters . fileWriter . writeLine ( 0 , diagramTitle ) ; parameters . fileWriter . writeLine ( 0 , 'end title' ) ; result . forEach ( ( decl ) => { decl . accept ( visitor , parameters ) ; } ) ; if ( options . UML . includeSystemNamespace === 'none' ) { const model = writer . getBuffer ( ) ; if ( model . includes ( 'org.hyperledger.composer.system.Asset' ) ) { parameters . fileWriter . writeLine ( 0 , 'hide org.hyperledger.composer.system.Asset' ) ; } if ( model . includes ( 'org.hyperledger.composer.system.Participant' ) ) { parameters . fileWriter . writeLine ( 0 , 'hide org.hyperledger.composer.system.Participant' ) ; } if ( model . includes ( 'org.hyperledger.composer.system.Transaction' ) ) { parameters . fileWriter . writeLine ( 0 , 'hide org.hyperledger.composer.system.Transaction' ) ; } if ( model . includes ( 'org.hyperledger.composer.system.Event' ) ) { parameters . fileWriter . writeLine ( 0 , 'hide org.hyperledger.composer.system.Event' ) ; } if ( model . includes ( 'org.hyperledger.composer.system.Registry' ) ) { parameters . fileWriter . writeLine ( 0 , 'hide org.hyperledger.composer.system.Registry' ) ; } } parameters . fileWriter . writeLine ( 0 , 'right footer DATE' ) ; parameters . fileWriter . writeLine ( 0 , '@enduml' ) ; connection . sendRequest ( 'openUML' , writer . getBuffer ( ) , originatingFileName ) ; } param { TextDocument } textDocument function validateTextDocument ( textDocument ) { const langId = textDocument . languageId ; const txt = textDocument . getText ( ) ; if ( txt != null && txt . length > 0 ) { if ( langId === 'composer-acl' ) { validateNewAclModelFile ( textDocument ) ; } else if ( langId === 'composer-qry' ) { validateNewQueryModelFile ( textDocument ) ; } else { validateCtoModelFile ( textDocument ) ; const aclFile = aclManager . getAclFile ( ) ; if ( aclFile != null ) { validateExistingAclModelFile ( aclFile ) ; } const queryFile = queryManager . getQueryFile ( ) ; if ( queryFile != null ) { validateExistingQueryModelFile ( queryFile ) ; } } } else { sendDiagnosticSuccess ( textDocument . uri ) ; } } param { TextDocument } textDocument private async function validateCtoModelFile ( textDocument ) { try { const currentModels = [ ] ; documents . all ( ) . forEach ( ( doc ) => { if ( doc . languageId === 'composer' ) { const modelContent = doc . getText ( ) ; if ( modelContent . length > 0 ) { try { const TheModel = new ModelFile ( modelManager , modelContent , doc . uri ) ; TheModel . lineCount = doc . lineCount ; if ( ! modelManager . getModelFile ( TheModel . getNamespace ( ) ) ) { currentModels . push ( TheModel ) ; } } catch ( err ) { buildAndSendDiagnosticFromException ( err , doc . lineCount , doc . uri ) ; } } } } ) ; if ( currentModels . length > 0 ) { try { modelManager . addModelFiles ( currentModels , null , true ) ; } catch ( err ) { } } const modelContents = textDocument . getText ( ) ; const aModel = new ModelFile ( modelManager , modelContents , textDocument . uri ) ; aModel . lineCount = textDocument . lineCount ; const existingModel = modelManager . getModelFile ( aModel . getNamespace ( ) ) ; if ( existingModel && existingModel . getName ( ) === textDocument . uri ) { modelManager . updateModelFile ( aModel , null , true ) ; } else { modelManager . addModelFile ( aModel , null , true ) ; } await modelManager . updateExternalModels ( ) ; modelManager . getModelFiles ( ) . forEach ( ( model ) => { try { model . validate ( ) ; sendDiagnosticSuccess ( model . getName ( ) ) ; } catch ( err ) { buildAndSendDiagnosticFromException ( err , model . lineCount , model . getName ( ) ) ; } } ) ; sendDiagnosticSuccess ( textDocument . uri ) ; } catch ( err ) { buildAndSendDiagnosticFromException ( err , textDocument . lineCount , textDocument . uri ) ; } } param { TextDocument } textDocument private function validateNewAclModelFile ( textDocument ) { try { const txt = textDocument . getText ( ) ; const aclFile = aclManager . createAclFile ( textDocument . uri , txt ) ; aclFile . lineCount = textDocument . lineCount ; aclManager . setAclFile ( aclFile ) ; sendDiagnosticSuccess ( textDocument . uri ) ; } catch ( err ) { buildAndSendDiagnosticFromException ( err , textDocument . lineCount , textDocument . uri ) ; } } param { string } textDocument private function validateExistingAclModelFile ( aclFile ) { try { aclFile . validate ( ) ; sendDiagnosticSuccess ( aclFile . getIdentifier ( ) ) ; } catch ( err ) { buildAndSendDiagnosticFromException ( err , aclFile . lineCount , aclFile . getIdentifier ( ) ) ; } } param { TextDocument } textDocument private function validateNewQueryModelFile ( textDocument ) { try { const txt = textDocument . getText ( ) ; const queryFile = queryManager . createQueryFile ( textDocument . uri , txt ) ; queryFile . lineCount = textDocument . lineCount ; queryManager . setQueryFile ( queryFile ) ; sendDiagnosticSuccess ( textDocument . uri ) ; } catch ( err ) { buildAndSendDiagnosticFromException ( err , textDocument . lineCount , textDocument . uri ) ; } } param { string } textDocument private function validateExistingQueryModelFile ( queryFile ) { try { queryFile . validate ( ) ; sendDiagnosticSuccess ( queryFile . getIdentifier ( ) ) ; } catch ( err ) { buildAndSendDiagnosticFromException ( err , queryFile . lineCount , queryFile . getIdentifier ( ) ) ; } } param { excepion } err param { number } lineCount param { string } sourceURI private function buildAndSendDiagnosticFromException ( err , lineCount , sourceURI ) { const diagnostics = [ ] ; let curLine = 0 ; let curColumn = 0 ; let endLine = lineCount ; let endColumn = Number . MAX_VALUE ; let fullMsg = err . name + ': ' ; if ( typeof err . getShortMessage === 'function' ) { fullMsg += err . getShortMessage ( ) ; } else { fullMsg += err . message ; } let finalMsg = fullMsg ; if ( typeof err . getFileLocation === 'function' ) { const location = err . getFileLocation ( ) ; if ( location ) { curLine = location . start . line - 1 ; endLine = location . end . line - 1 ; curColumn = location . start . column - 1 ; endColumn = location . end . column - 1 ; } } else { let index = fullMsg . lastIndexOf ( '. Line ' ) ; if ( index !== - 1 ) { finalMsg = fullMsg . substr ( 0 , index + 1 ) ; let current = fullMsg . substr ( index + 7 ) ; curLine = parseInt ( current , 10 ) - 1 ; if ( isNaN ( curLine ) || curLine < 0 ) { curLine = 0 ; } endLine = curLine ; index = current . lastIndexOf ( ' column ' ) ; current = current . substr ( index + 8 ) ; curColumn = parseInt ( current , 10 ) - 1 ; if ( isNaN ( curColumn ) || curColumn < 0 ) { curColumn = 0 ; } endColumn = curColumn ; } } diagnostics . push ( { severity : DiagnosticSeverity . Error , range : { start : { line : curLine , character : curColumn } , end : { line : endLine , character : endColumn } } , code : err . name , message : finalMsg , source : 'Composer' } ) ; connection . sendDiagnostics ( { uri : sourceURI , diagnostics } ) ; } param { string } sourceURI private function sendDiagnosticSuccess ( sourceURI ) { const diagnostics = [ ] ; connection . sendDiagnostics ( { uri : sourceURI , diagnostics } ) ; } param { TextDocument } textDocument function closeTextDocument ( textDocument ) { const langId = textDocument . languageId ; const txt = textDocument . getText ( ) ; if ( txt != null && txt . length > 0 ) { if ( langId === 'composer-acl' ) { } else if ( langId === 'composer-qry' ) { } else { try { const model = new ModelFile ( modelManager , txt , textDocument . uri ) ; const existingModel = modelManager . getModelFile ( model . getNamespace ( ) ) ; if ( existingModel && existingModel . getName ( ) === textDocument . uri ) { modelManager . deleteModelFile ( model . getNamespace ( ) ) ; } else { sendDiagnosticSuccess ( textDocument . uri ) ; } modelManager . getModelFiles ( ) . forEach ( ( currrentModel ) => { try { currrentModel . validate ( ) ; sendDiagnosticSuccess ( currrentModel . getName ( ) ) ; } catch ( err ) { buildAndSendDiagnosticFromException ( err , currrentModel . lineCount , currrentModel . getName ( ) ) ; } } ) ; } catch ( err ) { } const aclFile = aclManager . getAclFile ( ) ; if ( aclFile != null ) { validateExistingAclModelFile ( aclFile ) ; } const queryFile = queryManager . getQueryFile ( ) ; if ( queryFile != null ) { validateExistingQueryModelFile ( queryFile ) ; } } } else { sendDiagnosticSuccess ( textDocument . uri ) ; } } connection . onDidChangeWatchedFiles ( ( change ) => { } ) ; connection . onCompletion ( ( textDocumentPosition ) : CompletionItem [ ] => { return [ { label : 'asset' , kind : CompletionItemKind . Text , data : 1 } , { label : 'participant' , kind : CompletionItemKind . Text , data : 2 } , { label : 'transaction' , kind : CompletionItemKind . Text , data : 3 } , { label : 'enum' , kind : CompletionItemKind . Text , data : 4 } ] ; } ) ; connection . onCompletionResolve ( ( item ) : CompletionItem => { if ( item . data === 1 ) { item . detail = 'asset details' , item . documentation = 'Add an asset.' ; } else if ( item . data === 2 ) { item . detail = 'participant details' , item . documentation = 'Add an participant' ; } else if ( item . data === 3 ) { item . detail = 'transaction details' , item . documentation = 'Add an transaction' ; } else if ( item . data === 4 ) { item . detail = 'enum details' , item . documentation = 'Add an enum' ; } return item ; } ) ; connection . listen ( ) ;