1{-# LANGUAGE PatternGuards #-}
2module Idris.Elab.Data(elabData) where
14import Idris.Primitives
16import Idris.PartialEval
18import Idris.Output (iputStrLn, pshow, iWarn)
22import Idris.Elab.Utils
23import Idris.Elab.Value
26import Idris.Core.Elaborate hiding (Tactic(..))
27import Idris.Core.Evaluate
28import Idris.Core.Execute
29import Idris.Core.Typecheck
30import Idris.Core.CaseTree
32import Idris.Docstrings
34import Prelude hiding (id, (.))
35import Control.Category
37import Control.Applicative hiding (Const)
40import Control.Monad.State.Strict as State
45import qualified Data.Map as Map
46import qualified Data.Set as S
47import qualified Data.Text as T
48import Data.Char(isLetter, toLower)
49import Data.List.Split (splitOn)
51import Util.Pretty(pretty, text)
53elabData :: ElabInfo -> SyntaxInfo -> Docstring (Either Err PTerm)-> [(Name, Docstring (Either Err PTerm))] -> FC -> DataOpts -> PData -> Idris ()
54elabData info syn doc argDocs fc opts (PLaterdecl n t_in)
55 = do let codata = Codata `elem` opts
58 (cty, _, t, inacc) <- buildType info syn fc [] n t_in
61 updateContext (addTyDecl n (TCon 0 0) cty) -- temporary, to check cons
63elabData info syn doc argDocs fc opts (PDatadecl n t_in dcons)
64 = do let codata = Codata `elem` opts
66 undef <- isUndefined fc n
67 (cty, ckind, t, inacc) <- buildType info syn fc [] n t_in
68 -- if n is defined already, make sure it is just a type declaration
69 -- with the same type we've just elaborated, and no constructors
72 checkDefinedAs fc n cty i
73 -- temporary, to check cons
74 when undef $ updateContext (addTyDecl n (TCon 0 0) cty)
75 let cnameinfo = cinfo info (map cname dcons)
76 let unique = case getRetTy cty of
77 UType UniqueType -> True
79 cons <- mapM (elabCon cnameinfo syn n codata (getRetTy cty) ckind) dcons
82 let as = map (const (Left (Msg ""))) (getArgTys cty)
83 let params = findParams (map snd cons)
84 logLvl 2 $ "Parameters : " ++ show params
85 -- TI contains information about mutually declared types - this will
86 -- be updated when the mutual block is complete
87 putIState (i { idris_datatypes =
88 addDef n (TI (map fst cons) codata opts params [n])
89 (idris_datatypes i) })
92 checkDocs fc argDocs t
93 doc' <- elabDocTerms info doc
94 argDocs' <- mapM (\(n, d) -> do d' <- elabDocTerms info d
95 return (n, d')) argDocs
96 addDocStr n doc' argDocs'
98 let metainf = DataMI params
99 addIBC (IBCMetaInformation n metainf)
100 -- TMP HACK! Make this a data option
101 updateContext (addDatatype (Data n ttag cty unique cons))
102 updateContext (setMetaInformation n metainf)
103 mapM_ totcheck (zip (repeat fc) (map fst cons))
104-- mapM_ (checkPositive n) cons
106 -- if there's exactly one constructor,
107 -- mark both the type and the constructor as detaggable
109 [(cn,ct)] -> setDetaggable cn >> setDetaggable n
110 >> addIBC (IBCOpt cn) >> addIBC (IBCOpt n)
113 -- create an eliminator
114 when (DefaultEliminator `elem` opts) $
115 evalStateT (elabCaseFun True params n t dcons info) Map.empty
116 -- create a case function
117 when (DefaultCaseFun `elem` opts) $
118 evalStateT (elabCaseFun False params n t dcons info) Map.empty
120 setDetaggable :: Name -> Idris ()
123 let opt = idris_optimisation ist
124 case lookupCtxt n opt of
125 [oi] -> putIState ist{ idris_optimisation = addDef n oi{ detaggable = True } opt }
126 _ -> putIState ist{ idris_optimisation = addDef n (Optimise [] True) opt }
128 checkDefinedAs fc n t i
129 = let defined = tclift $ tfail (At fc (AlreadyDefined n))
131 case lookupDef n ctxt of
134 case converts ctxt [] t ty of
135 OK () -> case lookupCtxtExact n (idris_datatypes i) of
140 -- parameters are names which are unchanged across the structure,
141 -- which appear exactly once in the return type of a constructor
143 -- First, find all applications of the constructor, then check over
144 -- them for repeated arguments
146 findParams :: [Type] -> [Int]
147 findParams ts = let allapps = map getDataApp ts
148 -- do each constructor separately, then merge the results (names
149 -- may change between constructors)
150 conParams = map paramPos allapps in
153 inAll :: [[Int]] -> [Int]
155 inAll (x : xs) = filter (\p -> all (\ps -> p `elem` ps) xs) x
158 paramPos (args : rest)
159 = dropNothing $ keepSame (zip [0..] args) rest
162 dropNothing ((x, Nothing) : ts) = dropNothing ts
163 dropNothing ((x, _) : ts) = x : dropNothing ts
165 keepSame :: [(Int, Maybe Name)] -> [[Maybe Name]] ->
168 keepSame as (args : rest) = keepSame (update as args) rest
172 update ((n, Just x) : as) (Just x' : args)
173 | x == x' = (n, Just x) : update as args
174 update ((n, _) : as) (_ : args) = (n, Nothing) : update as args
176 getDataApp :: Type -> [[Maybe Name]]
177 getDataApp f@(App _ _)
178 | (P _ d _, args) <- unApply f
179 = if (d == n) then [mParam args args] else []
180 getDataApp (Bind n (Pi _ t _) sc)
181 = getDataApp t ++ getDataApp (instantiate (P Bound n t) sc)
184 -- keep the arguments which are single names, which don't appear
188 mParam args (P Bound n _ : rest)
190 = Just n : mParam args rest
193 | n `elem` freeNames t = 1 + count n ts
194 | otherwise = count n ts
195 mParam args (_ : rest) = Nothing : mParam args rest
197 cname (_, _, n, _, _, _) = n
199 -- Abuse of ElabInfo.
200 -- TODO Contemplate whether the ElabInfo type needs modification.
201 cinfo :: ElabInfo -> [Name] -> ElabInfo
203 = let newps = params info
204 dsParams = map (\n -> (n, [])) ds
205 newb = addAlist dsParams (inblock info)
207 info { params = newps,
209 liftname = id -- Is this appropriate?
212elabCon :: ElabInfo -> SyntaxInfo -> Name -> Bool ->
213 Type -> -- for unique kind checking
214 Type -> -- data type's kind
215 (Docstring (Either Err PTerm), [(Name, Docstring (Either Err PTerm))], Name, PTerm, FC, [Name]) ->
217elabCon info syn tn codata expkind dkind (doc, argDocs, n, t_in, fc, forcenames)
218 = do checkUndefined fc n
219 logLvl 2 $ show fc ++ ":Constructor " ++ show n ++ " : " ++ show t_in
220 (cty, ckind, t, inacc) <- buildType info syn fc [Constructor] n (if codata then mkLazy t_in else t_in)
222 let cty' = normalise ctxt [] cty
223 checkUniqueKind ckind expkind
224 addDataConstraint ckind dkind
226 -- Check that the constructor type is, in fact, a part of the family being defined
229 logLvl 5 $ show fc ++ ":Constructor " ++ show n ++ " elaborated : " ++ show t
230 logLvl 5 $ "Inaccessible args: " ++ show inacc
231 logLvl 2 $ "---> " ++ show n ++ " : " ++ show cty'
233 -- Add to the context (this is temporary, so that later constructors
234 -- can be indexed by it)
235 updateContext (addTyDecl n (DCon 0 0 False) cty)
238 checkDocs fc argDocs t
239 doc' <- elabDocTerms info doc
240 argDocs' <- mapM (\(n, d) -> do d' <- elabDocTerms info d
241 return (n, d')) argDocs
242 addDocStr n doc' argDocs'
244 fputState (opt_inaccessible . ist_optimisation n) inacc
248 tyIs con (Bind n b sc) = tyIs con sc
249 tyIs con t | (P _ n' _, _) <- unApply t
250 = if n' /= tn then tclift $ tfail (At fc (Elaborating "constructor " con (Msg (show n' ++ " is not " ++ show tn))))
252 tyIs con t = tclift $ tfail (At fc (Elaborating "constructor " con (Msg (show t ++ " is not " ++ show tn))))
254 mkLazy (PPi pl n ty sc)
255 = let ty' = if getTyName ty
256 then PApp fc (PRef fc (sUN "Lazy'"))
257 [pexp (PRef fc (sUN "LazyCodata")),
260 PPi pl n ty' (mkLazy sc)
263 getTyName (PApp _ (PRef _ n) _) = n == nsroot tn
264 getTyName (PRef _ n) = n == nsroot tn
268 getNamePos :: Int -> PTerm -> Name -> Maybe Int
269 getNamePos i (PPi _ n _ sc) x | n == x = Just i
270 | otherwise = getNamePos (i + 1) sc x
271 getNamePos _ _ _ = Nothing
273 -- if the constructor is a UniqueType, the datatype must be too
274 -- (Type* is fine, since that is checked for uniqueness too)
275 checkUniqueKind (UType NullType) (UType NullType) = return ()
276 checkUniqueKind (UType NullType) _
277 = tclift $ tfail (At fc (UniqueKindError NullType n))
278 checkUniqueKind (UType UniqueType) (UType UniqueType) = return ()
279 checkUniqueKind (UType UniqueType) (UType AllTypes) = return ()
280 checkUniqueKind (UType UniqueType) _
281 = tclift $ tfail (At fc (UniqueKindError UniqueType n))
282 checkUniqueKind (UType AllTypes) _ = return ()
283 checkUniqueKind _ _ = return ()
285 -- Constructor's kind must be <= expected kind
286 addDataConstraint (TType con) (TType exp)
287 = do ctxt <- getContext
288 let v = next_tvar ctxt
289 addConstraints fc (v, [ULT con exp])
290 addDataConstraint _ _ = return ()
292type EliminatorState = StateT (Map.Map String Int) Idris
294-- -- Issue #1616 in the issue tracker.
295-- https://github.com/idris-lang/Idris-dev/issues/1616
297-- TODO: Rewrite everything to use idris_implicits instead of manual splitting (or in TT)
298-- FIXME: Many things have name starting with elim internally since this was the only purpose in the first edition of the function
299-- rename to caseFun to match updated intend
300elabCaseFun :: Bool -> [Int] -> Name -> PTerm ->
301 [(Docstring (Either Err PTerm), [(Name, Docstring (Either Err PTerm))], Name, PTerm, FC, [Name])] ->
302 ElabInfo -> EliminatorState ()
303elabCaseFun ind paramPos n ty cons info = do
304 elimLog $ "Elaborating case function"
305 put (Map.fromList $ zip (concatMap (\(_, p, _, ty, _, _) -> (map show $ boundNamesIn ty) ++ map (show . fst) p) cons ++ (map show $ boundNamesIn ty)) (repeat 0))
306 let (cnstrs, _) = splitPi ty
307 let (splittedTy@(pms, idxs)) = splitPms cnstrs
308 generalParams <- namePis False pms
309 motiveIdxs <- namePis False idxs
310 let motive = mkMotive n paramPos generalParams motiveIdxs
311 consTerms <- mapM (\(c@(_, _, cnm, _, _, _)) -> do
312 let casefunt = if ind then "elim_" else "case_"
313 name <- freshName $ casefunt ++ simpleName cnm
314 consTerm <- extractConsTerm c generalParams
315 return (name, expl, consTerm)) cons
316 scrutineeIdxs <- namePis False idxs
317 let motiveConstr = [(motiveName, expl, motive)]
318 let scrutinee = (scrutineeName, expl, applyCons n (interlievePos paramPos generalParams scrutineeIdxs 0))
319 let eliminatorTy = piConstr (generalParams ++ motiveConstr ++ consTerms ++ scrutineeIdxs ++ [scrutinee]) (applyMotive (map (\(n,_,_) -> PRef elimFC n) scrutineeIdxs) (PRef elimFC scrutineeName))
320 let eliminatorTyDecl = PTy (fmap (const (Left $ Msg "")) . parseDocstring . T.pack $ show n) [] defaultSyntax elimFC [TotalFn] elimDeclName eliminatorTy
321 let clauseConsElimArgs = map getPiName consTerms
322 let clauseGeneralArgs' = map getPiName generalParams ++ [motiveName] ++ clauseConsElimArgs
323 let clauseGeneralArgs = map (\arg -> pexp (PRef elimFC arg)) clauseGeneralArgs'
324 let elimSig = "-- case function signature: " ++ showTmImpls eliminatorTy
326 eliminatorClauses <- mapM (\(cns, cnsElim) -> generateEliminatorClauses cns cnsElim clauseGeneralArgs generalParams) (zip cons clauseConsElimArgs)
327 let eliminatorDef = PClauses emptyFC [TotalFn] elimDeclName eliminatorClauses
328 elimLog $ "-- case function definition: " ++ (show . showDeclImp verbosePPOption) eliminatorDef
329 State.lift $ idrisCatch (rec_elabDecl info EAll info eliminatorTyDecl)
330 (ierror . Elaborating "type declaration of " elimDeclName)
331 -- Do not elaborate clauses if there aren't any
332 case eliminatorClauses of
333 [] -> State.lift $ solveDeferred elimDeclName -- Remove meta-variable for type
334 _ -> State.lift $ idrisCatch (rec_elabDecl info EAll info eliminatorDef)
335 (ierror . Elaborating "clauses of " elimDeclName)
336 where elimLog :: String -> EliminatorState ()
337 elimLog s = State.lift (logLvl 2 s)
340 elimFC = fileFC "(casefun)"
343 elimDeclName = if ind then SN . ElimN $ n else SN . CaseN $ n
345 applyNS :: Name -> [String] -> Name
347 applyNS n ns = sNS n ns
349 splitPi :: PTerm -> ([(Name, Plicity, PTerm)], PTerm)
350 splitPi = splitPi' []
351 where splitPi' :: [(Name, Plicity, PTerm)] -> PTerm -> ([(Name, Plicity, PTerm)], PTerm)
352 splitPi' acc (PPi pl n tyl tyr) = splitPi' ((n, pl, tyl):acc) tyr
353 splitPi' acc t = (reverse acc, t)
355 splitPms :: [(Name, Plicity, PTerm)] -> ([(Name, Plicity, PTerm)], [(Name, Plicity, PTerm)])
356 splitPms cnstrs = (map fst pms, map fst idxs)
357 where (pms, idxs) = partition (\c -> snd c `elem` paramPos) (zip cnstrs [0..])
359 isMachineGenerated :: Name -> Bool
360 isMachineGenerated (MN _ _) = True
361 isMachineGenerated _ = False
363 namePis :: Bool -> [(Name, Plicity, PTerm)] -> EliminatorState [(Name, Plicity, PTerm)]
364 namePis keepOld pms = do names <- mapM (mkPiName keepOld) pms
365 let oldNames = map fst names
366 let params = map snd names
367 return $ map (\(n, pl, ty) -> (n, pl, removeParamPis oldNames params ty)) params
369 mkPiName :: Bool -> (Name, Plicity, PTerm) -> EliminatorState (Name, (Name, Plicity, PTerm))
370 mkPiName keepOld (n, pl, piarg) | not (isMachineGenerated n) && keepOld = do return (n, (n, pl, piarg))
371 mkPiName _ (oldName, pl, piarg) = do name <- freshName $ keyOf piarg
372 return (oldName, (name, pl, piarg))
373 where keyOf :: PTerm -> String
374 keyOf (PRef _ name) | isLetter (nameStart name) = (toLower $ nameStart name):"__"
375 keyOf (PApp _ tyf _) = keyOf tyf
378 nameStart :: Name -> Char
379 nameStart n = nameStart' (simpleName n)
380 where nameStart' :: String -> Char
382 nameStart' ns = head ns
384 simpleName :: Name -> String
385 simpleName (NS n _) = simpleName n
386 simpleName (MN i n) = str n ++ show i
387 simpleName n = show n
389 nameSpaces :: Name -> [String]
390 nameSpaces (NS _ ns) = map str ns
393 freshName :: String -> EliminatorState Name
396 let i = fromMaybe 0 (Map.lookup key nameMap)
397 let name = uniqueName (sUN (key ++ show i)) (map (\(nm, nb) -> sUN (nm ++ show nb)) $ Map.toList nameMap)
398 put $ Map.insert key (i+1) nameMap
401 scrutineeName :: Name
402 scrutineeName = sUN "scrutinee"
404 scrutineeArgName :: Name
405 scrutineeArgName = sUN "scrutineeArg"
408 motiveName = sUN "prop"
410 mkMotive :: Name -> [Int] -> [(Name, Plicity, PTerm)] -> [(Name, Plicity, PTerm)] -> PTerm
411 mkMotive n paramPos params indicies =
412 let scrutineeTy = (scrutineeArgName, expl, applyCons n (interlievePos paramPos params indicies 0))
413 in piConstr (indicies ++ [scrutineeTy]) PType
415 piConstr :: [(Name, Plicity, PTerm)] -> PTerm -> PTerm
417 piConstr ((n, pl, tyb):tyr) ty = PPi pl n tyb (piConstr tyr ty)
419 interlievePos :: [Int] -> [a] -> [a] -> Int -> [a]
420 interlievePos idxs [] l2 i = l2
421 interlievePos idxs l1 [] i = l1
422 interlievePos idxs (x:xs) l2 i | i `elem` idxs = x:(interlievePos idxs xs l2 (i+1))
423 interlievePos idxs l1 (y:ys) i = y:(interlievePos idxs l1 ys (i+1))
425 replaceParams :: [Int] -> [(Name, Plicity, PTerm)] -> PTerm -> PTerm
426 replaceParams paramPos params cns =
427 let (_, cnsResTy) = splitPi cns
430 let oldParams = paramNamesOf 0 paramPos args
431 in removeParamPis oldParams params cns
434 removeParamPis :: [Name] -> [(Name, Plicity, PTerm)] -> PTerm -> PTerm
435 removeParamPis oldParams params (PPi pl n tyb tyr) =
436 case findIndex (== n) oldParams of
437 Nothing -> (PPi pl n (removeParamPis oldParams params tyb) (removeParamPis oldParams params tyr))
438 Just i -> (removeParamPis oldParams params tyr)
439 removeParamPis oldParams params (PRef _ n) =
440 case findIndex (== n) oldParams of
441 Nothing -> (PRef elimFC n)
442 Just i -> let (newname,_,_) = params !! i in (PRef elimFC (newname))
443 removeParamPis oldParams params (PApp _ cns args) =
444 PApp elimFC (removeParamPis oldParams params cns) $ replaceParamArgs args
445 where replaceParamArgs :: [PArg] -> [PArg]
446 replaceParamArgs [] = []
447 replaceParamArgs (arg:args) =
448 case extractName (getTm arg) of
449 [] -> arg:replaceParamArgs args
451 case findIndex (== n) oldParams of
452 Nothing -> arg:replaceParamArgs args
453 Just i -> let (newname,_,_) = params !! i in arg {getTm = PRef elimFC newname}:replaceParamArgs args
454 removeParamPis oldParams params t = t
456 paramNamesOf :: Int -> [Int] -> [PArg] -> [Name]
457 paramNamesOf i paramPos [] = []
458 paramNamesOf i paramPos (arg:args) = (if i `elem` paramPos then extractName (getTm arg) else []) ++ paramNamesOf (i+1) paramPos args
460 extractName :: PTerm -> [Name]
461 extractName (PRef _ n) = [n]
464 splitArgPms :: PTerm -> ([PTerm], [PTerm])
465 splitArgPms (PApp _ f args) = splitArgPms' args
466 where splitArgPms' :: [PArg] -> ([PTerm], [PTerm])
467 splitArgPms' cnstrs = (map (getTm . fst) pms, map (getTm . fst) idxs)
468 where (pms, idxs) = partition (\c -> snd c `elem` paramPos) (zip cnstrs [0..])
469 splitArgPms _ = ([],[])
472 implicitIndexes :: (Docstring (Either Err PTerm), Name, PTerm, FC, [Name]) -> EliminatorState [(Name, Plicity, PTerm)]
473 implicitIndexes (cns@(doc, cnm, ty, fc, fs)) = do
474 i <- State.lift getIState
475 implargs' <- case lookupCtxt cnm (idris_implicits i) of
476 [] -> do fail $ "Error while showing implicits for " ++ show cnm
477 [args] -> do return args
478 _ -> do fail $ "Ambigous name for " ++ show cnm
479 let implargs = mapMaybe convertImplPi implargs'
480 let (_, cnsResTy) = splitPi ty
483 let oldParams = paramNamesOf 0 paramPos args
484 in return $ filter (\(n,_,_) -> not (n `elem` oldParams))implargs
487 extractConsTerm :: (Docstring (Either Err PTerm), [(Name, Docstring (Either Err PTerm))], Name, PTerm, FC, [Name]) -> [(Name, Plicity, PTerm)] -> EliminatorState PTerm
488 extractConsTerm (doc, argDocs, cnm, ty, fc, fs) generalParameters = do
489 let cons' = replaceParams paramPos generalParameters ty
490 let (args, resTy) = splitPi cons'
491 implidxs <- implicitIndexes (doc, cnm, ty, fc, fs)
492 consArgs <- namePis False args
493 let recArgs = findRecArgs consArgs
494 let recMotives = if ind then map applyRecMotive recArgs else []
495 let (_, consIdxs) = splitArgPms resTy
496 return $ piConstr (implidxs ++ consArgs ++ recMotives) (applyMotive consIdxs (applyCons cnm consArgs))
497 where applyRecMotive :: (Name, Plicity, PTerm) -> (Name, Plicity, PTerm)
498 applyRecMotive (n,_,ty) = (sUN $ "ih" ++ simpleName n, expl, applyMotive idxs (PRef elimFC n))
499 where (_, idxs) = splitArgPms ty
501 findRecArgs :: [(Name, Plicity, PTerm)] -> [(Name, Plicity, PTerm)]
503 findRecArgs (ty@(_,_,PRef _ tn):rs) | simpleName tn == simpleName n = ty:findRecArgs rs
504 findRecArgs (ty@(_,_,PApp _ (PRef _ tn) _):rs) | simpleName tn == simpleName n = ty:findRecArgs rs
505 findRecArgs (ty:rs) = findRecArgs rs
507 applyCons :: Name -> [(Name, Plicity, PTerm)] -> PTerm
508 applyCons tn targs = PApp elimFC (PRef elimFC tn) (map convertArg targs)
510 convertArg :: (Name, Plicity, PTerm) -> PArg
511 convertArg (n, _, _) = pexp (PRef elimFC n)
513 applyMotive :: [PTerm] -> PTerm -> PTerm
514 applyMotive idxs t = PApp elimFC (PRef elimFC motiveName) (map pexp idxs ++ [pexp t])
516 getPiName :: (Name, Plicity, PTerm) -> Name
517 getPiName (name,_,_) = name
519 convertImplPi :: PArg -> Maybe (Name, Plicity, PTerm)
520 convertImplPi (PImp {getTm = t, pname = n}) = Just (n, expl, t)
521 convertImplPi _ = Nothing
523 generateEliminatorClauses :: (Docstring (Either Err PTerm), [(Name, Docstring (Either Err PTerm))], Name, PTerm, FC, [Name]) -> Name -> [PArg] -> [(Name, Plicity, PTerm)] -> EliminatorState PClause
524 generateEliminatorClauses (doc, _, cnm, ty, fc, fs) cnsElim generalArgs generalParameters = do
525 let cons' = replaceParams paramPos generalParameters ty
526 let (args, resTy) = splitPi cons'
527 i <- State.lift getIState
528 implidxs <- implicitIndexes (doc, cnm, ty, fc, fs)
529 let (_, generalIdxs') = splitArgPms resTy
530 let generalIdxs = map pexp generalIdxs'
531 consArgs <- namePis False args
532 let lhsPattern = PApp elimFC (PRef elimFC elimDeclName) (generalArgs ++ generalIdxs ++ [pexp $ applyCons cnm consArgs])
533 let recArgs = findRecArgs consArgs
534 let recElims = if ind then map applyRecElim recArgs else []
535 let rhsExpr = PApp elimFC (PRef elimFC cnsElim) (map convertArg implidxs ++ map convertArg consArgs ++ recElims)
536 return $ PClause elimFC elimDeclName lhsPattern [] rhsExpr []
537 where applyRecElim :: (Name, Plicity, PTerm) -> PArg
538 applyRecElim (constr@(recCnm,_,recTy)) = pexp $ PApp elimFC (PRef elimFC elimDeclName) (generalArgs ++ map pexp idxs ++ [pexp $ PRef elimFC recCnm])
539 where (_, idxs) = splitArgPms recTy
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