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 =
59 JS.Call toChar [ JS.String (Text.encodeUtf8Builder char) ]
62 JS.String (Text.encodeUtf8Builder char)
65 JsExpr $ JS.String (Text.encodeUtf8Builder string)
71 JsExpr $ JS.Float float
74 JsExpr $ JS.Ref (Name.fromLocal name)
76 Opt.VarGlobal (Opt.Global home name) ->
77 JsExpr $ JS.Ref (Name.fromGlobal home name)
79 Opt.VarEnum (Opt.Global home name) index ->
82 JsExpr $ JS.Ref (Name.fromGlobal home name)
85 JsExpr $ JS.Int (Index.toMachine index)
87 Opt.VarBox (Opt.Global home name) ->
90 Name.Debug _ -> Name.fromGlobal home name
91 Name.Prod _ _ -> Name.fromGlobal ModuleName.basics N.identity
93 Opt.VarCycle home name ->
94 JsExpr $ JS.Call (JS.Ref (Name.fromCycle home name)) []
96 Opt.VarDebug name home region unhandledValueName ->
97 JsExpr $ generateDebug name home region unhandledValueName
99 Opt.VarKernel home name ->
100 JsExpr $ JS.Ref (Name.fromKernel home name)
105 (JS.Ref (Name.fromKernel N.list "fromArray"))
106 [ JS.Array $ map (generateJsExpr mode) entries
109 Opt.Function args body ->
110 generateFunction (map Name.fromLocal args) (generate mode body)
112 Opt.Call func args ->
113 JsExpr $ generateCall mode func args
115 Opt.TailCall name args ->
116 JsBlock $ generateTailCall mode name args
118 Opt.If branches final ->
119 generateIf mode branches final
123 generateDef mode def : codeToStmtList (generate mode body)
125 Opt.Destruct (Opt.Destructor name path) body ->
127 pathExpr = generatePath mode path
128 pathDef = JS.Var [ (Name.fromLocal name, Just pathExpr) ]
130 JsBlock $ pathDef : codeToStmtList (generate mode body)
132 Opt.Case label root decider jumps ->
133 JsBlock $ generateCase mode label root decider jumps
135 Opt.Accessor field ->
136 JsExpr $ JS.Function Nothing [Name.dollar]
138 JS.Access (JS.Ref Name.dollar) (Name.fromField mode field)
141 Opt.Access record field ->
142 JsExpr $ JS.Access (generateJsExpr mode record) (Name.fromField mode field)
144 Opt.Update record fields ->
146 JS.Call (JS.Ref (Name.fromKernel N.utils "update"))
147 [ generateJsExpr mode record
148 , generateRecord mode fields
152 JsExpr $ generateRecord mode fields
157 JsExpr $ JS.Ref (Name.fromKernel N.utils "Tuple0")
162 Opt.Tuple a b maybeC ->
166 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple2"))
167 [ generateJsExpr mode a
168 , generateJsExpr mode b
172 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple3"))
173 [ generateJsExpr mode a
174 , generateJsExpr mode b
175 , generateJsExpr mode c
179 let string = JS.String (Text.encodeUtf8Builder src) in
180 JsExpr $ JS.Object [ ( Name.fromLocal "src", string ) ]
192codeToExpr :: Code -> JS.Expr
198 JsBlock [ JS.Return (Just expr) ] ->
202 JS.Call (JS.Function Nothing [] stmts) []
205codeToStmtList :: Code -> [JS.Stmt]
209 [ JS.Return (Just expr) ]
215codeToStmt :: Code -> JS.Stmt
219 JS.Return (Just expr)
232{-# NOINLINE toChar #-}
235 JS.Ref (Name.fromKernel N.utils "chr")
242generateCtor :: Mode -> N.Name -> Index.ZeroBased -> Int -> Code
243generateCtor mode name index arity =
246 Index.indexedMap (\i _ -> Name.fromIndex i) [1 .. arity]
250 Name.Debug _ -> JS.String (N.toBuilder name)
251 Name.Prod _ _ -> JS.Int (Index.toMachine index)
253 generateFunction argNames $ JsExpr $ JS.Object $
254 (Name.dollar, ctorTag) : map (\n -> (n, JS.Ref n)) argNames
261generateRecord :: Mode -> Map.Map N.Name Opt.Expr -> JS.Expr
262generateRecord mode fields =
264 toPair (field, value) =
265 (Name.fromField mode field, generateJsExpr mode value)
267 JS.Object (map toPair (Map.toList fields))
274generateDebug :: N.Name -> ModuleName.Canonical -> R.Region -> Maybe N.Name -> JS.Expr
275generateDebug name (ModuleName.Canonical _ home) region unhandledValueName =
276 if name /= "crash" then
277 JS.Ref (Name.fromGlobal ModuleName.debug name)
279 case unhandledValueName of
281 JS.Call (JS.Ref (Name.fromKernel N.utils "crash")) $
282 [ JS.String (N.toBuilder home)
283 , regionToJsExpr region
287 JS.Call (JS.Ref (Name.fromKernel N.utils "crashCase")) $
288 [ JS.String (N.toBuilder home)
289 , regionToJsExpr region
290 , JS.Ref (Name.fromLocal valueName)
294regionToJsExpr :: R.Region -> JS.Expr
295regionToJsExpr (R.Region start end) =
297 [ ( Name.fromLocal "start", positionToJsExpr start )
298 , ( Name.fromLocal "end", positionToJsExpr end )
302positionToJsExpr :: R.Position -> JS.Expr
303positionToJsExpr (R.Position line column) =
305 [ ( Name.fromLocal "line", JS.Int line )
306 , ( Name.fromLocal "column", JS.Int column )
314generateFunction :: [Name.Name] -> Code -> Code
315generateFunction args body =
316 case IntMap.lookup (length args) funcHelpers of
320 [ JS.Function Nothing args $
327 JsExpr $ JS.Function Nothing [arg] $
330 foldr addArg body args
333{-# NOINLINE funcHelpers #-}
334funcHelpers :: IntMap.IntMap JS.Expr
337 map (\n -> (n, JS.Ref (Name.makeF n))) [2..9]
344generateCall :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
345generateCall mode func args =
347 Opt.VarGlobal global@(Opt.Global (ModuleName.Canonical pkg _) _) | pkg == Pkg.core ->
348 generateCoreCall mode global args
353 generateCallHelp mode func args
358 generateJsExpr mode arg
361 generateCallHelp mode func args
364 generateCallHelp mode func args
367generateCallHelp :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
368generateCallHelp mode func args =
370 (generateJsExpr mode func)
371 (map (generateJsExpr mode) args)
374generateGlobalCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
375generateGlobalCall home name args =
376 generateNormalCall (JS.Ref (Name.fromGlobal home name)) args
379generateNormalCall :: JS.Expr -> [JS.Expr] -> JS.Expr
380generateNormalCall func args =
381 case IntMap.lookup (length args) callHelpers of
383 JS.Call helper (func:args)
386 List.foldl' (\f a -> JS.Call f [a]) func args
389{-# NOINLINE callHelpers #-}
390callHelpers :: IntMap.IntMap JS.Expr
393 map (\n -> (n, JS.Ref (Name.makeA n))) [2..9]
400generateCoreCall :: Mode -> Opt.Global -> [Opt.Expr] -> JS.Expr
401generateCoreCall mode (Opt.Global home@(ModuleName.Canonical _ moduleName) name) args =
402 if moduleName == N.basics then
403 generateBasicsCall mode home name args
405 else if moduleName == N.bitwise then
406 generateBitwiseCall home name (map (generateJsExpr mode) args)
408 else if moduleName == N.tuple then
409 generateTupleCall home name (map (generateJsExpr mode) args)
411 else if moduleName == N.jsArray then
412 generateJsArrayCall home name (map (generateJsExpr mode) args)
415 generateGlobalCall home name (map (generateJsExpr mode) args)
418generateTupleCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
419generateTupleCall home name args =
423 "first" -> JS.Access value (Name.fromLocal "a")
424 "second" -> JS.Access value (Name.fromLocal "b")
425 _ -> generateGlobalCall home name args
428 generateGlobalCall home name args
431generateJsArrayCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
432generateJsArrayCall home name args =
434 [entry] | name == "singleton" -> JS.Array [entry]
435 [index, array] | name == "unsafeGet" -> JS.Index array index
436 _ -> generateGlobalCall home name args
439generateBitwiseCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
440generateBitwiseCall home name args =
444 "complement" -> JS.Prefix JS.PrefixBNot arg
445 _ -> generateGlobalCall home name args
449 "and" -> JS.Infix JS.OpBAnd left right
450 "or" -> JS.Infix JS.OpBOr left right
451 "xor" -> JS.Infix JS.OpBXor left right
452 "shiftLeftBy" -> JS.Infix JS.OpLShift right left
453 "shiftRightBy" -> JS.Infix JS.OpSpRShift right left
454 "shiftRightZfBy" -> JS.Infix JS.OpZfRShift right left
455 _ -> generateGlobalCall home name args
458 generateGlobalCall home name args
461generateBasicsCall :: Mode -> ModuleName.Canonical -> N.Name -> [Opt.Expr] -> JS.Expr
462generateBasicsCall mode home name args =
465 let arg = generateJsExpr mode elmArg in
467 "not" -> JS.Prefix JS.PrefixLNot arg
469 "truncate" -> JS.Infix JS.OpBOr arg (JS.Int 0)
470 _ -> generateGlobalCall home name [arg]
472 [elmLeft, elmRight] ->
474 "composeL" -> decomposeL mode elmLeft [elmRight]
475 "composeR" -> decomposeR mode [elmLeft] elmRight
476 "append" -> append mode elmLeft elmRight
477 "apL" -> generateJsExpr mode $ apply elmLeft elmRight
478 "apR" -> generateJsExpr mode $ apply elmRight elmLeft
481 left = generateJsExpr mode elmLeft
482 right = generateJsExpr mode elmRight
485 "add" -> JS.Infix JS.OpAdd left right
486 "sub" -> JS.Infix JS.OpSub left right
487 "mul" -> JS.Infix JS.OpMul left right
488 "fdiv" -> JS.Infix JS.OpDiv left right
489 "idiv" -> JS.Infix JS.OpBOr (JS.Infix JS.OpDiv left right) (JS.Int 0)
490 "eq" -> equal left right
491 "neq" -> notEqual left right
492 "lt" -> cmp JS.OpLT JS.OpLT 0 left right
493 "gt" -> cmp JS.OpGT JS.OpGT 0 left right
494 "le" -> cmp JS.OpLEq JS.OpLT 1 left right
495 "ge" -> cmp JS.OpGEq JS.OpGT (-1) left right
496 "or" -> JS.Infix JS.OpLOr left right
497 "and" -> JS.Infix JS.OpLAnd left right
498 "xor" -> JS.Infix JS.OpStrictNEq left right
499 _ -> generateGlobalCall home name [left, right]
502 generateGlobalCall home name (map (generateJsExpr mode) args)
505equal :: JS.Expr -> JS.Expr -> JS.Expr
507 if isLiteral left || isLiteral right then
510 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
513notEqual :: JS.Expr -> JS.Expr -> JS.Expr
515 if isLiteral left || isLiteral right then
518 JS.Prefix JS.PrefixLNot $
519 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
522cmp :: JS.InfixOp -> JS.InfixOp -> Int -> JS.Expr -> JS.Expr -> JS.Expr
523cmp idealOp backupOp backupInt left right =
524 if isLiteral left || isLiteral right then
525 JS.Infix idealOp left right
528 (JS.Call (JS.Ref (Name.fromKernel N.utils "cmp")) [left, right])
532isLiteral :: JS.Expr -> Bool
551decomposeL :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
552decomposeL mode expr funcs =
554 Opt.Call (Opt.VarGlobal (Opt.Global home "composeL")) [left, func]
555 | home == ModuleName.basics ->
556 decomposeL mode left (func:funcs)
559 generateJsExpr mode $
560 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
563decomposeR :: Mode -> [Opt.Expr] -> Opt.Expr -> JS.Expr
564decomposeR mode funcs expr =
566 Opt.Call (Opt.VarGlobal (Opt.Global home "composeR")) [func, right]
567 | home == ModuleName.basics ->
568 decomposeR mode (func:funcs) right
571 generateJsExpr mode $
572 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
575apply :: Opt.Expr -> Opt.Expr -> Opt.Expr
578 Opt.Accessor field ->
579 Opt.Access value field
582 Opt.Call f (args ++ [value])
585 Opt.Call func [value]
588append :: Mode -> Opt.Expr -> Opt.Expr -> JS.Expr
589append mode left right =
590 let seqs = generateJsExpr mode left : toSeqs mode right in
591 if any isStringLiteral seqs then
592 foldr1 (JS.Infix JS.OpAdd) seqs
597jsAppend :: JS.Expr -> JS.Expr -> JS.Expr
599 JS.Call (JS.Ref (Name.fromKernel N.utils "ap")) [a, b]
602toSeqs :: Mode -> Opt.Expr -> [JS.Expr]
605 Opt.Call (Opt.VarGlobal (Opt.Global home "append")) [left, right]
606 | home == ModuleName.basics ->
607 generateJsExpr mode left : toSeqs mode right
610 [generateJsExpr mode expr]
613isStringLiteral :: JS.Expr -> Bool
614isStringLiteral expr =
624-- SIMPLIFY INFIX OPERATORS
627strictEq :: JS.Expr -> JS.Expr -> JS.Expr
631 JS.Prefix JS.PrefixLNot right
634 if bool then right else JS.Prefix JS.PrefixLNot right
639 JS.Prefix JS.PrefixLNot left
642 if bool then left else JS.Prefix JS.PrefixLNot left
645 JS.Infix JS.OpStrictEq left right
648strictNEq :: JS.Expr -> JS.Expr -> JS.Expr
649strictNEq left right =
655 if bool then JS.Prefix JS.PrefixLNot right else right
663 if bool then JS.Prefix JS.PrefixLNot left else left
666 JS.Infix JS.OpStrictNEq left right
673-- TODO check if JS minifiers collapse unnecessary temporary variables
675generateTailCall :: Mode -> N.Name -> [(N.Name, Opt.Expr)] -> [JS.Stmt]
676generateTailCall mode name args =
678 toTempVars (argName, arg) =
679 ( Name.makeTemp argName, Just (generateJsExpr mode arg) )
681 toRealVars (argName, _) =
683 JS.Assign (JS.LRef (Name.fromLocal argName)) (JS.Ref (Name.makeTemp argName))
685 JS.Var (map toTempVars args)
686 : map toRealVars args
687 ++ [ JS.Continue (Just (Name.fromLocal name)) ]
694generateDef :: Mode -> Opt.Def -> JS.Stmt
695generateDef mode def =
698 JS.Var [ (Name.fromLocal name, Just (generateJsExpr mode body)) ]
700 Opt.TailDef name argNames body ->
701 JS.Var [ (Name.fromLocal name, Just (generateTailDef mode name argNames body)) ]
704generateTailDef :: Mode -> N.Name -> [N.Name] -> Opt.Expr -> JS.Expr
705generateTailDef mode name argNames body =
706 JS.Function Nothing (map Name.fromLocal argNames)
707 [ JS.Labelled (Name.fromLocal name) $
708 JS.While (JS.Bool True) $
709 codeToStmt $ generate mode body
717generatePath :: Mode -> Opt.Path -> JS.Expr
718generatePath mode path =
720 Opt.Index index subPath ->
721 JS.Access (generatePath mode subPath) (Name.fromIndex index)
724 JS.Ref (Name.fromLocal name)
726 Opt.Field field subPath ->
727 JS.Access (generatePath mode subPath) (Name.fromField mode field)
732 JS.Access (generatePath mode subPath) (Name.fromIndex Index.first)
735 generatePath mode subPath
742generateIf :: Mode -> [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> Code
743generateIf mode givenBranches givenFinal =
746 crushIfs givenBranches givenFinal
748 convertBranch (condition, expr) =
749 ( generateJsExpr mode condition
753 branchExprs = map convertBranch branches
754 finalCode = generate mode final
756 if isBlock finalCode || any (isBlock . snd) branchExprs then
757 JsBlock [ foldr addStmtIf (codeToStmt finalCode) branchExprs ]
759 JsExpr $ foldr addExprIf (codeToExpr finalCode) branchExprs
762addExprIf :: (JS.Expr, Code) -> JS.Expr -> JS.Expr
763addExprIf (condition, branch) final =
764 JS.If condition (codeToExpr branch) final
767addStmtIf :: (JS.Expr, Code) -> JS.Stmt -> JS.Stmt
768addStmtIf (condition, branch) final =
769 JS.IfStmt condition (codeToStmt branch) final
772isBlock :: Code -> Bool
779crushIfs :: [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
780crushIfs branches final =
781 crushIfsHelp [] branches final
785 :: [(Opt.Expr, Opt.Expr)]
786 -> [(Opt.Expr, Opt.Expr)]
788 -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
789crushIfsHelp visitedBranches unvisitedBranches final =
790 case unvisitedBranches of
793 Opt.If subBranches subFinal ->
794 crushIfsHelp visitedBranches subBranches subFinal
797 (reverse visitedBranches, final)
799 visiting : unvisited ->
800 crushIfsHelp (visiting : visitedBranches) unvisited final
807generateCase :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [(Int, Opt.Expr)] -> [JS.Stmt]
808generateCase mode label root decider jumps =
809 foldr (goto mode label) (generateDecider mode label root decider) jumps
812goto :: Mode -> N.Name -> (Int, Opt.Expr) -> [JS.Stmt] -> [JS.Stmt]
813goto mode label (index, branch) stmts =
817 (Name.makeLabel label index)
818 (JS.While (JS.Bool True) (JS.Block stmts))
820 labeledDeciderStmt : codeToStmtList (generate mode branch)
823generateDecider :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [JS.Stmt]
824generateDecider mode label root decisionTree =
826 Opt.Leaf (Opt.Inline branch) ->
827 codeToStmtList (generate mode branch)
829 Opt.Leaf (Opt.Jump index) ->
830 [ JS.Break (Just (Name.makeLabel label index)) ]
832 Opt.Chain testChain success failure ->
834 (List.foldl1' (JS.Infix JS.OpLAnd) (map (generateIfTest mode root) testChain))
835 (JS.Block $ generateDecider mode label root success)
836 (JS.Block $ generateDecider mode label root failure)
839 Opt.FanOut path edges fallback ->
841 (generateCaseTest mode root path (fst (head edges)))
843 (\edge cases -> generateCaseBranch mode label root edge : cases)
844 [ JS.Default (generateDecider mode label root fallback) ]
850generateIfTest :: Mode -> N.Name -> (DT.Path, DT.Test) -> JS.Expr
851generateIfTest mode root (path, test) =
853 value = pathToJsExpr mode root path
856 DT.IsCtor name index _ _ ->
857 strictEq (JS.Access value Name.dollar) $
859 Name.Debug _ -> JS.String (N.toBuilder name)
860 Name.Prod _ _ -> JS.Int (Index.toMachine index)
863 strictEq value (JS.Int int)
866 strictEq (JS.String (Text.encodeUtf8Builder char)) $
868 Name.Debug _ -> JS.Call (JS.Access value (Name.fromLocal "valueOf")) []
869 Name.Prod _ _ -> value
872 strictEq value (JS.String (Text.encodeUtf8Builder string))
875 JS.Access value (Name.fromLocal "b")
878 JS.Prefix JS.PrefixLNot $
879 JS.Access value (Name.fromLocal "b")
882 error "COMPILER BUG - there should never be tests on a tuple"
886generateCaseBranch :: Mode -> N.Name -> N.Name -> (DT.Test, Opt.Decider Opt.Choice) -> JS.Case
887generateCaseBranch mode label root (test, subTree) =
889 (generateCaseValue mode test)
890 (generateDecider mode label root subTree)
893generateCaseValue :: Mode -> DT.Test -> JS.Expr
894generateCaseValue mode test =
896 DT.IsCtor name index _ _ ->
898 Name.Debug _ -> JS.String (N.toBuilder name)
899 Name.Prod _ _ -> JS.Int (Index.toMachine index)
905 JS.String (Text.encodeUtf8Builder char)
908 JS.String (Text.encodeUtf8Builder string)
911 error "COMPILER BUG - there should never be three tests on a list"
914 error "COMPILER BUG - there should never be three tests on a list"
917 error "COMPILER BUG - there should never be three tests on a tuple"
920generateCaseTest :: Mode -> N.Name -> DT.Path -> DT.Test -> JS.Expr
921generateCaseTest mode root path exampleTest =
923 value = pathToJsExpr mode root path
926 DT.IsCtor _ _ _ opts ->
929 JS.Access value Name.dollar
934 JS.Access value Name.dollar
951 JS.Call (JS.Access value (Name.fromLocal "valueOf")) []
957 error "COMPILER BUG - there should never be three tests on a list"
960 error "COMPILER BUG - there should never be three tests on a list"
963 error "COMPILER BUG - there should never be three tests on a list"
970pathToJsExpr :: Mode -> N.Name -> DT.Path -> JS.Expr
971pathToJsExpr mode root path =
973 DT.Index index subPath ->
974 JS.Access (pathToJsExpr mode root subPath) (Name.fromIndex index)
979 JS.Access (pathToJsExpr mode root subPath) (Name.fromIndex Index.first)
982 pathToJsExpr mode root subPath
985 JS.Ref (Name.fromLocal root)
992generateMain :: Mode -> I.Interfaces -> ModuleName.Canonical -> Opt.Main -> JS.Expr
993generateMain mode interfaces home main =
996 JS.Ref (Name.fromKernel N.browser "staticPage")
997 # JS.Ref (Name.fromGlobal home "main")
1001 Opt.Dynamic decoder msgType ->
1002 JS.Ref (Name.fromGlobal home "main")
1003 # generateJsExpr mode decoder
1004 # toDebugMetadata mode interfaces msgType
1007(#) :: JS.Expr -> JS.Expr -> JS.Expr
1012toDebugMetadata :: Mode -> I.Interfaces -> Can.Type -> JS.Expr
1013toDebugMetadata mode interfaces msgType =
1019 JS.Json $ Encode.object $
1020 [ ("versions", Encode.object [ ("elm", Pkg.encodeVersion Version.version) ])
1021 , ("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