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]
217 [ JS.Return (Just expr) ]
223codeToStmt :: Code -> JS.Stmt
227 JS.Return (Just expr)
240{-# NOINLINE toChar #-}
243 JS.Ref (Name.fromKernel N.utils "chr")
250generateCtor :: Mode -> N.Name -> Index.ZeroBased -> Int -> Code
251generateCtor mode name index arity =
254 Index.indexedMap (\i _ -> Name.fromIndex i) [1 .. arity]
258 Name.Debug _ -> JS.String (N.toBuilder name)
259 Name.Prod _ _ -> JS.Int (Index.toMachine index)
261 generateFunction argNames $ JsExpr $ JS.Object $
262 (Name.dollar, ctorTag) : map (\n -> (n, JS.Ref n)) argNames
269generateRecord :: Mode -> Map.Map N.Name Opt.Expr -> JS.Expr
270generateRecord mode fields =
272 toPair (field, value) =
273 (Name.fromField mode field, generateJsExpr mode value)
275 JS.Object (map toPair (Map.toList fields))
282generateDebug :: N.Name -> ModuleName.Canonical -> R.Region -> Maybe N.Name -> JS.Expr
283generateDebug name (ModuleName.Canonical _ home) region unhandledValueName =
284 if name /= "crash" then
285 JS.Ref (Name.fromGlobal ModuleName.debug name)
287 case unhandledValueName of
289 JS.Call (JS.Ref (Name.fromKernel N.utils "crash")) $
290 [ JS.String (N.toBuilder home)
291 , regionToJsExpr region
295 JS.Call (JS.Ref (Name.fromKernel N.utils "crashCase")) $
296 [ JS.String (N.toBuilder home)
297 , regionToJsExpr region
298 , JS.Ref (Name.fromLocal valueName)
302regionToJsExpr :: R.Region -> JS.Expr
303regionToJsExpr (R.Region start end) =
305 [ ( Name.fromLocal "start", positionToJsExpr start )
306 , ( Name.fromLocal "end", positionToJsExpr end )
310positionToJsExpr :: R.Position -> JS.Expr
311positionToJsExpr (R.Position line column) =
313 [ ( Name.fromLocal "line", JS.Int line )
314 , ( Name.fromLocal "column", JS.Int column )
322generateFunction :: [Name.Name] -> Code -> Code
323generateFunction args body =
324 case IntMap.lookup (length args) funcHelpers of
328 [ JS.Function Nothing args $
335 JsExpr $ JS.Function Nothing [arg] $
338 foldr addArg body args
341{-# NOINLINE funcHelpers #-}
342funcHelpers :: IntMap.IntMap JS.Expr
345 map (\n -> (n, JS.Ref (Name.makeF n))) [2..9]
352generateCall :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
353generateCall mode func args =
355 Opt.VarGlobal global@(Opt.Global (ModuleName.Canonical pkg _) _) | pkg == Pkg.core ->
356 generateCoreCall mode global args
361 generateCallHelp mode func args
366 generateJsExpr mode arg
369 generateCallHelp mode func args
372 generateCallHelp mode func args
375generateCallHelp :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
376generateCallHelp mode func args =
378 (generateJsExpr mode func)
379 (map (generateJsExpr mode) args)
382generateGlobalCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
383generateGlobalCall home name args =
384 generateNormalCall (JS.Ref (Name.fromGlobal home name)) args
387generateNormalCall :: JS.Expr -> [JS.Expr] -> JS.Expr
388generateNormalCall func args =
389 case IntMap.lookup (length args) callHelpers of
391 JS.Call helper (func:args)
394 List.foldl' (\f a -> JS.Call f [a]) func args
397{-# NOINLINE callHelpers #-}
398callHelpers :: IntMap.IntMap JS.Expr
401 map (\n -> (n, JS.Ref (Name.makeA n))) [2..9]
408generateCoreCall :: Mode -> Opt.Global -> [Opt.Expr] -> JS.Expr
409generateCoreCall mode (Opt.Global home@(ModuleName.Canonical _ moduleName) name) args =
410 if moduleName == N.basics then
411 generateBasicsCall mode home name args
413 else if moduleName == N.bitwise then
414 generateBitwiseCall home name (map (generateJsExpr mode) args)
416 else if moduleName == N.tuple then
417 generateTupleCall home name (map (generateJsExpr mode) args)
419 else if moduleName == N.jsArray then
420 generateJsArrayCall home name (map (generateJsExpr mode) args)
423 generateGlobalCall home name (map (generateJsExpr mode) args)
426generateTupleCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
427generateTupleCall home name args =
431 "first" -> JS.Access value (Name.fromLocal "a")
432 "second" -> JS.Access value (Name.fromLocal "b")
433 _ -> generateGlobalCall home name args
436 generateGlobalCall home name args
439generateJsArrayCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
440generateJsArrayCall home name args =
442 [entry] | name == "singleton" -> JS.Array [entry]
443 [index, array] | name == "unsafeGet" -> JS.Index array index
444 _ -> generateGlobalCall home name args
447generateBitwiseCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
448generateBitwiseCall home name args =
452 "complement" -> JS.Prefix JS.PrefixBNot arg
453 _ -> generateGlobalCall home name args
457 "and" -> JS.Infix JS.OpBAnd left right
458 "or" -> JS.Infix JS.OpBOr left right
459 "xor" -> JS.Infix JS.OpBXor left right
460 "shiftLeftBy" -> JS.Infix JS.OpLShift right left
461 "shiftRightBy" -> JS.Infix JS.OpSpRShift right left
462 "shiftRightZfBy" -> JS.Infix JS.OpZfRShift right left
463 _ -> generateGlobalCall home name args
466 generateGlobalCall home name args
469generateBasicsCall :: Mode -> ModuleName.Canonical -> N.Name -> [Opt.Expr] -> JS.Expr
470generateBasicsCall mode home name args =
473 let arg = generateJsExpr mode elmArg in
475 "not" -> JS.Prefix JS.PrefixLNot arg
477 "truncate" -> JS.Infix JS.OpBOr arg (JS.Int 0)
478 _ -> generateGlobalCall home name [arg]
480 [elmLeft, elmRight] ->
482 "composeL" -> decomposeL mode elmLeft [elmRight]
483 "composeR" -> decomposeR mode [elmLeft] elmRight
484 "append" -> append mode elmLeft elmRight
485 "apL" -> generateJsExpr mode $ apply elmLeft elmRight
486 "apR" -> generateJsExpr mode $ apply elmRight elmLeft
489 left = generateJsExpr mode elmLeft
490 right = generateJsExpr mode elmRight
493 "add" -> JS.Infix JS.OpAdd left right
494 "sub" -> JS.Infix JS.OpSub left right
495 "mul" -> JS.Infix JS.OpMul left right
496 "fdiv" -> JS.Infix JS.OpDiv left right
497 "idiv" -> JS.Infix JS.OpBOr (JS.Infix JS.OpDiv left right) (JS.Int 0)
498 "eq" -> equal left right
499 "neq" -> notEqual left right
500 "lt" -> cmp JS.OpLT JS.OpLT 0 left right
501 "gt" -> cmp JS.OpGT JS.OpGT 0 left right
502 "le" -> cmp JS.OpLEq JS.OpLT 1 left right
503 "ge" -> cmp JS.OpGEq JS.OpGT (-1) left right
504 "or" -> JS.Infix JS.OpLOr left right
505 "and" -> JS.Infix JS.OpLAnd left right
506 "xor" -> JS.Infix JS.OpStrictNEq left right
507 _ -> generateGlobalCall home name [left, right]
510 generateGlobalCall home name (map (generateJsExpr mode) args)
513equal :: JS.Expr -> JS.Expr -> JS.Expr
515 if isLiteral left || isLiteral right then
518 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
521notEqual :: JS.Expr -> JS.Expr -> JS.Expr
523 if isLiteral left || isLiteral right then
526 JS.Prefix JS.PrefixLNot $
527 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
530cmp :: JS.InfixOp -> JS.InfixOp -> Int -> JS.Expr -> JS.Expr -> JS.Expr
531cmp idealOp backupOp backupInt left right =
532 if isLiteral left || isLiteral right then
533 JS.Infix idealOp left right
536 (JS.Call (JS.Ref (Name.fromKernel N.utils "cmp")) [left, right])
540isLiteral :: JS.Expr -> Bool
559decomposeL :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
560decomposeL mode expr funcs =
562 Opt.Call (Opt.VarGlobal (Opt.Global home "composeL")) [left, func]
563 | home == ModuleName.basics ->
564 decomposeL mode left (func:funcs)
567 generateJsExpr mode $
568 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
571decomposeR :: Mode -> [Opt.Expr] -> Opt.Expr -> JS.Expr
572decomposeR mode funcs expr =
574 Opt.Call (Opt.VarGlobal (Opt.Global home "composeR")) [func, right]
575 | home == ModuleName.basics ->
576 decomposeR mode (func:funcs) right
579 generateJsExpr mode $
580 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
583apply :: Opt.Expr -> Opt.Expr -> Opt.Expr
586 Opt.Accessor field ->
587 Opt.Access value field
590 Opt.Call f (args ++ [value])
593 Opt.Call func [value]
596append :: Mode -> Opt.Expr -> Opt.Expr -> JS.Expr
597append mode left right =
598 let seqs = generateJsExpr mode left : toSeqs mode right in
599 if any isStringLiteral seqs then
600 foldr1 (JS.Infix JS.OpAdd) seqs
605jsAppend :: JS.Expr -> JS.Expr -> JS.Expr
607 JS.Call (JS.Ref (Name.fromKernel N.utils "ap")) [a, b]
610toSeqs :: Mode -> Opt.Expr -> [JS.Expr]
613 Opt.Call (Opt.VarGlobal (Opt.Global home "append")) [left, right]
614 | home == ModuleName.basics ->
615 generateJsExpr mode left : toSeqs mode right
618 [generateJsExpr mode expr]
621isStringLiteral :: JS.Expr -> Bool
622isStringLiteral expr =
632-- SIMPLIFY INFIX OPERATORS
635strictEq :: JS.Expr -> JS.Expr -> JS.Expr
639 JS.Prefix JS.PrefixLNot right
642 if bool then right else JS.Prefix JS.PrefixLNot right
647 JS.Prefix JS.PrefixLNot left
650 if bool then left else JS.Prefix JS.PrefixLNot left
653 JS.Infix JS.OpStrictEq left right
656strictNEq :: JS.Expr -> JS.Expr -> JS.Expr
657strictNEq left right =
663 if bool then JS.Prefix JS.PrefixLNot right else right
671 if bool then JS.Prefix JS.PrefixLNot left else left
674 JS.Infix JS.OpStrictNEq left right
681-- TODO check if JS minifiers collapse unnecessary temporary variables
683generateTailCall :: Mode -> N.Name -> [(N.Name, Opt.Expr)] -> [JS.Stmt]
684generateTailCall mode name args =
686 toTempVars (argName, arg) =
687 ( Name.makeTemp argName, Just (generateJsExpr mode arg) )
689 toRealVars (argName, _) =
691 JS.Assign (JS.LRef (Name.fromLocal argName)) (JS.Ref (Name.makeTemp argName))
693 JS.Var (map toTempVars args)
694 : map toRealVars args
695 ++ [ JS.Continue (Just (Name.fromLocal name)) ]
702generateDef :: Mode -> Opt.Def -> JS.Stmt
703generateDef mode def =
706 JS.Var [ (Name.fromLocal name, Just (generateJsExpr mode body)) ]
708 Opt.TailDef name argNames body ->
709 JS.Var [ (Name.fromLocal name, Just (codeToExpr (generateTailDef mode name argNames body))) ]
712generateTailDef :: Mode -> N.Name -> [N.Name] -> Opt.Expr -> Code
713generateTailDef mode name argNames body =
714 generateFunction (map Name.fromLocal argNames) $ JsBlock $
715 [ JS.Labelled (Name.fromLocal name) $
716 JS.While (JS.Bool True) $
717 codeToStmt $ generate mode body
725generatePath :: Mode -> Opt.Path -> JS.Expr
726generatePath mode path =
728 Opt.Index index subPath ->
729 JS.Access (generatePath mode subPath) (Name.fromIndex index)
732 JS.Ref (Name.fromLocal name)
734 Opt.Field field subPath ->
735 JS.Access (generatePath mode subPath) (Name.fromField mode field)
740 JS.Access (generatePath mode subPath) (Name.fromIndex Index.first)
743 generatePath mode subPath
750generateIf :: Mode -> [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> Code
751generateIf mode givenBranches givenFinal =
754 crushIfs givenBranches givenFinal
756 convertBranch (condition, expr) =
757 ( generateJsExpr mode condition
761 branchExprs = map convertBranch branches
762 finalCode = generate mode final
764 if isBlock finalCode || any (isBlock . snd) branchExprs then
765 JsBlock [ foldr addStmtIf (codeToStmt finalCode) branchExprs ]
767 JsExpr $ foldr addExprIf (codeToExpr finalCode) branchExprs
770addExprIf :: (JS.Expr, Code) -> JS.Expr -> JS.Expr
771addExprIf (condition, branch) final =
772 JS.If condition (codeToExpr branch) final
775addStmtIf :: (JS.Expr, Code) -> JS.Stmt -> JS.Stmt
776addStmtIf (condition, branch) final =
777 JS.IfStmt condition (codeToStmt branch) final
780isBlock :: Code -> Bool
787crushIfs :: [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
788crushIfs branches final =
789 crushIfsHelp [] branches final
793 :: [(Opt.Expr, Opt.Expr)]
794 -> [(Opt.Expr, Opt.Expr)]
796 -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
797crushIfsHelp visitedBranches unvisitedBranches final =
798 case unvisitedBranches of
801 Opt.If subBranches subFinal ->
802 crushIfsHelp visitedBranches subBranches subFinal
805 (reverse visitedBranches, final)
807 visiting : unvisited ->
808 crushIfsHelp (visiting : visitedBranches) unvisited final
815generateCase :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [(Int, Opt.Expr)] -> [JS.Stmt]
816generateCase mode label root decider jumps =
817 foldr (goto mode label) (generateDecider mode label root decider) jumps
820goto :: Mode -> N.Name -> (Int, Opt.Expr) -> [JS.Stmt] -> [JS.Stmt]
821goto mode label (index, branch) stmts =
825 (Name.makeLabel label index)
826 (JS.While (JS.Bool True) (JS.Block stmts))
828 labeledDeciderStmt : codeToStmtList (generate mode branch)
831generateDecider :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [JS.Stmt]
832generateDecider mode label root decisionTree =
834 Opt.Leaf (Opt.Inline branch) ->
835 codeToStmtList (generate mode branch)
837 Opt.Leaf (Opt.Jump index) ->
838 [ JS.Break (Just (Name.makeLabel label index)) ]
840 Opt.Chain testChain success failure ->
842 (List.foldl1' (JS.Infix JS.OpLAnd) (map (generateIfTest mode root) testChain))
843 (JS.Block $ generateDecider mode label root success)
844 (JS.Block $ generateDecider mode label root failure)
847 Opt.FanOut path edges fallback ->
849 (generateCaseTest mode root path (fst (head edges)))
851 (\edge cases -> generateCaseBranch mode label root edge : cases)
852 [ JS.Default (generateDecider mode label root fallback) ]
858generateIfTest :: Mode -> N.Name -> (DT.Path, DT.Test) -> JS.Expr
859generateIfTest mode root (path, test) =
861 value = pathToJsExpr mode root path
864 DT.IsCtor name index _ _ ->
865 strictEq (JS.Access value Name.dollar) $
867 Name.Debug _ -> JS.String (N.toBuilder name)
868 Name.Prod _ _ -> JS.Int (Index.toMachine index)
871 strictEq value (JS.Int int)
874 strictEq (JS.String (Text.encodeUtf8Builder char)) $
876 Name.Debug _ -> JS.Call (JS.Access value (Name.fromLocal "valueOf")) []
877 Name.Prod _ _ -> value
880 strictEq value (JS.String (Text.encodeUtf8Builder string))
883 JS.Access value (Name.fromLocal "b")
886 JS.Prefix JS.PrefixLNot $
887 JS.Access value (Name.fromLocal "b")
890 error "COMPILER BUG - there should never be tests on a tuple"
894generateCaseBranch :: Mode -> N.Name -> N.Name -> (DT.Test, Opt.Decider Opt.Choice) -> JS.Case
895generateCaseBranch mode label root (test, subTree) =
897 (generateCaseValue mode test)
898 (generateDecider mode label root subTree)
901generateCaseValue :: Mode -> DT.Test -> JS.Expr
902generateCaseValue mode test =
904 DT.IsCtor name index _ _ ->
906 Name.Debug _ -> JS.String (N.toBuilder name)
907 Name.Prod _ _ -> JS.Int (Index.toMachine index)
913 JS.String (Text.encodeUtf8Builder char)
916 JS.String (Text.encodeUtf8Builder string)
919 error "COMPILER BUG - there should never be three tests on a list"
922 error "COMPILER BUG - there should never be three tests on a list"
925 error "COMPILER BUG - there should never be three tests on a tuple"
928generateCaseTest :: Mode -> N.Name -> DT.Path -> DT.Test -> JS.Expr
929generateCaseTest mode root path exampleTest =
931 value = pathToJsExpr mode root path
934 DT.IsCtor _ _ _ opts ->
937 JS.Access value Name.dollar
942 JS.Access value Name.dollar
959 JS.Call (JS.Access value (Name.fromLocal "valueOf")) []
965 error "COMPILER BUG - there should never be three tests on a list"
968 error "COMPILER BUG - there should never be three tests on a list"
971 error "COMPILER BUG - there should never be three tests on a list"
978pathToJsExpr :: Mode -> N.Name -> DT.Path -> JS.Expr
979pathToJsExpr mode root path =
981 DT.Index index subPath ->
982 JS.Access (pathToJsExpr mode root subPath) (Name.fromIndex index)
987 JS.Access (pathToJsExpr mode root subPath) (Name.fromIndex Index.first)
990 pathToJsExpr mode root subPath
993 JS.Ref (Name.fromLocal root)
1000generateMain :: Mode -> I.Interfaces -> ModuleName.Canonical -> Opt.Main -> JS.Expr
1001generateMain mode interfaces home main =
1004 JS.Ref (Name.fromKernel N.browser "staticPage")
1005 # JS.Ref (Name.fromGlobal home "main")
1009 Opt.Dynamic msgType decoder ->
1010 JS.Ref (Name.fromGlobal home "main")
1011 # generateJsExpr mode decoder
1012 # toDebugMetadata mode interfaces msgType
1015(#) :: JS.Expr -> JS.Expr -> JS.Expr
1020toDebugMetadata :: Mode -> I.Interfaces -> Can.Type -> JS.Expr
1021toDebugMetadata mode interfaces msgType =
1027 JS.Json $ Encode.object $
1028 [ ("versions", Encode.object [ ("elm", Pkg.encodeVersion Version.version) ])
1029 , ("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