packages feed

hdmenu-0.3.0: app/Main.hs

module Main
    ( main  -- :: IO ()
    ) where

import CLI.Parser (Options (Options, dmenuOpts, historyPath, onlyFiles), options)
import Core.Select (evalDirs, getExecutables, makeNewEntries, runUpdate, selectWith, sortByValues, tryRead)
import Core.Toml (Config (Config, dmenuExe, files, histPath), getUserConfig)
import Core.Util

import Data.Map.Strict qualified as Map

import Options.Applicative (execParser)
import System.Posix.Env.ByteString (getEnvDefault)


-- | Execute dmenu and then do stuff.
main :: IO ()
main = do
    -- Get command line options and parse them.
    Options{ historyPath, dmenuOpts, onlyFiles } <- execParser options

    -- Create the `hdmenu' directory (and all parents) if necessary and
    -- parse user config.
    createDirectoryIfMissing True =<< hdmenuPath
    cfg@Config{ dmenuExe, files } <- getUserConfig

    -- Files the user added in the config file; see Note [Caching].
    home      <- getEnvDefault "HOME" ""
    userFiles <- evalDirs $ map (tryAddPrefix home) files

    -- See Note [Updating].
    newEntries <- makeNewEntries . (userFiles <>) <$> getExecutables
    hp         <- maybe histFile pure historyPath  -- path to history file
    hist       <- tryRead hp
    let newMap = (hist `Map.intersection` newEntries) <> newEntries

    -- Let the user select something from the list.
    let selectFrom | onlyFiles = filter (`elem` userFiles)
                   | otherwise = id
    selection <- selectWith dmenuOpts
                            (selectFrom $! sortByValues newMap)
                            dmenuExe

    -- Process output.
    case selection of
        Left  _ -> pure ()  -- silently fail
        Right s -> runUpdate s cfg{ files = userFiles, histPath = hp } newMap

{- Note [Updating]
   ~~~~~~~~~~~~~~~~~~~~~~
We create two new maps; one for all the "new things" (newEntries) and
one for everything "old" (i.e., known to us).  Then, what we are doing
(with A = 'hist' and B = 'newEntries') is this:

    (A ∩ B) ∪ B,

where the union and intersection for "Data.Map" maps is left-biased.
Note that, while in a set-theoretic context this would obviously equal
B, this is not the case here; the elements of A and B are tuples, but
the first component alone determines uniqueness!  We also explicitly
exploit the left-biasedness (I'm claiming that word) of the operations.

All in all, we add initialise everything new, throw everything out
that's not in the $PATH or in the config anymore, and update the rest.
-}

{- Note [Caching]
   ~~~~~~~~~~~~~~~~~~~~~~
Doing the caching *after* the user has selected something may be better
(in terms of perceived speed), though hdmenu would "lag behind" for one
execution when things are updated.

As of version 0.2.0, we're almost as fast as before being able to keep
track of often used commands, so this is almost certainly a non-issue.
-}