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.
-}