2Module : IRTS.JavaScript.Codegen
3Description : The JavaScript common code generator.
6Maintainer : The Idris Community.
8{-# LANGUAGE OverloadedStrings #-}
10module IRTS.JavaScript.Codegen( codegenJs
16import IRTS.CodegenCommon
17import IRTS.JavaScript.AST
18import IRTS.JavaScript.LangTransforms
19import IRTS.JavaScript.Name
20import IRTS.JavaScript.PrimOp
21import IRTS.JavaScript.Specialize
27import Control.Monad.Trans.State
29import Data.Map.Strict (Map)
30import qualified Data.Map.Strict as Map
31import Data.Maybe (fromJust)
33import qualified Data.Set as Set
34import Data.Text (Text)
35import qualified Data.Text as T
36import qualified Data.Text.IO as TIO
37import System.Directory (doesFileExist)
38import System.Environment
41import Control.Applicative (pure, (<$>))
44import Data.Generics.Uniplate.Data
46import GHC.Generics (Generic)
48data CGStats = CGStats { usedBigInt :: Bool
49 , partialApplications :: Set Partial
50 , hiddenClasses :: Set HiddenClass
54emptyStats = CGStats { partialApplications = Set.empty
55 , hiddenClasses = Set.empty
59data CGConf = CGConf { header :: Text
62 , extraRunTime :: String
66getInclude :: FilePath -> IO Text
69 libs <- getIdrisLibDir
70 let libPath = libs </> p
71 exitsInLib <- doesFileExist libPath
76getIncludes :: [FilePath] -> IO Text
78 incs <- mapM getInclude l
79 return $ T.intercalate "\n\n" incs
81includeLibs :: [String] -> String
89 concatMap (\lib -> "var " ++ (repl <$> lib) ++ " = require(\"" ++ lib ++"\");\n")
91isYes :: Maybe String -> Bool
92isYes (Just "Y") = True
93isYes (Just "y") = True
97codegenJs :: CGConf -> CodeGenerator
100 debug <- isYes <$> lookupEnv "IDRISJS_DEBUG"
101 let defs' = Map.fromList $ liftDecls ci
102 let defs = globlToCon defs'
103 let used = Map.elems $ removeDeadCode defs [sMN 0 "runMain"]
106 writeFile (outputFile ci ++ ".LDeclsDebug") $ (unlines $ intersperse "" $ map show used) ++ "\n\n\n"
107 putStrLn $ "Finished calculating used"
110 let (out, stats) = doCodegen conf defs used
112 path <- getIdrisJSRTSDir
113 jsbn <- if usedBigInt stats
114 then TIO.readFile $ path </> jsbnPath conf
117 runtimeCommon <- TIO.readFile $ path </> "Runtime-common.js"
118 extraRT <- TIO.readFile $ path </> (extraRunTime conf)
120 includes <- getIncludes $ includes ci
121 let libs = T.pack $ includeLibs $ compileLibs ci
122 TIO.writeFile (outputFile ci) $ T.concat [ header conf
123 , "\"use strict\";\n\n"
126 , runtimeCommon, "\n"
129 -- external libraries
133 , doPartials (partialApplications stats), "\n"
134 , doHiddenClasses (hiddenClasses stats), "\n"
136 , jsName (sMN 0 "runMain"), "();\n"
141doPartials :: Set Partial -> Text
143 T.intercalate "\n" (map f $ Set.toList x)
145 f p@(Partial n i j) =
146 let vars1 = map (T.pack . ("x"++) . show) [1..i]
147 vars2 = map (T.pack . ("x"++) . show) [(i+1)..j]
149 JsFun (jsNamePartial p) vars1 $ JsReturn $
150 jsCurryLam vars2 (jsAppN (jsName n) (map JsVar (vars1 ++ vars2)) )
152doHiddenClasses :: Set HiddenClass -> Text
154 T.intercalate "\n" (map f $ Set.toList x)
156 f p@(HiddenClass n id 0) = jsStmt2Text $ JsDecConst (jsNameHiddenClass p) $ JsObj [("type", JsInt id)]
157 f p@(HiddenClass n id arity) =
158 let vars = map dataPartName $ take arity [1..]
160 JsFun (jsNameHiddenClass p) vars $ JsSeq (JsSet (JsProp JsThis "type") (JsInt id)) $ seqJs
161 $ map (\tv -> JsSet (JsProp JsThis tv) (JsVar tv)) vars
163doCodegen :: CGConf -> Map Name LDecl -> [LDecl] -> (Text, CGStats)
164doCodegen conf defs decls =
165 let xs = map (doCodegenDecl conf defs) decls
166 groupCGStats x y = CGStats { partialApplications = partialApplications x `Set.union` partialApplications y
167 , hiddenClasses = hiddenClasses x `Set.union` hiddenClasses y
168 , usedBigInt = usedBigInt x || usedBigInt y
170 in (T.intercalate "\n" $ map fst xs, foldl' groupCGStats emptyStats (map snd xs) )
172doCodegenDecl :: CGConf -> Map Name LDecl -> LDecl -> (Text, CGStats)
173doCodegenDecl conf defs (LFun _ n args def) =
174 let (ast, stats) = cgFun conf defs n args def
175 in (T.concat [jsStmt2Text (JsComment $ T.pack $ show n), "\n", jsStmt2Text ast], stats)
176doCodegenDecl conf defs (LConstructor n i sz) = ("", emptyStats)
178seqJs :: [JsStmt] -> JsStmt
180seqJs (x:xs) = JsSeq x (seqJs xs)
183data CGBodyState = CGBodyState { defs :: Map Name LDecl
185 , reWrittenNames :: Map.Map Name JsExpr
186 , currentFnNameAndArgs :: (Text, [Text])
187 , usedArgsTailCallOptim :: Set (Text, Text)
191 , partialApps :: Set Partial
192 , hiddenCls :: Set HiddenClass
195getNewCGName :: State CGBodyState Text
199 let v = lastIntName st + 1
200 put $ st {lastIntName = v}
201 return $ jsNameGenerated v
203addPartial :: Partial -> State CGBodyState ()
205 modify (\s -> s {partialApps = Set.insert p (partialApps s) })
207addHiddenClass :: HiddenClass -> State CGBodyState ()
209 modify (\s -> s {hiddenCls = Set.insert p (hiddenCls s) })
211addUsedArgsTailCallOptim :: Set (Text, Text) -> State CGBodyState ()
212addUsedArgsTailCallOptim p =
213 modify (\s -> s {usedArgsTailCallOptim = Set.union p (usedArgsTailCallOptim s) })
215getNewCGNames :: Int -> State CGBodyState [Text]
217 mapM (\_ -> getNewCGName) [1..n]
219getConsId :: Name -> State CGBodyState (Int, Int)
223 case Map.lookup n (defs st) of
224 Just (LConstructor _ conId arity) -> pure (conId, arity)
225 _ -> error $ "Internal JS Backend error " ++ showCG n ++ " is not a constructor."
227getArgList :: Name -> State CGBodyState (Maybe [Name])
231 case Map.lookup n (defs st) of
232 Just (LFun _ _ a _) -> pure $ Just a
235data BodyResTarget = ReturnBT
241cgFun :: CGConf -> Map Name LDecl -> Name -> [Name] -> LExp -> (JsStmt, CGStats)
242cgFun cnf dfs n args def = do
243 let fnName = jsName n
244 let argNames = map jsName args
245 let ((decs, res),st) = runState
246 (cgBody ReturnBT def)
247 (CGBodyState { defs = dfs
249 , reWrittenNames = Map.empty
250 , currentFnNameAndArgs = (fnName, argNames)
251 , usedArgsTailCallOptim = Set.empty
255 , partialApps = Set.empty
256 , hiddenCls = Set.empty
259 let body = if isTailRec st then JsSeq (declareUsedOptimArgs $ usedArgsTailCallOptim st) (JsForever ((seqJs decs) `JsSeq` res)) else (seqJs decs) `JsSeq` res
260 let fn = JsFun fnName argNames body
261 let state' = CGStats { partialApplications = partialApps st
262 , hiddenClasses = hiddenCls st
263 , usedBigInt = usedITBig st
267getSwitchJs :: JsExpr -> [LAlt] -> JsExpr
269 if any conCase alts then JsArrayProj (JsInt 0) x
270 else if any constBigIntCase alts then JsForeign "%0.toString()" [x]
272 where conCase (LConCase _ _ _ _) = True
274 constBigIntCase (LConstCase (BI _) _) = True
275 constBigIntCase _ = False
277addRT :: BodyResTarget -> JsExpr -> JsStmt
278addRT ReturnBT x = JsReturn x
279addRT (DecBT n) x = JsDecLet n x
280addRT (DecConstBT n) x = JsDecConst n x
281addRT (SetBT n) x = JsSet (JsVar n) x
282addRT GetExpBT x = JsExprStmt x
284declareUsedOptimArgs :: Set (Text, Text) -> JsStmt
285declareUsedOptimArgs x = seqJs $ map (\(x,y) -> JsDecLet x (JsVar y) ) (Set.toList x)
287tailCallOptimRefreshArgs :: [(Text, JsExpr)] -> Set Text -> ((JsStmt, JsStmt), Set (Text, Text))
288tailCallOptimRefreshArgs [] s = ((JsEmpty, JsEmpty), Set.empty)
289tailCallOptimRefreshArgs ((n,x):r) s =
290 let ((y1,y2), y3) = tailCallOptimRefreshArgs r (Set.insert n s) --
291 in if Set.null $ (Set.fromList [ z | z <- universeBi x ]) `Set.intersection` s then
292 ((y1, jsSetVar n x `JsSeq` y2), y3)
294 let n' = jsTailCallOptimName n
295 in ((jsSetVar n' x `JsSeq` y1, jsSetVar n (JsVar n') `JsSeq` y2), Set.insert (n',n) y3)
297cgName :: Name -> State CGBodyState JsExpr
300 case Map.lookup b (reWrittenNames st) of
302 _ -> pure $ JsVar $ jsName b
304cgBody :: BodyResTarget -> LExp -> State CGBodyState ([JsStmt], JsStmt)
307 (LCase _ (LOp oper [x, y]) [LConstCase (I 0) (LCon _ _ ff []), LDefaultCase (LCon _ _ tt [])])
308 | (ff == qualifyN "Prelude.Bool" "False" &&
309 tt == qualifyN "Prelude.Bool" "True") ->
310 case (Map.lookup oper primDB) of
311 Just (needBI, pti, c) | pti == PTBool -> do
312 z <- mapM (cgBody GetExpBT) [x, y]
313 when needBI setUsedITBig
314 let res = jsPrimCoerce pti PTBool $ c $ map (jsStmt2Expr . snd) z
315 pure $ (concat $ map fst z, addRT rt res)
317 (LCase _ e [LConCase _ n _ (LCon _ _ tt []), LDefaultCase (LCon _ _ ff [])])
318 | (ff == qualifyN "Prelude.Bool" "False" &&
319 tt == qualifyN "Prelude.Bool" "True") -> do
320 (d, v) <- cgBody GetExpBT e
321 test <- formConTest n (jsStmt2Expr v)
322 pure $ (d, addRT rt $ JsUniOp (T.pack "!") $ JsUniOp (T.pack "!") test)
323 (LCase _ e [LConCase _ n _ (LCon _ _ tt []), LConCase _ _ _ (LCon _ _ ff [])])
324 | (ff == qualifyN "Prelude.Bool" "False" &&
325 tt == qualifyN "Prelude.Bool" "True") -> do
326 (d, v) <- cgBody GetExpBT e
327 test <- formConTest n (jsStmt2Expr v)
328 pure $ (d, addRT rt $ JsUniOp (T.pack "!") $ JsUniOp (T.pack "!") test)
329 (LCase _ e [LConCase _ n _ (LCon _ _ ff []), LDefaultCase (LCon _ _ tt [])])
330 | (ff == qualifyN "Prelude.Bool" "False" &&
331 tt == qualifyN "Prelude.Bool" "True") -> do
332 (d, v) <- cgBody GetExpBT e
333 test <- formConTest n (jsStmt2Expr v)
334 pure $ (d, addRT rt $ JsUniOp (T.pack "!") test)
335 (LCase _ e [LConCase _ n _ (LCon _ _ ff []), LConCase _ _ _ (LCon _ _ tt [])])
336 | (ff == qualifyN "Prelude.Bool" "False" &&
337 tt == qualifyN "Prelude.Bool" "True") -> do
338 (d, v) <- cgBody GetExpBT e
339 test <- formConTest n (jsStmt2Expr v)
340 pure $ (d, addRT rt $ JsUniOp (T.pack "!") test)
341 (LCase f e [LConCase nf ff [] alt, LConCase nt tt [] conseq])
342 | (ff == qualifyN "Prelude.Bool" "False" &&
343 tt == qualifyN "Prelude.Bool" "True") ->
344 cgBody' rt $ LCase f e [LConCase nt tt [] conseq, LConCase nf ff [] alt]
345 expr -> cgBody' rt expr
347cgBody' :: BodyResTarget -> LExp -> State CGBodyState ([JsStmt], JsStmt)
348cgBody' rt (LV (Glob n)) =
350 argsFn <- getArgList n
352 Just a -> cgBody' rt (LApp False (LV (Glob n)) [])
355 pure $ ([], addRT rt n')
356cgBody' rt (LApp tailcall (LV (Glob fn)) args) =
358 let fname = jsName fn
360 let (currFn, argN) = currentFnNameAndArgs st
361 z <- mapM (cgBody GetExpBT) args
362 let argVals = map (jsStmt2Expr . snd) z
363 let preDecs = concat $ map fst z
364 case (fname == currFn && (length args) == (length argN), rt) of
367 modify (\x-> x {isTailRec = True})
368 let ((y1,y2), y3) = tailCallOptimRefreshArgs (zip argN argVals) Set.empty
369 addUsedArgsTailCallOptim y3
370 pure (preDecs, y1 `JsSeq` y2)
372 app <- formApp fn argVals
373 pure (preDecs, addRT rt app)
375cgBody' rt (LForce (LLazyApp n args)) = cgBody rt (LApp False (LV (Glob n)) args)
376cgBody' rt (LLazyApp n args) =
378 (d,v) <- cgBody ReturnBT (LApp False (LV (Glob n)) args)
379 pure ([], addRT rt $ jsLazy $ jsStmt2Expr $ JsSeq (seqJs d) v)
380cgBody' rt (LForce e) =
382 (d,v) <- cgBody GetExpBT e
383 pure (d, addRT rt $ JsForce $ jsStmt2Expr v)
384cgBody' rt (LLet n v sc) =
386 (d1, v1) <- cgBody (DecConstBT $ jsName n) v
387 (d2, v2) <- cgBody rt sc
388 pure $ ((d1 ++ v1 : d2), v2)
389cgBody' rt (LProj e i) =
391 (d, v) <- cgBody GetExpBT e
392 pure $ (d, addRT rt $ JsArrayProj (JsInt $ i+1) $ jsStmt2Expr v)
393cgBody' rt (LCon _ conId n args) =
395 z <- mapM (cgBody GetExpBT) args
396 con <- formCon n (map (jsStmt2Expr . snd) z)
397 pure $ (concat $ map fst z, addRT rt con)
398cgBody' rt (LCase _ e alts) = do
399 (d, v) <- cgBody GetExpBT e
400 resName <- getNewCGName
402 case (all altHasNoProj alts && length alts <= 2, v) of
403 (True, _) -> pure (JsEmpty, jsStmt2Expr v)
404 (False, JsExprStmt (JsVar n)) -> pure (JsEmpty, jsStmt2Expr v)
406 swName <- getNewCGName
407 pure (JsDecConst swName $ jsStmt2Expr v, JsVar swName)
408 sw' <- cgIfTree rt resName entry alts
412 Nothing -> JsExprStmt JsNull
414 ReturnBT -> pure (d ++ [decSw], sw)
415 (DecBT nvar) -> pure (d ++ [decSw, JsDecLet nvar JsNull], sw)
416 (DecConstBT nvar) -> pure (d ++ [decSw, JsDecLet nvar JsNull], sw)
417 (SetBT nvar) -> pure (d ++ [decSw], sw)
420 (d ++ [decSw, JsDecLet resName JsNull, sw], JsExprStmt $ JsVar resName)
421cgBody' rt (LConst c) =
424 pure ([], (addRT rt) $ cst)
425cgBody' rt (LOp op args) =
427 z <- mapM (cgBody GetExpBT) args
428 res <- cgOp op (map (jsStmt2Expr . snd) z)
429 pure $ (concat $ map fst z, addRT rt $ res)
430cgBody' rt LNothing = pure ([], addRT rt JsNull)
431cgBody' rt (LError x) = pure ([], JsError $ JsStr x)
432cgBody' rt x@(LForeign dres (FStr code) args ) =
434 z <- mapM (cgBody GetExpBT) (map snd args)
435 jsArgs <- sequence $ map cgForeignArg (zip (map fst args) (map (jsStmt2Expr . snd) z))
436 jsDres <- cgForeignRes dres $ JsForeign (T.pack code) jsArgs
437 pure $ (concat $ map fst z, addRT rt $ jsDres)
438cgBody' _ x = error $ "Instruction " ++ show x ++ " not compilable yet"
440altsRT :: Text -> BodyResTarget -> BodyResTarget
441altsRT rn ReturnBT = ReturnBT
442altsRT rn (DecBT n) = SetBT n
443altsRT rn (SetBT n) = SetBT n
444altsRT rn (DecConstBT n) = SetBT n
445altsRT rn GetExpBT = SetBT rn
447altHasNoProj :: LAlt -> Bool
448altHasNoProj (LConCase _ _ args _) = args == []
451formApp :: Name -> [JsExpr] -> State CGBodyState JsExpr
452formApp fn argVals = case specialCall fn of
453 Just (arity, g) | arity == length argVals -> pure $ g argVals
455 argsFn <- getArgList fn
458 Nothing -> pure $ jsCurryApp fname argVals
460 let lenAgFn = length agFn
461 let lenArgs = length argVals
462 case compare lenAgFn lenArgs of
463 EQ -> pure $ JsApp fname argVals
464 LT -> pure $ jsCurryApp (JsApp fname (take lenAgFn argVals)) (drop lenAgFn argVals)
466 let part = Partial fn lenArgs lenAgFn
468 pure $ jsAppN (jsNamePartial part) argVals
470formCon :: Name -> [JsExpr] -> State CGBodyState JsExpr
472 case specialCased n of
473 Just (ctor, test, match) -> pure $ ctor args
475 (conId, arity) <- getConsId n
476 let hc = HiddenClass n conId arity
478 pure $ if (arity > 0)
479 then JsNew (JsVar $ jsNameHiddenClass hc) args
480 else JsVar $ jsNameHiddenClass hc
482formConTest :: Name -> JsExpr -> State CGBodyState JsExpr
484 case specialCased n of
485 Just (ctor, test, match) -> pure $ test x
487 (conId, arity) <- getConsId n
488 pure $ JsBinOp "===" (JsProp x (T.pack "type")) (JsInt conId)
490 -- then pure $ JsBinOp "===" (JsProp x (T.pack "type")) (JsInt conId)
491 -- else pure $ JsBinOp "===" x (JsInt conId)
493formProj :: Name -> JsExpr -> Int -> JsExpr
495 case specialCased n of
496 Just (ctor, test, proj) -> proj v i
497 Nothing -> JsProp v (dataPartName i)
499smartif :: JsExpr -> JsStmt -> Maybe JsStmt -> JsStmt
500smartif cond conseq (Just alt) = JsIf cond conseq (Just alt)
501smartif cond conseq Nothing = conseq
503formConstTest :: JsExpr -> Const -> State CGBodyState JsExpr
504formConstTest scrvar t = case t of
507 cgOp' PTBool (LEq (ATInt ITBig)) [scrvar, t']
510 pure $ JsBinOp "===" scrvar t'
512cgIfTree :: BodyResTarget
516 -> State CGBodyState (Maybe JsStmt)
517cgIfTree _ _ _ [] = pure Nothing
518cgIfTree rt resName scrvar ((LConstCase t exp):r) = do
519 (d, v) <- cgBody (altsRT resName rt) exp
520 alternatives <- cgIfTree rt resName scrvar r
521 test <- formConstTest scrvar t
523 smartif test (JsSeq (seqJs d) v) alternatives
524cgIfTree rt resName scrvar ((LDefaultCase exp):r) = do
525 (d, v) <- cgBody (altsRT resName rt) exp
526 pure $ Just $ JsSeq (seqJs d) v
527cgIfTree rt resName scrvar ((LConCase _ n args exp):r) = do
528 alternatives <- cgIfTree rt resName scrvar r
529 test <- formConTest n scrvar
531 let rwn = reWrittenNames st
536 (\m (n, j) -> Map.insert n (formProj n scrvar j) m)
540 (d, v) <- cgBody (altsRT resName rt) exp
542 put $ st1 {reWrittenNames = rwn}
543 let branchBody = JsSeq (seqJs d) v
544 pure $ Just $ smartif test branchBody alternatives
547cgForeignArg :: (FDesc, JsExpr) -> State CGBodyState JsExpr
548cgForeignArg (FApp (UN "JS_IntT") _, v) = pure v
549cgForeignArg (FCon (UN "JS_Str"), v) = pure v
550cgForeignArg (FCon (UN "JS_Ptr"), v) = pure v
551cgForeignArg (FCon (UN "JS_Unit"), v) = pure v
552cgForeignArg (FCon (UN "JS_Float"), v) = pure v
553cgForeignArg (FApp (UN "JS_FnT") [_,FApp (UN "JS_Fn") [_,_, a, FApp (UN "JS_FnBase") [_,b]]], f) =
555cgForeignArg (FApp (UN "JS_FnT") [_,FApp (UN "JS_Fn") [_,_, a, FApp (UN "JS_FnIO") [_,_, b]]], f) =
557 jsx <- cgForeignArg (a, JsVar "x")
558 jsres <- cgForeignRes b $ jsCurryApp (jsCurryApp f [jsx]) [JsNull]
559 pure $ JsLambda ["x"] $ JsReturn jsres
560cgForeignArg (desc, _) =
563 error $ "Foreign arg type " ++ show desc ++ " not supported. While generating function " ++ (show $ fst $ currentFnNameAndArgs st)
565cgForeignRes :: FDesc -> JsExpr -> State CGBodyState JsExpr
566cgForeignRes (FApp (UN "JS_IntT") _) x = pure x
567cgForeignRes (FCon (UN "JS_Unit")) x = pure x
568cgForeignRes (FCon (UN "JS_Str")) x = pure x
569cgForeignRes (FCon (UN "JS_Ptr")) x = pure x
570cgForeignRes (FCon (UN "JS_Float")) x = pure x
571cgForeignRes desc val =
574 error $ "Foreign return type " ++ show desc ++ " not supported. While generating function " ++ (show $ fst $ currentFnNameAndArgs st)
576setUsedITBig :: State CGBodyState ()
577setUsedITBig = modify (\s -> s {usedITBig = True})
580cgConst :: Const -> State CGBodyState JsExpr
581cgConst (I i) = pure $ JsInt i
585 pure $ JsForeign "new $JSRTS.jsbn.BigInteger(%0)" [JsStr $ show i]
586cgConst (Ch c) = pure $ JsStr [c]
587cgConst (Str s) = pure $ JsStr s
588cgConst (Fl f) = pure $ JsDouble f
589cgConst (B8 x) = pure $ JsForeign (T.pack $ show x ++ " & 0xFF") []
590cgConst (B16 x) = pure $ JsForeign (T.pack $ show x ++ " & 0xFFFF") []
591cgConst (B32 x) = pure $ JsForeign (T.pack $ show x ++ "|0" ) []
595 pure $ JsForeign "new $JSRTS.jsbn.BigInteger(%0).and(new $JSRTS.jsbn.BigInteger(%1))" [JsStr $ show x, JsStr $ show 0xFFFFFFFFFFFFFFFF]
596cgConst x | isTypeConst x = pure $ JsInt 0
597cgConst x = error $ "Constant " ++ show x ++ " not compilable yet"
599cgOp :: PrimFn -> [JsExpr] -> State CGBodyState JsExpr
602cgOp' :: JsPrimTy -> PrimFn -> [JsExpr] -> State CGBodyState JsExpr
603cgOp' pt (LExternal name) _ | name == sUN "prim__null" = pure JsNull
604cgOp' pt (LExternal name) [l,r] | name == sUN "prim__eqPtr" = pure $ JsBinOp "==" l r
605cgOp' pt op exps = case Map.lookup op primDB of
606 Just (useBigInt, pti, combinator) -> do
607 when useBigInt setUsedITBig
608 pure $ jsPrimCoerce pti pt $ combinator exps
609 Nothing -> error ("Operator " ++ show (op, exps) ++ " not implemented")
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