2Module : Idris.Parser.Helpers
3Description : Utilities for Idris' parser.
6Maintainer : The Idris Community.
8{-# LANGUAGE CPP, ConstraintKinds, GeneralizedNewtypeDeriving, PatternGuards,
10module Idris.Parser.Helpers where
13import Idris.Core.Evaluate
15import Idris.Delaborate (pprintErr)
16import Idris.Docstrings
18import Idris.Output (iWarn)
20import qualified Util.Pretty as Pretty (text)
22import Prelude hiding (pi)
24import Control.Applicative
26import Control.Monad.State.Strict
27import qualified Data.ByteString.UTF8 as UTF8
29import qualified Data.HashSet as HS
31import qualified Data.List.Split as Spl
32import qualified Data.Map as M
35import qualified Data.Text as T
38import qualified Text.Parser.Char as Chr
39import Text.Parser.Expression
40import Text.Parser.LookAhead
41import qualified Text.Parser.Token as Tok
42import qualified Text.Parser.Token.Highlight as Hi
43import Text.Trifecta hiding (Err, char, charLiteral, natural, span, string,
44 stringLiteral, symbol, whiteSpace)
45import Text.Trifecta.Delta
47-- | Idris parser with state used during parsing
48type IdrisParser = StateT IState IdrisInnerParser
50newtype IdrisInnerParser a = IdrisInnerParser { runInnerParser :: Parser a }
51 deriving (Monad, Functor, MonadPlus, Applicative, Alternative, CharParsing, LookAheadParsing, DeltaParsing, MarkParsing Delta, Monoid, TokenParsing)
53deriving instance Parsing IdrisInnerParser
55#if MIN_VERSION_base(4,9,0)
56instance {-# OVERLAPPING #-} DeltaParsing IdrisParser where
59 position = lift position
60 {-# INLINE position #-}
61 slicedWith f (StateT m) = StateT $ \s -> slicedWith (\(a,s') b -> (f a b, s')) $ m s
62 {-# INLINE slicedWith #-}
65 restOfLine = lift restOfLine
66 {-# INLINE restOfLine #-}
70instance {-# OVERLAPPING #-} TokenParsing IdrisParser where
71 someSpace = many (simpleWhiteSpace <|> singleLineComment <|> multiLineComment) *> pure ()
73 (FC fn (sl, sc) _) <- getFC --TODO: Update after fixing getFC
76 (FC fn _ (el, ec)) <- getFC
78 put (s { lastTokenSpan = Just (FC fn (sl, sc) (el, ec)) })
80-- | Generalized monadic parsing constraint type
81type MonadicParsing m = (DeltaParsing m, LookAheadParsing m, TokenParsing m, Monad m)
83class HasLastTokenSpan m where
84 getLastTokenSpan :: m (Maybe FC)
86instance HasLastTokenSpan IdrisParser where
87 getLastTokenSpan = lastTokenSpan <$> get
89-- | Helper to run Idris inner parser based stateT parsers
90runparser :: StateT st IdrisInnerParser res -> st -> String -> String -> Result res
91runparser p i inputname =
92 parseString (runInnerParser (evalStateT p i))
93 (Directed (UTF8.fromString inputname) 0 0 0 0)
95highlightP :: FC -> OutputAnnotation -> IdrisParser ()
96highlightP fc annot = do ist <- get
97 put ist { idris_parserHighlights = (fc, annot) : idris_parserHighlights ist}
99noDocCommentHere :: String -> IdrisParser ()
100noDocCommentHere msg =
101 optional (do fc <- getFC
104 put ist { parserWarnings = (fc, Msg msg) : parserWarnings ist}) *>
107clearParserWarnings :: Idris ()
108clearParserWarnings = do ist <- getIState
109 putIState ist { parserWarnings = [] }
111reportParserWarnings :: Idris ()
112reportParserWarnings = do ist <- getIState
113 mapM_ (uncurry iWarn)
114 (map (\ (fc, err) -> (fc, pprintErr ist err)) .
116 nubBy (\(fc, err) (fc', err') ->
117 FC' fc == FC' fc' && err == err') $
122parserWarning :: FC -> Maybe Opt -> Err -> IdrisParser ()
123parserWarning fc warnOpt warnErr = do
125 let cmdline = opt_cmdline (idris_options ist)
126 unless (maybe False (`elem` cmdline) warnOpt) $
127 put ist { parserWarnings = (fc, warnErr) : parserWarnings ist }
129{- * Space, comments and literals (token/lexing like parsers) -}
131-- | Consumes any simple whitespace (any character which satisfies Char.isSpace)
132simpleWhiteSpace :: MonadicParsing m => m ()
133simpleWhiteSpace = satisfy isSpace *> pure ()
135-- | Checks if a charcter is end of line
140-- | A parser that succeeds at the end of the line
141eol :: MonadicParsing m => m ()
142eol = (satisfy isEol *> pure ()) <|> lookAhead eof <?> "end of line"
144{- | Consumes a single-line comment
147 SingleLineComment_t ::= '--' ~EOL_t* EOL_t ;
150singleLineComment :: MonadicParsing m => m ()
151singleLineComment = (string "--" *>
152 many (satisfy (not . isEol)) *>
156{- | Consumes a multi-line comment
159 MultiLineComment_t ::=
161 | '{ -' InCommentChars_t
168 | MultiLineComment_t InCommentChars_t
169 | ~'- }'+ InCommentChars_t
173multiLineComment :: MonadicParsing m => m ()
174multiLineComment = try (string "{-" *> (string "-}") *> pure ())
175 <|> string "{-" *> inCommentChars
177 where inCommentChars :: MonadicParsing m => m ()
178 inCommentChars = string "-}" *> pure ()
179 <|> try (multiLineComment) *> inCommentChars
180 <|> string "|||" *> many (satisfy (not . isEol)) *> eol *> inCommentChars
181 <|> skipSome (noneOf startEnd) *> inCommentChars
182 <|> oneOf startEnd *> inCommentChars
187{-| Parses a documentation comment
190 DocComment_t ::= '|||' ~EOL_t* EOL_t
194docComment :: IdrisParser (Docstring (), [(Name, Docstring ())])
195docComment = do dc <- pushIndent *> docCommentLine
196 rest <- many (indented docCommentLine)
197 args <- many $ do (name, first) <- indented argDocCommentLine
198 rest <- many (indented docCommentLine)
199 return (name, concat (intersperse "\n" (first:rest)))
201 return (parseDocstring $ T.pack (concat (intersperse "\n" (dc:rest))),
202 map (\(n, d) -> (n, parseDocstring (T.pack d))) args)
204 where docCommentLine :: MonadicParsing m => m String
205 docCommentLine = try (do string "|||"
206 many (satisfy (==' '))
207 contents <- option "" (do first <- satisfy (\c -> not (isEol c || c == '@'))
208 res <- many (satisfy (not . isEol))
211 return contents)-- ++ concat rest))
214 argDocCommentLine = do string "|||"
215 many (satisfy isSpace)
217 many (satisfy isSpace)
219 many (satisfy isSpace)
220 docs <- many (satisfy (not . isEol))
224-- | Parses some white space
225whiteSpace :: MonadicParsing m => m ()
226whiteSpace = Tok.whiteSpace
228-- | Parses a string literal
229stringLiteral :: (MonadicParsing m, HasLastTokenSpan m) => m (String, FC)
230stringLiteral = do str <- Tok.stringLiteral
231 fc <- getLastTokenSpan
232 return (str, fromMaybe NoFC fc)
234-- | Parses a char literal
235charLiteral :: (MonadicParsing m, HasLastTokenSpan m) => m (Char, FC)
236charLiteral = do ch <- Tok.charLiteral
237 fc <- getLastTokenSpan
238 return (ch, fromMaybe NoFC fc)
240-- | Parses a natural number
241natural :: (MonadicParsing m, HasLastTokenSpan m) => m (Integer, FC)
242natural = do n <- Tok.natural
243 fc <- getLastTokenSpan
244 return (n, fromMaybe NoFC fc)
246-- | Parses an integral number
247integer :: MonadicParsing m => m Integer
250-- | Parses a floating point number
251float :: (MonadicParsing m, HasLastTokenSpan m) => m (Double, FC)
252float = do f <- Tok.double
253 fc <- getLastTokenSpan
254 return (f, fromMaybe NoFC fc)
256{- * Symbols, identifiers, names and operators -}
259-- | Idris Style for parsing identifiers/reserved keywords
260idrisStyle :: MonadicParsing m => IdentifierStyle m
261idrisStyle = IdentifierStyle {
263 , _styleStart = satisfy isAlpha <|> oneOf "_"
264 , _styleLetter = satisfy isAlphaNum <|> oneOf "_'."
265 , _styleReserved = HS.fromList
266 ["let", "in", "data", "codata", "record", "corecord", "Type", "do", "dsl",
267 "import", "impossible", "case", "of", "total", "partial", "mutual",
268 "infix", "infixl", "infixr", "rewrite", "where", "with", "syntax",
269 "proof", "postulate", "using", "namespace", "class", "instance",
270 "interface", "implementation", "parameters", "public", "private",
271 "export", "abstract", "implicit", "quoteGoal", "constructor", "if",
273 , _styleHighlight = Hi.Identifier
274 , _styleReservedHighlight = Hi.ReservedIdentifier }
276char :: MonadicParsing m => Char -> m Char
279string :: MonadicParsing m => String -> m String
282-- | Parses a character as a token
283lchar :: MonadicParsing m => Char -> m Char
286-- | Parses a character as a token, returning the source span of the character
287lcharFC :: MonadicParsing m => Char -> m FC
288lcharFC ch = do (FC file (l, c) _) <- getFC
290 return $ FC file (l, c) (l, c+1)
292-- | Parses string as a token
293symbol :: MonadicParsing m => String -> m String
296symbolFC :: MonadicParsing m => String -> m FC
297symbolFC str = do (FC file (l, c) _) <- getFC
299 return $ FC file (l, c) (l, c + length str)
301-- | Parses a reserved identifier
302reserved :: MonadicParsing m => String -> m ()
303reserved = Tok.reserve idrisStyle
305-- | Parses a reserved identifier, computing its span. Assumes that
306-- reserved identifiers never contain line breaks.
307reservedFC :: MonadicParsing m => String -> m FC
308reservedFC str = do (FC file (l, c) _) <- getFC
310 return $ FC file (l, c) (l, c + length str)
312-- | Parse a reserved identfier, highlighting its span as a keyword
313reservedHL :: String -> IdrisParser ()
314reservedHL str = reservedFC str >>= flip highlightP AnnKeyword
316-- Taken from Parsec (c) Daan Leijen 1999-2001, (c) Paolo Martini 2007
317-- | Parses a reserved operator
318reservedOp :: MonadicParsing m => String -> m ()
319reservedOp name = token $ try $
321 notFollowedBy (operatorLetter) <?> ("end of " ++ show name)
323reservedOpFC :: MonadicParsing m => String -> m FC
324reservedOpFC name = do (FC f (l, c) _) <- getFC
326 return (FC f (l, c) (l, c + length name))
328-- | Parses an identifier as a token
329identifier :: (MonadicParsing m) => m (String, FC)
330identifier = try(do (i, fc) <-
331 token $ do (FC f (l, c) _) <- getFC
332 i <- Tok.ident idrisStyle
333 return (i, FC f (l, c) (l, c + length i))
334 when (i == "_") $ unexpected "wildcard"
337-- | Parses an identifier with possible namespace as a name
338iName :: (MonadicParsing m, HasLastTokenSpan m) => [String] -> m (Name, FC)
339iName bad = do (n, fc) <- maybeWithNS identifier False bad
343-- | Parses an string possibly prefixed by a namespace
344maybeWithNS :: (MonadicParsing m, HasLastTokenSpan m) => m (String, FC) -> Bool -> [String] -> m (Name, FC)
345maybeWithNS parser ascend bad = do
347 i <- option "" (lookAhead (fst <$> identifier))
348 when (i `elem` bad) $ unexpected "reserved identifier"
349 let transf = if ascend then id else reverse
350 (x, xs, fc) <- choice (transf (parserNoNS parser : parsersNS parser i))
351 return (mkName (x, xs), fc)
352 where parserNoNS :: MonadicParsing m => m (String, FC) -> m (String, String, FC)
353 parserNoNS parser = do startFC <- getFC
354 (x, nameFC) <- parser
355 return (x, "", spanFC startFC nameFC)
356 parserNS :: MonadicParsing m => m (String, FC) -> String -> m (String, String, FC)
357 parserNS parser ns = do startFC <- getFC
359 lchar '.'; (x, nameFC) <- parser
360 return (x, xs, spanFC startFC nameFC)
361 parsersNS :: MonadicParsing m => m (String, FC) -> String -> [m (String, String, FC)]
362 parsersNS parser i = [try (parserNS parser ns) | ns <- (initsEndAt (=='.') i)]
365name :: IdrisParser (Name, FC)
366name = (<?> "name") $ do
367 keywords <- syntax_keywords <$> get
368 aliases <- module_aliases <$> get
369 (n, fc) <- iName keywords
370 return (unalias aliases n, fc)
372 unalias :: M.Map [T.Text] [T.Text] -> Name -> Name
373 unalias aliases (NS n ns) | Just ns' <- M.lookup ns aliases = NS n ns'
374 unalias aliases name = name
376{- | List of all initial segments in ascending order of a list. Every
377such initial segment ends right before an element satisfying the given
380initsEndAt :: (a -> Bool) -> [a] -> [[a]]
382initsEndAt p (x:xs) | p x = [] : x_inits_xs
383 | otherwise = x_inits_xs
384 where x_inits_xs = [x : cs | cs <- initsEndAt p xs]
387{- | Create a `Name' from a pair of strings representing a base name and its
390mkName :: (String, String) -> Name
391mkName (n, "") = sUN n
392mkName (n, ns) = sNS (sUN n) (reverse (parseNS ns))
393 where parseNS x = case span (/= '.') x of
395 (x, '.':y) -> x : parseNS y
398opChars = ":!#$%&*+./<=>?@\\^|-~"
400operatorLetter :: MonadicParsing m => m Char
401operatorLetter = oneOf opChars
404commentMarkers :: [String]
405commentMarkers = [ "--", "|||" ]
407invalidOperators :: [String]
408invalidOperators = [":", "=>", "->", "<-", "=", "?=", "|", "**", "==>", "\\", "%", "~", "?", "!", "@"]
410-- | Parses an operator
411operator :: MonadicParsing m => m String
412operator = do op <- token . some $ operatorLetter
413 when (op `elem` (invalidOperators ++ commentMarkers)) $
414 fail $ op ++ " is not a valid operator"
417-- | Parses an operator
418operatorFC :: MonadicParsing m => m (String, FC)
419operatorFC = do (FC f (l, c) _) <- getFC
421 return (op, FC f (l, c) (l, c + length op))
423{- * Position helpers -}
424{- | Get filename from position (returns "(interactive)" when no source file is given) -}
425fileName :: Delta -> String
426fileName (Directed fn _ _ _ _) = UTF8.toString fn
427fileName _ = "(interactive)"
429{- | Get line number from position -}
430lineNum :: Delta -> Int
431lineNum (Lines l _ _ _) = fromIntegral l + 1
432lineNum (Directed _ l _ _ _) = fromIntegral l + 1
435{- | Get column number from position -}
436columnNum :: Delta -> Int
437columnNum pos = fromIntegral (column pos) + 1
440{- | Get file position as FC -}
441getFC :: MonadicParsing m => m FC
442getFC = do s <- position
443 let (dir, file) = splitFileName (fileName s)
444 let f = if dir == addTrailingPathSeparator "." then file else fileName s
445 return $ FC f (lineNum s, columnNum s) (lineNum s, columnNum s) -- TODO: Change to actual spanning
446 -- Issue #1594 on the Issue Tracker.
447 -- https://github.com/idris-lang/Idris-dev/issues/1594
451-- | Bind constraints to term
452bindList :: (RigCount -> Name -> FC -> PTerm -> PTerm -> PTerm) -> [(RigCount, Name, FC, PTerm)] -> PTerm -> PTerm
454bindList b ((r, n, fc, t):bs) sc = b r n fc t (bindList b bs sc)
456{- | @commaSeparated p@ parses one or more occurences of `p`,
457 separated by commas and optional whitespace. -}
458commaSeparated :: MonadicParsing m => m a -> m [a]
459commaSeparated p = p `sepBy1` (spaces >> char ',' >> spaces)
461{- * Layout helpers -}
463-- | Push indentation to stack
464pushIndent :: IdrisParser ()
465pushIndent = do pos <- position
467 put (ist { indent_stack = (fromIntegral (column pos) + 1) : indent_stack ist })
469-- | Pops indentation from stack
470popIndent :: IdrisParser ()
471popIndent = do ist <- get
472 case indent_stack ist of
473 [] -> error "The impossible happened! Tried to pop an indentation level where none was pushed (underflow)."
474 (x : xs) -> put (ist { indent_stack = xs })
477-- | Gets current indentation
478indent :: IdrisParser Int
479indent = liftM ((+1) . fromIntegral . column) position
481-- | Gets last indentation
482lastIndent :: IdrisParser Int
483lastIndent = do ist <- get
484 case indent_stack ist of
488-- | Applies parser in an indented position
489indented :: IdrisParser a -> IdrisParser a
490indented p = notEndBlock *> p <* keepTerminator
492-- | Applies parser to get a block (which has possibly indented statements)
493indentedBlock :: IdrisParser a -> IdrisParser [a]
494indentedBlock p = do openBlock
496 res <- many (indented p)
501-- | Applies parser to get a block with at least one statement (which has possibly indented statements)
502indentedBlock1 :: IdrisParser a -> IdrisParser [a]
503indentedBlock1 p = do openBlock
505 res <- some (indented p)
510-- | Applies parser to get a block with exactly one (possibly indented) statement
511indentedBlockS :: IdrisParser a -> IdrisParser a
512indentedBlockS p = do openBlock
520-- | Checks if the following character matches provided parser
521lookAheadMatches :: MonadicParsing m => m a -> m Bool
522lookAheadMatches p = do match <- lookAhead (optional p)
523 return $ isJust match
525-- | Parses a start of block
526openBlock :: IdrisParser ()
527openBlock = do lchar '{'
529 put (ist { brace_stack = Nothing : brace_stack ist })
532 -- if we're not indented further, it's an empty block, so
533 -- increment lvl to ensure we get to the end
534 let lvl = case brace_stack ist of
536 if lvl' <= lvl_old then lvl_old+1
538 [] -> if lvl' == 1 then 2 else lvl'
540 put (ist { brace_stack = Just lvl : brace_stack ist })
543-- | Parses an end of block
544closeBlock :: IdrisParser ()
545closeBlock = do ist <- get
546 bs <- case brace_stack ist of
547 [] -> eof >> return []
548 Nothing : xs -> lchar '}' >> return xs <?> "end of block"
549 Just lvl : xs -> (do i <- indent
550 isParen <- lookAheadMatches (char ')')
551 isIn <- lookAheadMatches (reserved "in")
552 if i >= lvl && not (isParen || isIn)
553 then fail "not end of block"
555 <|> (do notOpenBraces
558 put (ist { brace_stack = bs })
560-- | Parses a terminator
561terminator :: IdrisParser ()
562terminator = do lchar ';'; popIndent
563 <|> do c <- indent; l <- lastIndent
564 if c <= l then popIndent else fail "not a terminator"
565 <|> do isParen <- lookAheadMatches (oneOf ")}")
566 if isParen then popIndent else fail "not a terminator"
569-- | Parses and keeps a terminator
570keepTerminator :: IdrisParser ()
571keepTerminator = do lchar ';'; return ()
572 <|> do c <- indent; l <- lastIndent
573 unless (c <= l) $ fail "not a terminator"
574 <|> do isParen <- lookAheadMatches (oneOf ")}|")
575 isIn <- lookAheadMatches (reserved "in")
576 unless (isIn || isParen) $ fail "not a terminator"
579-- | Checks if application expression does not end
580notEndApp :: IdrisParser ()
581notEndApp = do c <- indent; l <- lastIndent
582 when (c <= l) (fail "terminator")
584-- | Checks that it is not end of block
585notEndBlock :: IdrisParser ()
586notEndBlock = do ist <- get
587 case brace_stack ist of
588 Just lvl : xs -> do i <- indent
589 isParen <- lookAheadMatches (char ')')
590 when (i < lvl || isParen) (fail "end of block")
593-- | Representation of an operation that can compare the current indentation with the last indentation, and an error message if it fails
594data IndentProperty = IndentProperty (Int -> Int -> Bool) String
596-- | Allows comparison of indent, and fails if property doesn't hold
597indentPropHolds :: IndentProperty -> IdrisParser ()
598indentPropHolds (IndentProperty op msg) = do
601 when (not $ op i li) $ fail ("Wrong indention: " ++ msg)
603-- | Greater-than indent property
604gtProp :: IndentProperty
605gtProp = IndentProperty (>) "should be greater than context indentation"
607-- | Greater-than or equal to indent property
608gteProp :: IndentProperty
609gteProp = IndentProperty (>=) "should be greater than or equal context indentation"
611-- | Equal indent property
612eqProp :: IndentProperty
613eqProp = IndentProperty (==) "should be equal to context indentation"
615-- | Less-than indent property
616ltProp :: IndentProperty
617ltProp = IndentProperty (<) "should be less than context indentation"
619-- | Less-than or equal to indent property
620lteProp :: IndentProperty
621lteProp = IndentProperty (<=) "should be less than or equal to context indentation"
624-- | Checks that there are no braces that are not closed
625notOpenBraces :: IdrisParser ()
626notOpenBraces = do ist <- get
627 when (hasNothing $ brace_stack ist) $ fail "end of input"
628 where hasNothing :: [Maybe a] -> Bool
629 hasNothing = any isNothing
631{- | Parses an accessibilty modifier (e.g. public, private) -}
632accessibility' :: IdrisParser Accessibility
634 = do reserved "public";
635 gotexp <- optional (reserved "export")
641 put ist { parserWarnings =
642 (fc, Msg "'public' is deprecated. Use 'public export' instead.")
643 : parserWarnings ist }
645 <|> do reserved "abstract";
648 put ist { parserWarnings =
649 (fc, Msg "The 'abstract' keyword is deprecated. Use 'export' instead.")
650 : parserWarnings ist }
652 <|> do reserved "export"; return Frozen
653 <|> do reserved "private"; return Private
654 <?> "accessibility modifier"
656accessibility :: IdrisParser Accessibility
657accessibility = do acc <- optional accessibility'
660 Nothing -> do ist <- get
661 return (default_access ist)
663-- | Adds accessibility option for function
664addAcc :: Name -> Accessibility -> IdrisParser ()
665addAcc n a = do i <- get
666 put (i { hide_list = addDef n a (hide_list i) })
668{- | Add accessbility option for data declarations
669 (works for interfaces too - 'abstract' means the data/interface is visible but members not) -}
670accData :: Accessibility -> Name -> [Name] -> IdrisParser ()
671accData Frozen n ns = do addAcc n Public -- so that it can be used in public definitions
672 mapM_ (\n -> addAcc n Private) ns -- so that they are invisible
673accData a n ns = do addAcc n a
674 mapM_ (`addAcc` a) ns
677{- * Error reporting helpers -}
678{- | Error message with possible fixes list -}
679fixErrorMsg :: String -> [String] -> String
680fixErrorMsg msg fixes = msg ++ ", possible fixes:\n" ++ (concat $ intersperse "\n\nor\n\n" fixes)
682-- | Collect 'PClauses' with the same function name
683collect :: [PDecl] -> [PDecl]
684collect (c@(PClauses _ o _ _) : ds)
685 = clauses (cname c) [] (c : ds)
686 where clauses :: Maybe Name -> [PClause] -> [PDecl] -> [PDecl]
687 clauses j@(Just n) acc (PClauses fc _ _ [PClause fc' n' l ws r w] : ds)
688 | n == n' = clauses j (PClause fc' n' l ws r (collect w) : acc) ds
689 clauses j@(Just n) acc (PClauses fc _ _ [PWith fc' n' l ws r pn w] : ds)
690 | n == n' = clauses j (PWith fc' n' l ws r pn (collect w) : acc) ds
691 clauses (Just n) acc xs = PClauses (fcOf c) o n (reverse acc) : collect xs
692 clauses Nothing acc (x:xs) = collect xs
693 clauses Nothing acc [] = []
695 cname :: PDecl -> Maybe Name
696 cname (PClauses fc _ _ [PClause _ n _ _ _ _]) = Just n
697 cname (PClauses fc _ _ [PWith _ n _ _ _ _ _]) = Just n
698 cname (PClauses fc _ _ [PClauseR _ _ _ _]) = Nothing
699 cname (PClauses fc _ _ [PWithR _ _ _ _ _]) = Nothing
701 fcOf (PClauses fc _ _ _) = fc
702collect (PParams f ns ps : ds) = PParams f ns (collect ps) : collect ds
703collect (POpenInterfaces f ns ps : ds) = POpenInterfaces f ns (collect ps) : collect ds
704collect (PMutual f ms : ds) = PMutual f (collect ms) : collect ds
705collect (PNamespace ns fc ps : ds) = PNamespace ns fc (collect ps) : collect ds
706collect (PInterface doc f s cs n nfc ps pdocs fds ds cn cd : ds')
707 = PInterface doc f s cs n nfc ps pdocs fds (collect ds) cn cd : collect ds'
708collect (PImplementation doc argDocs f s cs pnames acc opts n nfc ps pextra t en ds : ds')
709 = PImplementation doc argDocs f s cs pnames acc opts n nfc ps pextra t en (collect ds) : collect ds'
710collect (d : ds) = d : collect ds
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