1{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE RankNTypes #-}
4module Main (main) where
6import Control.Concurrent (threadDelay)
7import Data.Foldable (forM_)
12import qualified Streamly.Prelude as A
14toListSerial :: StreamT IO a -> IO [a]
15toListSerial = A.toList . serially
17toListInterleaved :: InterleavedT IO a -> IO [a]
18toListInterleaved = A.toList . interleaving
20toListAsync :: AsyncT IO a -> IO [a]
21toListAsync = A.toList . asyncly
23toListParallel :: Ord a => ParallelT IO a -> IO [a]
24toListParallel = fmap sort . A.toList . parallely
28 describe "Runners" $ do
29 it "simple serially" $
30 (runStreaming . serially) (return (0 :: Int)) `shouldReturn` ()
31 it "simple serially with IO" $
32 (runStreaming . serially) (liftIO $ putStrLn "hello") `shouldReturn` ()
33 it "Captures a return value using toList" $
34 toListSerial (return 0) `shouldReturn` ([0] :: [Int])
37 it "Monoid - mempty" $
38 (toListSerial mempty) `shouldReturn` ([] :: [Int])
39 it "Alternative - empty" $
40 (toListSerial empty) `shouldReturn` ([] :: [Int])
41 it "MonadPlus - mzero" $
42 (toListSerial mzero) `shouldReturn` ([] :: [Int])
44 ---------------------------------------------------------------------------
46 ---------------------------------------------------------------------------
48 describe "Functor (fmap)" $ do
50 (toListSerial $ fmap (+1) (return 1)) `shouldReturn` ([2] :: [Int])
51 it "fmap on composed (<>)" $
52 (toListSerial $ fmap (+1) (return 1 <> return 2))
53 `shouldReturn` ([2,3] :: [Int])
54 it "fmap on composed (<|>)" $
55 (toListSerial $ fmap (+1) (return 1 <|> return 2))
56 `shouldReturn` ([2,3] :: [Int])
58 ---------------------------------------------------------------------------
60 ---------------------------------------------------------------------------
62 describe "Applicative" $ do
64 (toListSerial $ (,) <$> (return 1) <*> (return 2))
65 `shouldReturn` ([(1,2)] :: [(Int, Int)])
67 it "Apply - serial composed first argument" $
68 (toListSerial $ (,) <$> (return 1 <> return 2) <*> (return 3))
69 `shouldReturn` ([(1,3),(2,3)] :: [(Int, Int)])
71 it "Apply - serial composed second argument" $
72 (toListSerial $ (,) <$> (return 1) <*> (return 2 <> return 3))
73 `shouldReturn` ([(1,2),(1,3)] :: [(Int, Int)])
75 it "Apply - parallel composed first argument" $
76 (toListSerial $ (,) <$> (return 1 <|> return 2) <*> (return 3))
77 `shouldReturn` ([(1,3),(2,3)] :: [(Int, Int)])
79 it "Apply - parallel composed second argument" $
80 (toListSerial $ (,) <$> (return 1) <*> (return 2 <|> return 3))
81 `shouldReturn` ([(1,2),(1,3)] :: [(Int, Int)])
83 ---------------------------------------------------------------------------
85 ---------------------------------------------------------------------------
87 describe "Bind then" thenBind
88 describe "Pure bind serial" $ pureBind toListSerial
89 describe "Pure bind serial interleaved" $ pureBind toListInterleaved
90 describe "Pure bind parallel DFS" $ pureBind toListAsync
91 describe "Pure bind parallel BFS" $ pureBind toListParallel
93 describe "Bind (>>=) with empty" $ bindEmpty toListSerial
94 describe "Bind (>->) with empty" $ bindEmpty toListInterleaved
95 describe "Bind (>|>) with empty" $ bindEmpty toListAsync
96 describe "Bind (>>|) with empty" $ bindEmpty toListParallel
98 ---------------------------------------------------------------------------
99 -- Monoidal Compositions
100 ---------------------------------------------------------------------------
102 describe "Serial Composition (<>)" $ compose (<>) id
103 describe "Serial Composition (mappend)" $ compose mappend id
104 describe "Interleaved Composition (<>)" $ compose (<=>) sort
105 describe "Left biased parallel Composition (<|)" $ compose (<|) sort
106 describe "Fair parallel Composition (<|>)" $ compose (<|>) sort
107 describe "Fair parallel Composition (mplus)" $ compose mplus sort
109 ---------------------------------------------------------------------------
110 -- Monoidal Composition ordering checks
111 ---------------------------------------------------------------------------
113 describe "Serial interleaved ordering check (<=>)" $ interleaveCheck (<=>)
114 describe "Parallel interleaved ordering check (<|>)" $ interleaveCheck (<|>)
115 describe "Left biased parallel time order check" $ parallelCheck (<|)
116 describe "Fair parallel time order check" $ parallelCheck (<|>)
118 ---------------------------------------------------------------------------
119 -- TBD Monoidal composition combinations
120 ---------------------------------------------------------------------------
122 -- TBD need more such combinations to be tested.
123 describe "<> and <>" $ composeAndComposeSimple (<>) (<>) (cycle [[1 .. 9]])
125 describe "<> and <=>" $ composeAndComposeSimple
130 , [1, 3, 2, 4, 6, 5, 7, 9, 8]
131 , [1, 3, 2, 4, 6, 5, 7, 9, 8]
134 describe "<=> and <=>" $ composeAndComposeSimple
137 ([ [1, 4, 2, 7, 3, 5, 8, 6, 9]
138 , [1, 7, 4, 8, 2, 9, 5, 3, 6]
139 , [1, 4, 3, 7, 2, 6, 9, 5, 8]
140 , [1, 7, 4, 9, 3, 8, 6, 2, 5]
143 describe "<=> and <>" $ composeAndComposeSimple
146 ([ [1, 4, 2, 7, 3, 5, 8, 6, 9]
147 , [1, 7, 4, 8, 2, 9, 5, 3, 6]
148 , [1, 4, 2, 7, 3, 5, 8, 6, 9]
149 , [1, 7, 4, 8, 2, 9, 5, 3, 6]
152 describe "Nested parallel and serial compositions" $ do
154 -- This is not correct, the result can also be [4,4,8,0,8,0,2,2]
155 -- because of parallelism of [8,0] and [8,0].
156 it "Nest <|>, <>, <|> (1)" $
159 ((t 8 <|> t 4) <> (t 2 <|> t 0))
160 <|> ((t 8 <|> t 4) <> (t 2 <|> t 0)))
161 `shouldReturn` ([4,4,8,8,0,0,2,2])
163 it "Nest <|>, <>, <|> (2)" $
166 ((t 4 <|> t 8) <> (t 1 <|> t 2))
167 <|> ((t 4 <|> t 8) <> (t 1 <|> t 2)))
168 `shouldReturn` ([4,4,8,8,1,1,2,2])
169 -- FIXME: These two keep failing intermittently on Mac OS X
170 -- Need to examine and fix the tests.
172 it "Nest <|>, <=>, <|> (1)" $
175 ((t 8 <|> t 4) <=> (t 2 <|> t 0))
176 <|> ((t 9 <|> t 4) <=> (t 2 <|> t 0)))
177 `shouldReturn` ([4,4,0,0,8,2,9,2])
178 it "Nest <|>, <=>, <|> (2)" $
181 ((t 4 <|> t 8) <=> (t 1 <|> t 2))
182 <|> ((t 4 <|> t 9) <=> (t 1 <|> t 2)))
183 `shouldReturn` ([4,4,1,1,8,2,9,2])
185 it "Nest <|>, <|>, <|>" $
188 ((t 4 <|> t 8) <|> (t 0 <|> t 2))
189 <|> ((t 4 <|> t 8) <|> (t 0 <|> t 2)))
190 `shouldReturn` ([0,0,2,2,4,4,8,8])
192 ---------------------------------------------------------------------------
193 -- Monoidal composition recursion loops
194 ---------------------------------------------------------------------------
196 describe "Serial loops (<>)" $ loops (<>) id reverse
197 describe "Left biased parallel loops (<|)" $ loops (<|) sort sort
198 describe "Fair parallel loops (<|>)" $ loops (<|>) sort sort
200 ---------------------------------------------------------------------------
201 -- Bind and monoidal composition combinations
202 ---------------------------------------------------------------------------
204 forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
205 describe "Bind and compose" $ bindAndComposeSimple toListSerial g
207 forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
208 describe "Bind and compose" $ bindAndComposeSimple toListInterleaved g
210 forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
211 describe "Bind and compose" $ bindAndComposeSimple toListAsync g
213 forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
214 describe "Bind and compose" $ bindAndComposeSimple toListParallel g
216 let fldr f = foldr f empty
217 fldl f = foldl f empty
219 forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
220 forM_ [fldr, fldl] $ \k ->
221 describe "Bind and compose" $
222 bindAndComposeHierarchy toListSerial (k g)
223 forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
224 forM_ [fldr, fldl] $ \k ->
225 describe "Bind and compose" $
226 bindAndComposeHierarchy toListInterleaved (k g)
227 forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
228 forM_ [fldr, fldl] $ \k ->
229 describe "Bind and compose" $
230 bindAndComposeHierarchy toListAsync (k g)
231 forM_ [(<>), (<=>), (<|), (<|>)] $ \g ->
232 forM_ [fldr, fldl] $ \k ->
233 describe "Bind and compose" $
234 bindAndComposeHierarchy toListParallel (k g)
236 -- Nest two lists using different styles of product compositions
237 it "Nests two streams using monadic serial composition" nestTwoSerial
238 it "Nests two streams using monadic interleaved composition" nestTwoInterleaved
239 it "Nests two streams using monadic async composition" nestTwoAsync
240 it "Nests two streams using monadic parallel composition" nestTwoParallel
242 it "Nests two streams using applicative serial composition" nestTwoSerialApp
243 it "Nests two streams using applicative interleaved composition" nestTwoInterleavedApp
244 it "Nests two streams using applicative async composition" nestTwoAsyncApp
245 it "Nests two streams using applicative parallel composition" nestTwoParallelApp
247 it "Nests two streams using Num serial composition" nestTwoSerialNum
248 it "Nests two streams using Num interleaved composition" nestTwoInterleavedNum
249 it "Nests two streams using Num async composition" nestTwoAsyncNum
250 -- This test fails intermittently, need to investigate
251 -- it "Nests two streams using Num parallel composition" nestTwoParallelNum
253 ---------------------------------------------------------------------------
254 -- TBD Bind and Bind combinations
255 ---------------------------------------------------------------------------
257 -- TBD combine all binds and all compose in one example
258 describe "Miscellaneous combined examples" mixedOps
260 describe "Transformation" $ transformOps (<>)
261 describe "Serial zipping" $
262 zipOps A.zipWith A.zipWithM zipping
263 describe "Async zipping" $
264 zipOps A.zipAsyncWith A.zipAsyncWithM zippingAsync
266nestTwoSerial :: Expectation
268 let s1 = foldMapWith (<>) return [1..4]
269 s2 = foldMapWith (<>) return [5..8]
274 ) `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])
276nestTwoSerialApp :: Expectation
278 let s1 = foldMapWith (<>) return [1..4]
279 s2 = foldMapWith (<>) return [5..8]
280 in toListSerial ((+) <$> s1 <*> s2)
281 `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])
283nestTwoSerialNum :: Expectation
285 let s1 = foldMapWith (<>) return [1..4]
286 s2 = foldMapWith (<>) return [5..8]
287 in toListSerial (s1 + s2)
288 `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])
290nestTwoInterleaved :: Expectation
292 let s1 = foldMapWith (<>) return [1..4]
293 s2 = foldMapWith (<>) return [5..8]
294 in toListInterleaved (do
298 ) `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])
300nestTwoInterleavedApp :: Expectation
301nestTwoInterleavedApp =
302 let s1 = foldMapWith (<>) return [1..4]
303 s2 = foldMapWith (<>) return [5..8]
304 in toListInterleaved ((+) <$> s1 <*> s2)
305 `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])
307nestTwoInterleavedNum :: Expectation
308nestTwoInterleavedNum =
309 let s1 = foldMapWith (<>) return [1..4]
310 s2 = foldMapWith (<>) return [5..8]
311 in toListInterleaved (s1 + s2)
312 `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])
314nestTwoAsync :: Expectation
316 let s1 = foldMapWith (<>) return [1..4]
317 s2 = foldMapWith (<>) return [5..8]
322 ) `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])
324nestTwoAsyncApp :: Expectation
326 let s1 = foldMapWith (<>) return [1..4]
327 s2 = foldMapWith (<>) return [5..8]
328 in toListAsync ((+) <$> s1 <*> s2)
329 `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])
331nestTwoAsyncNum :: Expectation
333 let s1 = foldMapWith (<>) return [1..4]
334 s2 = foldMapWith (<>) return [5..8]
335 in toListAsync (s1 + s2)
336 `shouldReturn` ([6,7,8,9,7,8,9,10,8,9,10,11,9,10,11,12] :: [Int])
338nestTwoParallel :: Expectation
340 let s1 = foldMapWith (<>) return [1..4]
341 s2 = foldMapWith (<>) return [5..8]
342 in toListParallel (do
346 ) `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])
348nestTwoParallelApp :: Expectation
350 let s1 = foldMapWith (<>) return [1..4]
351 s2 = foldMapWith (<>) return [5..8]
352 in toListParallel ((+) <$> s1 <*> s2)
353 `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])
356nestTwoParallelNum :: Expectation
358 let s1 = foldMapWith (<>) return [1..4]
359 s2 = foldMapWith (<>) return [5..8]
360 in toListParallel (s1 + s2)
361 `shouldReturn` ([6,7,7,8,8,8,9,9,9,9,10,10,10,11,11,12] :: [Int])
364zipOps :: (Streaming t, Applicative (t IO))
365 => (forall a b c. (a -> b -> c)
366 -> StreamT IO a -> StreamT IO b -> StreamT IO c)
367 -> (forall a b c. (a -> b -> StreamT IO c)
368 -> StreamT IO a -> StreamT IO b -> StreamT IO c)
369 -> (forall a. t IO a -> t IO a)
373 let s1 = foldMapWith (<>) return [1..10]
374 s2 = foldMapWith (<>) return [1..]
375 in toListSerial (z (+) s1 s2)
376 `shouldReturn` ([2,4..20] :: [Int])
379 let s1 = foldMapWith (<>) return [1..10]
380 s2 = foldMapWith (<>) return [1..]
381 in toListSerial (zM (\a b -> return (a + b)) s1 s2)
382 `shouldReturn` ([2,4..20] :: [Int])
384 it "Applicative zip" $
385 let s1 = adapt $ serially $ foldMapWith (<>) return [1..10]
386 s2 = adapt $ serially $ foldMapWith (<>) return [1..]
387 in (A.toList . app) ((+) <$> s1 <*> s2)
388 `shouldReturn` ([2,4..20] :: [Int])
390timed :: Int -> StreamT IO Int
391timed x = liftIO (threadDelay (x * 100000)) >> return x
395 it "Simple runStreaming and 'then' with IO" $
396 (runStreaming . serially) (liftIO (putStrLn "hello") >> liftIO (putStrLn "world"))
398 it "Then and toList" $
399 toListSerial (return (1 :: Int) >> return 2) `shouldReturn` ([2] :: [Int])
401type ToListType s = (forall a. Ord a => s IO a -> IO [a])
402pureBind :: Monad (s IO) => ToListType s -> Spec
404 it "Bind and toList" $
405 l (return 1 `f` \x -> return 2 `f` \y -> return (x + y))
406 `shouldReturn` ([3] :: [Int])
409bindEmpty :: (Monad (s IO), Alternative (s IO)) => ToListType s -> Spec
410bindEmpty l = it "Binds with empty" $
411 (l (return (1 :: Int) `f` \_ -> empty `f` \_ -> return 2))
412 `shouldReturn` ([] :: [Int])
416 :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int)
419 it "Interleave four" $
420 toListSerial ((return 0 <> return 1) `f` (return 100 <> return 101))
421 `shouldReturn` ([0, 100, 1, 101])
423parallelCheck :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int) -> Spec
425 it "Parallel ordering left associated" $
426 toListSerial (((event 4 `f` event 3) `f` event 2) `f` event 1)
427 `shouldReturn` ([1..4])
429 it "Parallel ordering right associated" $
430 toListSerial (event 4 `f` (event 3 `f` (event 2 `f` event 1)))
431 `shouldReturn` ([1..4])
433 where event n = (liftIO $ threadDelay (n * 100000)) >> (return n)
436 :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int)
440 it "Compose mempty, mempty" $
441 (tl (mempty `f` mempty)) `shouldReturn` []
442 it "Compose empty, empty" $
443 (tl (empty `f` empty)) `shouldReturn` []
444 it "Compose empty at the beginning" $
445 (tl $ (empty `f` return 1)) `shouldReturn` [1]
446 it "Compose empty at the end" $
447 (tl $ (return 1 `f` empty)) `shouldReturn` [1]
449 (tl (return 0 `f` return 1) >>= return . srt)
450 `shouldReturn` [0, 1]
451 it "Compose three - empty in the middle" $
452 ((tl $ (return 0 `f` empty `f` return 1)) >>= return . srt)
453 `shouldReturn` [0, 1]
454 it "Compose left associated" $
455 ((tl $ (((return 0 `f` return 1) `f` return 2) `f` return 3))
456 >>= return . srt) `shouldReturn` [0, 1, 2, 3]
457 it "Compose right associated" $
458 ((tl $ (return 0 `f` (return 1 `f` (return 2 `f` return 3))))
459 >>= return . srt) `shouldReturn` [0, 1, 2, 3]
461 ((tl $ forEachWith f [1..100] return) >>= return . srt)
462 `shouldReturn` [1..100]
463 it "Compose hierarchical (multiple levels)" $
464 ((tl $ (((return 0 `f` return 1) `f` (return 2 `f` return 3))
465 `f` ((return 4 `f` return 5) `f` (return 6 `f` return 7)))
466 ) >>= return . srt) `shouldReturn` [0..7]
467 where tl = toListSerial
469composeAndComposeSimple
470 :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int)
471 -> (StreamT IO Int -> StreamT IO Int -> StreamT IO Int)
474composeAndComposeSimple f g answer = do
475 it "Compose right associated outer expr, right folded inner" $
476 let fold = foldMapWith g return
477 in (toListSerial (fold [1,2,3] `f` (fold [4,5,6] `f` fold [7,8,9])))
478 `shouldReturn` (answer !! 0)
480 it "Compose left associated outer expr, right folded inner" $
481 let fold = foldMapWith g return
482 in (toListSerial ((fold [1,2,3] `f` fold [4,5,6]) `f` fold [7,8,9]))
483 `shouldReturn` (answer !! 1)
485 it "Compose right associated outer expr, left folded inner" $
486 let fold xs = foldl g empty $ map return xs
487 in (toListSerial (fold [1,2,3] `f` (fold [4,5,6] `f` fold [7,8,9])))
488 `shouldReturn` (answer !! 2)
490 it "Compose left associated outer expr, left folded inner" $
491 let fold xs = foldl g empty $ map return xs
492 in (toListSerial ((fold [1,2,3] `f` fold [4,5,6]) `f` fold [7,8,9]))
493 `shouldReturn` (answer !! 3)
497 :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int)
501loops f tsrt hsrt = do
502 it "Tail recursive loop" $ (toListSerial (loopTail 0) >>= return . tsrt)
503 `shouldReturn` [0..3]
505 it "Head recursive loop" $ (toListSerial (loopHead 0) >>= return . hsrt)
506 `shouldReturn` [0..3]
510 -- this print line is important for the test (causes a bind)
511 liftIO $ putStrLn "LoopHead..."
512 (if x < 3 then loopHead (x + 1) else empty) `f` return x
515 -- this print line is important for the test (causes a bind)
516 liftIO $ putStrLn "LoopTail..."
517 return x `f` (if x < 3 then loopTail (x + 1) else empty)
520 :: (Streaming t, Alternative (t IO), Monad (t IO))
521 => (forall a. Ord a => t IO a -> IO [a])
522 -> (t IO Int -> t IO Int -> t IO Int)
524bindAndComposeSimple tl g = do
525 it "Compose many (right fold) with bind" $
526 (tl (forEachWith g [1..10 :: Int] $ \x -> return x `f` (return . id))
527 >>= return . sort) `shouldReturn` [1..10]
529 it "Compose many (left fold) with bind" $
530 let forL xs k = foldl g empty $ map k xs
531 in (tl (forL [1..10 :: Int] $ \x -> return x `f` (return . id))
532 >>= return . sort) `shouldReturn` [1..10]
535bindAndComposeHierarchy
536 :: Monad (s IO) => (forall a. Ord a => s IO a -> IO [a])
537 -> ([s IO Int] -> s IO Int)
539bindAndComposeHierarchy tl g = do
540 it "Bind and compose nested" $
541 (tl bindComposeNested >>= return . sort)
542 `shouldReturn` (sort (
548 ++ replicate 7 15) :: [Int])
552 -- bindComposeNested :: AsyncT IO Int
554 let c1 = tripleCompose (return 1) (return 2) (return 3)
555 c2 = tripleCompose (return 4) (return 5) (return 6)
556 c3 = tripleCompose (return 7) (return 8) (return 9)
557 b = tripleBind c1 c2 c3
558-- it seems to be causing a huge space leak in hspec so disabling this for now
559-- c = tripleCompose b b b
560-- m = tripleBind c c c
564 tripleCompose a b c = g [a, b, c]
565 tripleBind mx my mz =
568 `f` \z -> return (x + y + z)
573 it "Compose many ops" $
574 (toListSerial composeMixed >>= return . sort)
575 `shouldReturn` ([8,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,11,11
576 ,11,11,11,11,11,11,11,11,12,12,12,12,12,13
580 composeMixed :: StreamT IO Int
587 x1 <- return 1 <|> return 2
590 y1 <- return 1 <| return 2
592 x11 <- return 1 <> return 2
593 y11 <- return 1 <| return 2
594 z11 <- return 1 <=> return 2
597 return (x11 + y11 + z11)
598 return (x1 + y1 + z1)
601transformOps :: (StreamT IO Int -> StreamT IO Int -> StreamT IO Int) -> Spec
604 (toListSerial $ A.take 10 $ foldMapWith f return [1..10])
605 `shouldReturn` [1..10]
607 (toListSerial $ A.take 0 $ foldMapWith f return [1..10])
610 (toListSerial $ A.take 5 $ foldMapWith f return [1..10])
611 `shouldReturn` [1..5]
614 (toListSerial $ A.drop 10 $ foldMapWith f return [1..10])
617 (toListSerial $ A.drop 0 $ foldMapWith f return [1..10])
618 `shouldReturn` [1..10]
620 (toListSerial $ A.drop 5 $ foldMapWith f return [1..10])
621 `shouldReturn` [6..10]
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