1{-# LANGUAGE OverloadedStrings #-}
2module Generate.JavaScript.Expression
14import qualified Data.IntMap as IntMap
15import qualified Data.List as List
16import qualified Data.Map as Map
17import qualified Data.Text.Encoding as Text
19import qualified AST.Canonical as Can
20import qualified AST.Optimized as Opt
21import qualified AST.Module.Name as ModuleName
22import qualified Data.Index as Index
23import qualified Elm.Compiler.Type as Type
24import qualified Elm.Compiler.Type.Extract as Extract
25import qualified Elm.Interface as I
26import qualified Elm.Name as N
27import qualified Elm.Package as Pkg
28import qualified Elm.Compiler.Version as Version
29import qualified Generate.JavaScript.Builder as JS
30import qualified Generate.JavaScript.Name as Name
31import qualified Json.Encode as Encode
32import qualified Optimize.DecisionTree as DT
33import qualified Reporting.Region as R
47generateJsExpr :: Mode -> Opt.Expr -> JS.Expr
48generateJsExpr mode expression =
49 codeToExpr (generate mode expression)
52generate :: Mode -> Opt.Expr -> Code
53generate mode expression =
62 JS.Call toChar [ JS.String (Text.encodeUtf8Builder char) ]
65 JS.String (Text.encodeUtf8Builder char)
68 JsExpr $ JS.String (Text.encodeUtf8Builder string)
74 JsExpr $ JS.Float float
77 JsExpr $ JS.Ref (Name.fromLocal name)
79 Opt.VarGlobal (Opt.Global home name) ->
80 JsExpr $ JS.Ref (Name.fromGlobal home name)
82 Opt.VarEnum (Opt.Global home name) index ->
85 JsExpr $ JS.Ref (Name.fromGlobal home name)
88 JsExpr $ JS.Int (Index.toMachine index)
90 Opt.VarBox (Opt.Global home name) ->
93 Name.Debug _ -> Name.fromGlobal home name
94 Name.Prod _ _ -> Name.fromGlobal ModuleName.basics N.identity
96 Opt.VarCycle home name ->
97 JsExpr $ JS.Call (JS.Ref (Name.fromCycle home name)) []
99 Opt.VarDebug name home region unhandledValueName ->
100 JsExpr $ generateDebug name home region unhandledValueName
102 Opt.VarKernel home name ->
103 JsExpr $ JS.Ref (Name.fromKernel home name)
108 JsExpr $ JS.Ref (Name.fromKernel N.list "Nil")
113 (JS.Ref (Name.fromKernel N.list "fromArray"))
114 [ JS.Array $ map (generateJsExpr mode) entries
117 Opt.Function args body ->
118 generateFunction (map Name.fromLocal args) (generate mode body)
120 Opt.Call func args ->
121 JsExpr $ generateCall mode func args
123 Opt.TailCall name args ->
124 JsBlock $ generateTailCall mode name args
126 Opt.If branches final ->
127 generateIf mode branches final
131 generateDef mode def : codeToStmtList (generate mode body)
133 Opt.Destruct (Opt.Destructor name path) body ->
135 pathExpr = generatePath mode path
136 pathDef = JS.Var [ (Name.fromLocal name, Just pathExpr) ]
138 JsBlock $ pathDef : codeToStmtList (generate mode body)
140 Opt.Case label root decider jumps ->
141 JsBlock $ generateCase mode label root decider jumps
143 Opt.Accessor field ->
144 JsExpr $ JS.Function Nothing [Name.dollar]
146 JS.Access (JS.Ref Name.dollar) (Name.fromField mode field)
149 Opt.Access record field ->
150 JsExpr $ JS.Access (generateJsExpr mode record) (Name.fromField mode field)
152 Opt.Update record fields ->
154 JS.Call (JS.Ref (Name.fromKernel N.utils "update"))
155 [ generateJsExpr mode record
156 , generateRecord mode fields
160 JsExpr $ generateRecord mode fields
165 JsExpr $ JS.Ref (Name.fromKernel N.utils "Tuple0")
170 Opt.Tuple a b maybeC ->
174 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple2"))
175 [ generateJsExpr mode a
176 , generateJsExpr mode b
180 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple3"))
181 [ generateJsExpr mode a
182 , generateJsExpr mode b
183 , generateJsExpr mode c
187 let string = JS.String (Text.encodeUtf8Builder src) in
188 JsExpr $ JS.Object [ ( Name.fromLocal "src", string ) ]
200codeToExpr :: Code -> JS.Expr
206 JsBlock [ JS.Return (Just expr) ] ->
210 JS.Call (JS.Function Nothing [] stmts) []
213codeToStmtList :: Code -> [JS.Stmt]
216 JsExpr (JS.Call (JS.Function Nothing [] stmts) []) ->
220 [ JS.Return (Just expr) ]
226codeToStmt :: Code -> JS.Stmt
229 JsExpr (JS.Call (JS.Function Nothing [] stmts) []) ->
233 JS.Return (Just expr)
246{-# NOINLINE toChar #-}
249 JS.Ref (Name.fromKernel N.utils "chr")
256generateCtor :: Mode -> N.Name -> Index.ZeroBased -> Int -> Code
257generateCtor mode name index arity =
260 Index.indexedMap (\i _ -> Name.fromIndex i) [1 .. arity]
264 Name.Debug _ -> JS.String (N.toBuilder name)
265 Name.Prod _ _ -> JS.Int (Index.toMachine index)
267 generateFunction argNames $ JsExpr $ JS.Object $
268 (Name.dollar, ctorTag) : map (\n -> (n, JS.Ref n)) argNames
275generateRecord :: Mode -> Map.Map N.Name Opt.Expr -> JS.Expr
276generateRecord mode fields =
278 toPair (field, value) =
279 (Name.fromField mode field, generateJsExpr mode value)
281 JS.Object (map toPair (Map.toList fields))
288generateDebug :: N.Name -> ModuleName.Canonical -> R.Region -> Maybe N.Name -> JS.Expr
289generateDebug name (ModuleName.Canonical _ home) region unhandledValueName =
290 if name /= "crash" then
291 JS.Ref (Name.fromGlobal ModuleName.debug name)
293 case unhandledValueName of
295 JS.Call (JS.Ref (Name.fromKernel N.utils "crash")) $
296 [ JS.String (N.toBuilder home)
297 , regionToJsExpr region
301 JS.Call (JS.Ref (Name.fromKernel N.utils "crashCase")) $
302 [ JS.String (N.toBuilder home)
303 , regionToJsExpr region
304 , JS.Ref (Name.fromLocal valueName)
308regionToJsExpr :: R.Region -> JS.Expr
309regionToJsExpr (R.Region start end) =
311 [ ( Name.fromLocal "start", positionToJsExpr start )
312 , ( Name.fromLocal "end", positionToJsExpr end )
316positionToJsExpr :: R.Position -> JS.Expr
317positionToJsExpr (R.Position line column) =
319 [ ( Name.fromLocal "line", JS.Int line )
320 , ( Name.fromLocal "column", JS.Int column )
328generateFunction :: [Name.Name] -> Code -> Code
329generateFunction args body =
330 case IntMap.lookup (length args) funcHelpers of
334 [ JS.Function Nothing args $
341 JsExpr $ JS.Function Nothing [arg] $
344 foldr addArg body args
347{-# NOINLINE funcHelpers #-}
348funcHelpers :: IntMap.IntMap JS.Expr
351 map (\n -> (n, JS.Ref (Name.makeF n))) [2..9]
358generateCall :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
359generateCall mode func args =
361 Opt.VarGlobal global@(Opt.Global (ModuleName.Canonical pkg _) _) | pkg == Pkg.core ->
362 generateCoreCall mode global args
367 generateCallHelp mode func args
372 generateJsExpr mode arg
375 generateCallHelp mode func args
378 generateCallHelp mode func args
381generateCallHelp :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
382generateCallHelp mode func args =
384 (generateJsExpr mode func)
385 (map (generateJsExpr mode) args)
388generateGlobalCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
389generateGlobalCall home name args =
390 generateNormalCall (JS.Ref (Name.fromGlobal home name)) args
393generateNormalCall :: JS.Expr -> [JS.Expr] -> JS.Expr
394generateNormalCall func args =
395 case IntMap.lookup (length args) callHelpers of
397 JS.Call helper (func:args)
400 List.foldl' (\f a -> JS.Call f [a]) func args
403{-# NOINLINE callHelpers #-}
404callHelpers :: IntMap.IntMap JS.Expr
407 map (\n -> (n, JS.Ref (Name.makeA n))) [2..9]
414generateCoreCall :: Mode -> Opt.Global -> [Opt.Expr] -> JS.Expr
415generateCoreCall mode (Opt.Global home@(ModuleName.Canonical _ moduleName) name) args =
416 if moduleName == N.basics then
417 generateBasicsCall mode home name args
419 else if moduleName == N.bitwise then
420 generateBitwiseCall home name (map (generateJsExpr mode) args)
422 else if moduleName == N.tuple then
423 generateTupleCall home name (map (generateJsExpr mode) args)
425 else if moduleName == N.jsArray then
426 generateJsArrayCall home name (map (generateJsExpr mode) args)
429 generateGlobalCall home name (map (generateJsExpr mode) args)
432generateTupleCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
433generateTupleCall home name args =
437 "first" -> JS.Access value (Name.fromLocal "a")
438 "second" -> JS.Access value (Name.fromLocal "b")
439 _ -> generateGlobalCall home name args
442 generateGlobalCall home name args
445generateJsArrayCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
446generateJsArrayCall home name args =
448 [entry] | name == "singleton" -> JS.Array [entry]
449 [index, array] | name == "unsafeGet" -> JS.Index array index
450 _ -> generateGlobalCall home name args
453generateBitwiseCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
454generateBitwiseCall home name args =
458 "complement" -> JS.Prefix JS.PrefixBNot arg
459 _ -> generateGlobalCall home name args
463 "and" -> JS.Infix JS.OpBAnd left right
464 "or" -> JS.Infix JS.OpBOr left right
465 "xor" -> JS.Infix JS.OpBXor left right
466 "shiftLeftBy" -> JS.Infix JS.OpLShift right left
467 "shiftRightBy" -> JS.Infix JS.OpSpRShift right left
468 "shiftRightZfBy" -> JS.Infix JS.OpZfRShift right left
469 _ -> generateGlobalCall home name args
472 generateGlobalCall home name args
475generateBasicsCall :: Mode -> ModuleName.Canonical -> N.Name -> [Opt.Expr] -> JS.Expr
476generateBasicsCall mode home name args =
479 let arg = generateJsExpr mode elmArg in
481 "not" -> JS.Prefix JS.PrefixLNot arg
483 "truncate" -> JS.Infix JS.OpBOr arg (JS.Int 0)
484 _ -> generateGlobalCall home name [arg]
486 [elmLeft, elmRight] ->
488 "composeL" -> decomposeL mode elmLeft [elmRight]
489 "composeR" -> decomposeR mode [elmLeft] elmRight
490 "append" -> append mode elmLeft elmRight
491 "apL" -> generateJsExpr mode $ apply elmLeft elmRight
492 "apR" -> generateJsExpr mode $ apply elmRight elmLeft
495 left = generateJsExpr mode elmLeft
496 right = generateJsExpr mode elmRight
499 "add" -> JS.Infix JS.OpAdd left right
500 "sub" -> JS.Infix JS.OpSub left right
501 "mul" -> JS.Infix JS.OpMul left right
502 "fdiv" -> JS.Infix JS.OpDiv left right
503 "idiv" -> JS.Infix JS.OpBOr (JS.Infix JS.OpDiv left right) (JS.Int 0)
504 "eq" -> equal left right
505 "neq" -> notEqual left right
506 "lt" -> cmp JS.OpLT JS.OpLT 0 left right
507 "gt" -> cmp JS.OpGT JS.OpGT 0 left right
508 "le" -> cmp JS.OpLEq JS.OpLT 1 left right
509 "ge" -> cmp JS.OpGEq JS.OpGT (-1) left right
510 "or" -> JS.Infix JS.OpLOr left right
511 "and" -> JS.Infix JS.OpLAnd left right
512 "xor" -> JS.Infix JS.OpStrictNEq left right
513 _ -> generateGlobalCall home name [left, right]
516 generateGlobalCall home name (map (generateJsExpr mode) args)
519equal :: JS.Expr -> JS.Expr -> JS.Expr
521 if isLiteral left || isLiteral right then
524 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
527notEqual :: JS.Expr -> JS.Expr -> JS.Expr
529 if isLiteral left || isLiteral right then
532 JS.Prefix JS.PrefixLNot $
533 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
536cmp :: JS.InfixOp -> JS.InfixOp -> Int -> JS.Expr -> JS.Expr -> JS.Expr
537cmp idealOp backupOp backupInt left right =
538 if isLiteral left || isLiteral right then
539 JS.Infix idealOp left right
542 (JS.Call (JS.Ref (Name.fromKernel N.utils "cmp")) [left, right])
546isLiteral :: JS.Expr -> Bool
565decomposeL :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
566decomposeL mode expr funcs =
568 Opt.Call (Opt.VarGlobal (Opt.Global home "composeL")) [left, func]
569 | home == ModuleName.basics ->
570 decomposeL mode left (func:funcs)
573 generateJsExpr mode $
574 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
577decomposeR :: Mode -> [Opt.Expr] -> Opt.Expr -> JS.Expr
578decomposeR mode funcs expr =
580 Opt.Call (Opt.VarGlobal (Opt.Global home "composeR")) [func, right]
581 | home == ModuleName.basics ->
582 decomposeR mode (func:funcs) right
585 generateJsExpr mode $
586 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
589apply :: Opt.Expr -> Opt.Expr -> Opt.Expr
592 Opt.Accessor field ->
593 Opt.Access value field
596 Opt.Call f (args ++ [value])
599 Opt.Call func [value]
602append :: Mode -> Opt.Expr -> Opt.Expr -> JS.Expr
603append mode left right =
604 let seqs = generateJsExpr mode left : toSeqs mode right in
605 if any isStringLiteral seqs then
606 foldr1 (JS.Infix JS.OpAdd) seqs
611jsAppend :: JS.Expr -> JS.Expr -> JS.Expr
613 JS.Call (JS.Ref (Name.fromKernel N.utils "ap")) [a, b]
616toSeqs :: Mode -> Opt.Expr -> [JS.Expr]
619 Opt.Call (Opt.VarGlobal (Opt.Global home "append")) [left, right]
620 | home == ModuleName.basics ->
621 generateJsExpr mode left : toSeqs mode right
624 [generateJsExpr mode expr]
627isStringLiteral :: JS.Expr -> Bool
628isStringLiteral expr =
638-- SIMPLIFY INFIX OPERATORS
641strictEq :: JS.Expr -> JS.Expr -> JS.Expr
645 JS.Prefix JS.PrefixLNot right
648 if bool then right else JS.Prefix JS.PrefixLNot right
653 JS.Prefix JS.PrefixLNot left
656 if bool then left else JS.Prefix JS.PrefixLNot left
659 JS.Infix JS.OpStrictEq left right
662strictNEq :: JS.Expr -> JS.Expr -> JS.Expr
663strictNEq left right =
669 if bool then JS.Prefix JS.PrefixLNot right else right
677 if bool then JS.Prefix JS.PrefixLNot left else left
680 JS.Infix JS.OpStrictNEq left right
687-- TODO check if JS minifiers collapse unnecessary temporary variables
689generateTailCall :: Mode -> N.Name -> [(N.Name, Opt.Expr)] -> [JS.Stmt]
690generateTailCall mode name args =
692 toTempVars (argName, arg) =
693 ( Name.makeTemp argName, Just (generateJsExpr mode arg) )
695 toRealVars (argName, _) =
697 JS.Assign (JS.LRef (Name.fromLocal argName)) (JS.Ref (Name.makeTemp argName))
699 JS.Var (map toTempVars args)
700 : map toRealVars args
701 ++ [ JS.Continue (Just (Name.fromLocal name)) ]
708generateDef :: Mode -> Opt.Def -> JS.Stmt
709generateDef mode def =
712 JS.Var [ (Name.fromLocal name, Just (generateJsExpr mode body)) ]
714 Opt.TailDef name argNames body ->
715 JS.Var [ (Name.fromLocal name, Just (codeToExpr (generateTailDef mode name argNames body))) ]
718generateTailDef :: Mode -> N.Name -> [N.Name] -> Opt.Expr -> Code
719generateTailDef mode name argNames body =
720 generateFunction (map Name.fromLocal argNames) $ JsBlock $
721 [ JS.Labelled (Name.fromLocal name) $
722 JS.While (JS.Bool True) $
723 codeToStmt $ generate mode body
731generatePath :: Mode -> Opt.Path -> JS.Expr
732generatePath mode path =
734 Opt.Index index subPath ->
735 JS.Access (generatePath mode subPath) (Name.fromIndex index)
738 JS.Ref (Name.fromLocal name)
740 Opt.Field field subPath ->
741 JS.Access (generatePath mode subPath) (Name.fromField mode field)
746 JS.Access (generatePath mode subPath) (Name.fromIndex Index.first)
749 generatePath mode subPath
756generateIf :: Mode -> [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> Code
757generateIf mode givenBranches givenFinal =
760 crushIfs givenBranches givenFinal
762 convertBranch (condition, expr) =
763 ( generateJsExpr mode condition
767 branchExprs = map convertBranch branches
768 finalCode = generate mode final
770 if isBlock finalCode || any (isBlock . snd) branchExprs then
771 JsBlock [ foldr addStmtIf (codeToStmt finalCode) branchExprs ]
773 JsExpr $ foldr addExprIf (codeToExpr finalCode) branchExprs
776addExprIf :: (JS.Expr, Code) -> JS.Expr -> JS.Expr
777addExprIf (condition, branch) final =
778 JS.If condition (codeToExpr branch) final
781addStmtIf :: (JS.Expr, Code) -> JS.Stmt -> JS.Stmt
782addStmtIf (condition, branch) final =
783 JS.IfStmt condition (codeToStmt branch) final
786isBlock :: Code -> Bool
793crushIfs :: [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
794crushIfs branches final =
795 crushIfsHelp [] branches final
799 :: [(Opt.Expr, Opt.Expr)]
800 -> [(Opt.Expr, Opt.Expr)]
802 -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
803crushIfsHelp visitedBranches unvisitedBranches final =
804 case unvisitedBranches of
807 Opt.If subBranches subFinal ->
808 crushIfsHelp visitedBranches subBranches subFinal
811 (reverse visitedBranches, final)
813 visiting : unvisited ->
814 crushIfsHelp (visiting : visitedBranches) unvisited final
821generateCase :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [(Int, Opt.Expr)] -> [JS.Stmt]
822generateCase mode label root decider jumps =
823 foldr (goto mode label) (generateDecider mode label root decider) jumps
826goto :: Mode -> N.Name -> (Int, Opt.Expr) -> [JS.Stmt] -> [JS.Stmt]
827goto mode label (index, branch) stmts =
831 (Name.makeLabel label index)
832 (JS.While (JS.Bool True) (JS.Block stmts))
834 labeledDeciderStmt : codeToStmtList (generate mode branch)
837generateDecider :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [JS.Stmt]
838generateDecider mode label root decisionTree =
840 Opt.Leaf (Opt.Inline branch) ->
841 codeToStmtList (generate mode branch)
843 Opt.Leaf (Opt.Jump index) ->
844 [ JS.Break (Just (Name.makeLabel label index)) ]
846 Opt.Chain testChain success failure ->
848 (List.foldl1' (JS.Infix JS.OpLAnd) (map (generateIfTest mode root) testChain))
849 (JS.Block $ generateDecider mode label root success)
850 (JS.Block $ generateDecider mode label root failure)
853 Opt.FanOut path edges fallback ->
855 (generateCaseTest mode root path (fst (head edges)))
857 (\edge cases -> generateCaseBranch mode label root edge : cases)
858 [ JS.Default (generateDecider mode label root fallback) ]
864generateIfTest :: Mode -> N.Name -> (DT.Path, DT.Test) -> JS.Expr
865generateIfTest mode root (path, test) =
867 value = pathToJsExpr mode root path
870 DT.IsCtor name index _ _ ->
871 strictEq (JS.Access value Name.dollar) $
873 Name.Debug _ -> JS.String (N.toBuilder name)
874 Name.Prod _ _ -> JS.Int (Index.toMachine index)
877 strictEq value (JS.Int int)
880 strictEq (JS.String (Text.encodeUtf8Builder char)) $
882 Name.Debug _ -> JS.Call (JS.Access value (Name.fromLocal "valueOf")) []
883 Name.Prod _ _ -> value
886 strictEq value (JS.String (Text.encodeUtf8Builder string))
889 JS.Access value (Name.fromLocal "b")
892 JS.Prefix JS.PrefixLNot $
893 JS.Access value (Name.fromLocal "b")
896 error "COMPILER BUG - there should never be tests on a tuple"
900generateCaseBranch :: Mode -> N.Name -> N.Name -> (DT.Test, Opt.Decider Opt.Choice) -> JS.Case
901generateCaseBranch mode label root (test, subTree) =
903 (generateCaseValue mode test)
904 (generateDecider mode label root subTree)
907generateCaseValue :: Mode -> DT.Test -> JS.Expr
908generateCaseValue mode test =
910 DT.IsCtor name index _ _ ->
912 Name.Debug _ -> JS.String (N.toBuilder name)
913 Name.Prod _ _ -> JS.Int (Index.toMachine index)
919 JS.String (Text.encodeUtf8Builder char)
922 JS.String (Text.encodeUtf8Builder string)
925 error "COMPILER BUG - there should never be three tests on a list"
928 error "COMPILER BUG - there should never be three tests on a list"
931 error "COMPILER BUG - there should never be three tests on a tuple"
934generateCaseTest :: Mode -> N.Name -> DT.Path -> DT.Test -> JS.Expr
935generateCaseTest mode root path exampleTest =
937 value = pathToJsExpr mode root path
940 DT.IsCtor _ _ _ opts ->
943 JS.Access value Name.dollar
948 JS.Access value Name.dollar
965 JS.Call (JS.Access value (Name.fromLocal "valueOf")) []
971 error "COMPILER BUG - there should never be three tests on a list"
974 error "COMPILER BUG - there should never be three tests on a list"
977 error "COMPILER BUG - there should never be three tests on a list"
984pathToJsExpr :: Mode -> N.Name -> DT.Path -> JS.Expr
985pathToJsExpr mode root path =
987 DT.Index index subPath ->
988 JS.Access (pathToJsExpr mode root subPath) (Name.fromIndex index)
993 JS.Access (pathToJsExpr mode root subPath) (Name.fromIndex Index.first)
996 pathToJsExpr mode root subPath
999 JS.Ref (Name.fromLocal root)
1006generateMain :: Mode -> I.Interfaces -> ModuleName.Canonical -> Opt.Main -> JS.Expr
1007generateMain mode interfaces home main =
1010 JS.Ref (Name.fromKernel N.browser "staticPage")
1011 # JS.Ref (Name.fromGlobal home "main")
1015 Opt.Dynamic msgType decoder ->
1016 JS.Ref (Name.fromGlobal home "main")
1017 # generateJsExpr mode decoder
1018 # toDebugMetadata mode interfaces msgType
1021(#) :: JS.Expr -> JS.Expr -> JS.Expr
1026toDebugMetadata :: Mode -> I.Interfaces -> Can.Type -> JS.Expr
1027toDebugMetadata mode interfaces msgType =
1033 JS.Json $ Encode.object $
1034 [ ("versions", Encode.object [ ("elm", Pkg.encodeVersion Version.version) ])
1035 , ("types", Type.encodeMetadata (Extract.fromMsg interfaces msgType))
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list