vgrep-0.2.3.0: src/Vgrep/Widget/Results.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Rank2Types #-}
module Vgrep.Widget.Results (
-- * Results list widget
resultsWidget
, ResultsWidget
-- ** Internal widget state
, Results ()
-- ** Widget actions
, feedResult
, resizeToWindow
, prevLine
, nextLine
, pageUp
, pageDown
-- ** Lenses
, currentFileName
, currentLineNumber
, currentFileResults
-- * Re-exports
, module Vgrep.Results
) where
import Control.Applicative
import Control.Lens.Compat
import Control.Monad.State.Extended
import Data.Foldable
import Data.Maybe
import Data.Text (Text)
import qualified Data.Text as T
import Graphics.Vty.Attributes
import Graphics.Vty.Image hiding ((<|>))
import Vgrep.Ansi
import Vgrep.Environment
import Vgrep.Event
import Vgrep.Results
import Vgrep.Type
import Vgrep.Widget.Results.Internal as Internal
import Vgrep.Widget.Type
type ResultsWidget = Widget Results
-- | The results widget displays a list of lines with line numbers, grouped
-- by files.
--
-- * __Initial state__
--
-- The initial buffer is empty and can be filled line by line using
-- 'feedResult'.
--
-- * __Drawing the results list__
--
-- Found matches are grouped by file name. Each file group has a header
-- and a list of result lines with line numbers. The result lines can
-- be selected with the cursor, the file group headers are skipped.
-- When only part of a file group is shown at the top of the screen,
-- the header is shown nevertheless.
resultsWidget :: ResultsWidget
resultsWidget =
Widget { initialize = initResults
, draw = renderResultList }
initResults :: Results
initResults = EmptyResults
-- | Add a line to the results list. If the result is found in the same
-- file as the current last result, it will be added to the same results
-- group, otherwise a new group will be opened.
feedResult :: Monad m => FileLineReference -> VgrepT Results m Redraw
feedResult line = do
modify (feed line)
resizeToWindow
-- | Move up/down one results page. File group headers will be skipped.
pageUp, pageDown :: Monad m => VgrepT Results m ()
pageUp = do
unlessS (isJust . moveUp) $ do
modify (repeatedly (hideNext >=> showPrev))
void resizeToWindow
modify (repeatedly moveUp)
pageDown = do
unlessS (isJust . moveDown) $ do
modify (repeatedly hidePrev)
void resizeToWindow
modify (repeatedly moveDown)
repeatedly :: (a -> Maybe a) -> a -> a
repeatedly f = go
where
go x | Just x' <- f x = go x'
| otherwise = x
-- | Move up/down one results line. File group headers will be skipped.
prevLine, nextLine :: Monad m => VgrepT Results m ()
prevLine = maybeModify tryPrevLine >> void resizeToWindow
nextLine = maybeModify tryNextLine >> void resizeToWindow
tryPrevLine, tryNextLine :: Results -> Maybe Results
tryPrevLine buf = moveUp buf <|> (showPrev buf >>= tryPrevLine)
tryNextLine buf = moveDown buf <|> (showNext buf >>= tryNextLine)
maybeModify :: Monad m => (s -> Maybe s) -> VgrepT s m ()
maybeModify f = do
s <- get
case f s of
Just s' -> put s'
Nothing -> pure ()
renderResultList :: Monad m => VgrepT Results m Image
renderResultList = do
void resizeToWindow
visibleLines <- use (to toLines)
width <- view viewportWidth
let render = renderLine width (lineNumberWidth visibleLines)
renderedLines <- traverse render visibleLines
pure (vertCat renderedLines)
where lineNumberWidth
= foldl' max 0
. map (twoExtraSpaces . length . show)
. mapMaybe displayLineNumber
twoExtraSpaces = (+ 2) -- because line numbers are padded,
-- see `justifyRight` below
renderLine
:: Monad m
=> Int
-> Int
-> DisplayLine
-> VgrepT Results m Image
renderLine width lineNumberWidth displayLine = do
fileHeaderStyle <- view (config . colors . fileHeaders)
lineNumberStyle <- view (config . colors . lineNumbers)
resultLineStyle <- view (config . colors . normal)
selectedStyle <- view (config . colors . selected)
pure $ case displayLine of
FileHeader (File f)
-> renderFileHeader fileHeaderStyle f
Line (LineReference n t)
-> horizCat [ renderLineNumber lineNumberStyle n
, renderLineText resultLineStyle t ]
SelectedLine (LineReference n t)
-> horizCat [ renderLineNumber lineNumberStyle n
, renderLineText selectedStyle t ]
where
padWithSpace w = T.take (fromIntegral w)
. T.justifyLeft (fromIntegral w) ' '
. T.cons ' '
justifyRight w s = T.justifyRight (fromIntegral w) ' ' (s <> " ")
renderFileHeader :: Attr -> Text -> Image
renderFileHeader attr = text' attr . padWithSpace width
renderLineNumber :: Attr -> Maybe Int -> Image
renderLineNumber attr = text' attr
. justifyRight lineNumberWidth
. maybe "" (T.pack . show)
renderLineText :: Attr -> AnsiFormatted -> Image
renderLineText attr txt
= renderAnsi attr
. takeFormatted (width - lineNumberWidth)
. padFormatted (width - lineNumberWidth) ' '
$ cat [ bare " ", txt, bare (T.replicate width " ") ]
resizeToWindow :: Monad m => VgrepT Results m Redraw
resizeToWindow = do
height <- view viewportHeight
currentBuffer <- get
case Internal.resize height currentBuffer of
Just resizedBuffer -> put resizedBuffer >> pure Redraw
Nothing -> pure Unchanged