import fuzzysort from 'fuzzysort' ; import CodeMirror , { Editor , Hint , Hints , Position , TextMarker } from '../../lib/codemirror' ; import { glslES100 , glslES300 , Name , NameWithType , NameWithParametersAndType , Docs , CodeSnippet , snippets , predefinedUniforms , predefinedAttributes } from './glsl-reference' ; import { insertPlaceholder } from './placeholder' ; interface Keyword { name : string ; docs ? : string ; } interface Variable extends Keyword { type : string ; } interface Snippet extends Keyword { snippet : string ; } interface Function extends Snippet { returns : string ; parameters : Parameter [ ] ; } interface Parameter { name : string ; type : string ; } function parseKeyword ( [ name , docs ] ) { return { name , docs } ; } function parseVariable ( [ variable , docs ] ) { variable = variable . replace ( /\s/g , '' ) ; const [ name , type ] = variable . split ( ':' ) ; return { name , type , docs } ; } function parseSnippet ( [ name , snippet , docs ] ) { return { name , snippet , docs } ; } function parseFunction ( [ func , docs ] ) { let aux ; let name ; let returns ; let parameters ; func = func . replace ( /\s/g , '' ) ; [ aux , returns ] = func . split ( '):' ) ; [ name , aux ] = aux . split ( '(' ) ; if ( aux !== '' ) { aux = aux . split ( ',' ) ; parameters = aux . map ( ( parameter ) => { const [ name , type ] = parameter . split ( ':' ) ; return { name , type } ; } ) ; } else { parameters = [ ] ; } const snippet = newFunctionSnippet ( name , parameters ) ; return { name , docs , returns , parameters , snippet } ; } function newFunctionSnippet ( name , parameters ) { const formattedParameters = parameters . map ( parameter => `{{${ parameter . name }: ${ parameter . type }}}` ) . join ( ', ' ) ; return name + '(' + formattedParameters + ')' ; } enum HintClass { LocalIdentifier = 'local-identifier' , Type = 'type' , Preprocessor = 'preprocessor' , Macro = 'macro' , StorageQualifier = 'storage-qualifier' , ParameterQualifier = 'parameter-qualifier' , PrecisionQualifier = 'precision-qualifier' , InvarianceQualifier = 'invariance-qualifier' , InterpolationQualifier = 'interpolation-qualifier' , Variable = 'variable' , Constant = 'constant' , Function = 'function' , Snippet = 'snippet' , VertexAttribute = 'vertex-attribute' , Uniform = 'uniform' } function mapKeywordsToHints ( keywords , className ? ) { return keywords . map ( ( { name , docs } ) => ( { text : name , displayText : name , docs , className , hint , render } ) ) ; } function mapVariablesToHints ( variables , className ? ) { return variables . map ( ( { name , type , docs } ) => ( { text : name , displayText : `${ name }: ${ type }` , docs , className , hint , render } ) ) ; } function mapFunctionsToHints ( functions , className ? ) { return functions . map ( ( { name , returns , parameters , docs , snippet } ) => { const displayParameters = parameters . map ( ( { name , type } ) => `${ name }: ${ type }` ) . join ( ', ' ) ; return { text : name , displayText : `${ name }(${ displayParameters }): ${ returns }` , docs , className , snippet , hint , render } ; } ) ; } function mapSnippetsToHints ( snippets , className ? ) { return snippets . map ( ( { name , docs , snippet } ) => ( { text : name , displayText : name , docs , className , snippet , hint , render } ) ) ; } function mapLocalIdentifiersToHints ( identifiers , className ? ) { return identifiers . map ( identifier => ( { text : identifier , className , hint , render } ) ) ; } const snippetsHints = [ ... mapSnippetsToHints ( snippets . map ( value => parseSnippet ( value ) ) , HintClass . Snippet ) ] ; const predefinedVariablesHints = [ ... mapVariablesToHints ( predefinedUniforms . map ( value => parseVariable ( value ) ) , HintClass . Uniform ) , ... mapVariablesToHints ( predefinedAttributes . map ( value => parseVariable ( value ) ) , HintClass . VertexAttribute ) ] ; const glslES100Hints = [ ... predefinedVariablesHints , ... snippetsHints , ... mapFunctionsToHints ( glslES100 . functions . map ( value => parseFunction ( value ) ) , HintClass . Function ) , ... mapKeywordsToHints ( glslES100 . types . map ( value => parseKeyword ( value ) ) , HintClass . Type ) , ... mapKeywordsToHints ( glslES100 . storageQualifiers . map ( value => parseKeyword ( value ) ) , HintClass . StorageQualifier ) , ... mapKeywordsToHints ( glslES100 . parameterQualifiers . map ( value => parseKeyword ( value ) ) , HintClass . ParameterQualifier ) , ... mapKeywordsToHints ( glslES100 . precisionQualifiers . map ( value => parseKeyword ( value ) ) , HintClass . PrecisionQualifier ) , ... mapKeywordsToHints ( glslES100 . invarianceQualifiers . map ( value => parseKeyword ( value ) ) , HintClass . InvarianceQualifier ) , ... mapKeywordsToHints ( glslES100 . preprocessor . map ( value => parseKeyword ( value ) ) , HintClass . Preprocessor ) , ... mapKeywordsToHints ( glslES100 . macros . map ( value => parseKeyword ( value ) ) , HintClass . Macro ) , ... mapKeywordsToHints ( glslES100 . others . map ( value => parseKeyword ( value ) ) ) , ... mapVariablesToHints ( glslES100 . variables . map ( value => parseVariable ( value ) ) , HintClass . Variable ) , ... mapVariablesToHints ( glslES100 . constants . map ( value => parseVariable ( value ) ) , HintClass . Constant ) ] ; const glslES300Hints = [ ... predefinedVariablesHints , ... snippetsHints , ... mapFunctionsToHints ( glslES300 . functions . map ( value => parseFunction ( value ) ) , HintClass . Function ) , ... mapKeywordsToHints ( glslES300 . types . map ( value => parseKeyword ( value ) ) , HintClass . Type ) , ... mapKeywordsToHints ( glslES300 . storageQualifiers . map ( value => parseKeyword ( value ) ) , HintClass . StorageQualifier ) , ... mapKeywordsToHints ( glslES300 . parameterQualifiers . map ( value => parseKeyword ( value ) ) , HintClass . ParameterQualifier ) , ... mapKeywordsToHints ( glslES300 . precisionQualifiers . map ( value => parseKeyword ( value ) ) , HintClass . PrecisionQualifier ) , ... mapKeywordsToHints ( glslES300 . invarianceQualifiers . map ( value => parseKeyword ( value ) ) , HintClass . InvarianceQualifier ) , ... mapKeywordsToHints ( glslES300 . interpolationQualifiers . map ( value => parseKeyword ( value ) ) , HintClass . InterpolationQualifier ) , ... mapKeywordsToHints ( glslES300 . preprocessor . map ( value => parseKeyword ( value ) ) , HintClass . Preprocessor ) , ... mapKeywordsToHints ( glslES300 . macros . map ( value => parseKeyword ( value ) ) , HintClass . Macro ) , ... mapKeywordsToHints ( glslES300 . others . map ( value => parseKeyword ( value ) ) ) , ... mapVariablesToHints ( glslES300 . variables . map ( value => parseVariable ( value ) ) , HintClass . Variable ) , ... mapVariablesToHints ( glslES300 . constants . map ( value => parseVariable ( value ) ) , HintClass . Constant ) ] ; const predefinedVariablesNames = [ ... predefinedUniforms . map ( value => parseVariable ( value ) . name ) , ... predefinedAttributes . map ( value => parseVariable ( value ) . name ) ] ; function render ( element , data , cur ) { const typeIcon = document . createElement ( 'span' ) ; typeIcon . classList . add ( 'icon' ) ; element . appendChild ( typeIcon ) ; const textSpan = document . createElement ( 'span' ) ; textSpan . classList . add ( 'display-text' ) ; textSpan . innerHTML = highlight ( cur . displayText || cur . text , cur . indexes || [ ] ) ; element . appendChild ( textSpan ) ; } function hint ( editor , data , selected ) { const { from , to } = data ; if ( ! selected . snippet ) { editor . replaceRange ( selected . text , from , to ) ; } else { const placeholderMarkers = [ ] ; editor . operation ( ( ) => { const lines = selected . snippet . split ( '\n' ) ; for ( let index = 0 ; index < lines . length ; index ++ ) { const line = lines [ index ] ; const isFirstLine = index === 0 ; const placeholders = [ ] ; let replacement = line . replace ( /\{\{.*?\}\}/g , ( match , offset ) => { placeholders . push ( { text : match . substring ( 2 , match . length - 2 ) , offset : offset - placeholders . length * 4 } ) ; return match . substring ( 2 , match . length - 2 ) ; } ) ; let cursor ; if ( isFirstLine ) { editor . replaceRange ( replacement , from , to ) ; cursor = { line : from . line , ch : from . ch } ; } else { editor . replaceRange ( '\n' + replacement , editor . getCursor ( ) ) ; cursor = { line : from . line + index , ch : 0 } ; } for ( let placeholder of placeholders ) { const from = { line : cursor . line , ch : cursor . ch + placeholder . offset } ; const to = { line : cursor . line , ch : cursor . ch + placeholder . offset + placeholder . text . length } ; const marker = insertPlaceholder ( editor , from , to , placeholder . text ) ; placeholderMarkers . push ( marker ) ; } editor . indentLine ( cursor . line ) ; } editor . setCursor ( placeholderMarkers [ 0 ] . find ( ) . to ) ; editor . getInputField ( ) . blur ( ) ; } ) ; setTimeout ( ( ) => { placeholderMarkers [ 0 ] . replacedWith . focus ( ) ; } , 0 ) ; } } function highlight ( text , indexes = [ ] , openingMark = '<b>' , closingMark = '</b>' ) { var highlighted = '' ; var matchesIndex = 0 ; var opened = false ; var textLength = text . length ; var matchesBest = indexes ; for ( var i = 0 ; i < textLength ; ++ i ) { var char = text [ i ] ; if ( matchesBest [ matchesIndex ] === i ) { ++ matchesIndex ; if ( ! opened ) { opened = true ; highlighted += openingMark ; } if ( matchesIndex === matchesBest . length ) { highlighted += char + closingMark + text . substr ( i + 1 ) ; break ; } } else { if ( opened ) { opened = false ; highlighted += closingMark ; } } highlighted += char ; } return highlighted ; } function getLocalIdentifiers ( editor ) { const firstLine = editor . firstLine ( ) ; const lastLine = editor . lastLine ( ) ; const cursor = editor . getCursor ( ) ; const identifiers = new Set ( ) ; for ( let line = firstLine ; line <= lastLine ; line ++ ) { const tokens = editor . getLineTokens ( line ) ; if ( line !== cursor . line ) { for ( let token of tokens ) { if ( token . type === 'identifier' && ! predefinedVariablesNames . includes ( token . string ) ) identifiers . add ( token . string ) ; } } else { for ( let token of tokens ) { if ( ! ( token . start <= cursor . ch && cursor . ch <= token . end ) && token . type === 'identifier' ) identifiers . add ( token . string ) ; } } } return Array . from ( identifiers ) ; } function getGLSLHintsHelper ( glslHints ) { return ( editor , options ) => { const cursor = editor . getCursor ( ) ; const tokenAtCursor = editor . getTokenAt ( cursor ) ; const cursorFollowsWhitespace = tokenAtCursor . string . trimLeft ( ) . length === 0 ; const start = cursorFollowsWhitespace ? cursor . ch : tokenAtCursor . start ; const end = tokenAtCursor . end > cursor . ch ? tokenAtCursor . end : cursor . ch ; const localIdentifiersHints = mapLocalIdentifiersToHints ( getLocalIdentifiers ( editor ) , HintClass . LocalIdentifier ) ; const hints = tokenAtCursor . type === 'attribute' || tokenAtCursor . string === '.' ? localIdentifiersHints : localIdentifiersHints . concat ( glslHints ) ; const results = fuzzysort . go < Hint > ( tokenAtCursor . string , hints , { key : 'text' } ) ; let list ; if ( options . trigger === undefined && results . length === 0 ) { list = hints ; } else { list = results . map ( ( { obj , indexes } ) : Hint => ( { ... obj , indexes } ) ) ; } const from = CodeMirror . Pos ( cursor . line , start ) ; const to = CodeMirror . Pos ( cursor . line , end ) ; return { list , from , to } ; } ; } CodeMirror . registerHelper ( 'hint' , 'glsl-es-100' , getGLSLHintsHelper ( glslES100Hints ) ) ; CodeMirror . registerHelper ( 'hint' , 'glsl-es-300' , getGLSLHintsHelper ( glslES300Hints ) ) ;