dmenu (empty) → 0.1.0.1
raw patch · 8 files changed
+726/−0 lines, 8 filesdep +basedep +containersdep +directorybuild-type:Customsetup-changedbinary-added
Dependencies added: base, containers, directory, lens, mtl, process, transformers
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- dmenu.cabal +56/−0
- doc/dmenu-pmount.png binary
- src/DMenu.hs +130/−0
- src/DMenu/Color.hs +33/−0
- src/DMenu/Options.hs +256/−0
- src/DMenu/Run.hs +219/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Hannes Saffrich (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
@@ -0,0 +1,56 @@+name:+ dmenu+version:+ 0.1.0.1+synopsis:+ Complete bindings to the dmenu and dmenu2 command line tools.+description:+ Provides fuzzy selection via a GUI menu.+homepage:+ https://github.com/m0rphism/haskell-dmenu+bug-reports:+ https://github.com/m0rphism/haskell-dmenu/issues+license:+ BSD3+license-file:+ LICENSE+author:+ Hannes Saffrich+maintainer:+ Hannes Saffrich <m0rphism@zankapfel.org>+copyright:+ 2016 Hannes Saffrich+category:+ System+build-type:+ Custom+cabal-version:+ >=1.10+extra-doc-files:+ doc/*.png++library+ hs-source-dirs:+ src+ default-language:+ Haskell2010+ build-depends:+ base >= 4.7 && < 5,+ containers,+ lens,+ mtl,+ transformers,+ process,+ directory+ ghc-options:+ -Wall -O2+ exposed-modules:+ DMenu+ other-modules:+ DMenu.Color+ DMenu.Options+ DMenu.Run++source-repository head+ type: git+ location: https://github.com/m0rphism/haskell-dmenu.git
binary file changed (absent → 9562 bytes)
+ src/DMenu.hs view
@@ -0,0 +1,130 @@+{-+ TODO+ - Add support for regular command line syntax config and/or env variable+-}++module DMenu (+ -- * Overview+ -- $overview++ -- * Types+ DMenuT, MonadDMenu, ProcessError,+ -- * Running @dmenu@+ run,+ -- ** Selecting a single item+ selectM, select, selectWithM, selectWith,+ -- ** Selecting multiple items+ filterM, filter, filterWithM, filterWith,++ -- * @dmenu@ Command Line Options+ Options(..),+ -- ** Lenses+ binaryPath,+ displayAtBottom,+ grabKeyboardBeforeStdin,+ caseInsensitive,+ spawnOnMonitor,+ numLines,+ prompt,+ font,+ normalBGColor,+ normalFGColor,+ selectedBGColor,+ selectedFGColor,+ printVersionAndExit,++ -- * @dmenu2@-specific Command Line Options+ Options2(..),+ -- ** Lenses+ displayNoItemsIfEmpty,+ filterMode,+ fuzzyMatching,+ tokenMatching,+ maskInputWithStar,+ ignoreStdin,+ spawnOnScreen,+ windowName,+ windowClass,+ windowOpacity,+ windowDimOpacity,+ windowDimColor,+ heightInPixels,+ underlineHeightInPixels,+ windowOffsetX,+ windowOffsetY,+ width,+ underlineColor,+ historyFile,++ -- * Color+ Color(..),++ -- * Reexports from @lens@+ (.=),++ -- * Configuration File+ -- $configFile+ ) where++import Control.Lens++import DMenu.Color+import DMenu.Options+import DMenu.Run+import Prelude hiding (filter)++{- $overview+ This module provides complete bindings to the+ <http://tools.suckless.org/dmenu/ dmenu> and+ <https://bitbucket.org/melek/dmenu2 dmenu2> command-line tools.++ <<doc/dmenu-pmount.png>>++ The @dmenu@ command line tool++ 1. takes command line 'Options' and reads a list of strings from @stdin@,+ 2. presents the list in a special overlay window, in which the user can select+ from the list via fuzzy matching, and+ 3. prints the selected string to @stdout@ or fails with exit code @1@ if the+ user hit the @ESC@ key.++ Typical uses of @dmenu@ are for example++ 1. as a program launcher by piping the program names from @PATH@ into @dmenu@+ and executing the selected program.+ 2. as an interface for killing programs by piping process information from+ @ps aux@ into @dmenu@, and running @kill -9@ on the selected process id.+ 3. as an interface for mounting devices by piping the device files from @\/dev\/@+ into @dmenu@, and running @pmount@ on the selected device (shown in the+ image above).++ @dmenu2@ is a fork of @dmenu@, which provides additional options, e.g. selecting+ multiple items at once.++ Ontop of the functionality of @dmenu@ and @dmenu2@, this library+ supports a configuration file for specifying default command line options for+ @dmenu@. See the last section for more on the configuration file.++ The simplest way to run @dmenu@ is with the 'select' function.++ Note for @stack@ users: When running programs using this library via+ @stack exec@, the program may fail to find @dmenu@ in the @PATH@.+ This problem can be solved by running the program directly without @stack@, or+ by temporarily using an absolute path for @dmenu@ in the '_binaryPath' option.+-}+{- $configFile+ The default @Options@ used by 'run', 'select', etc. can be specified+ in the @~/.haskell-dmenu@ file.++ The following shows an example @~/.haskell-dmenu@ file:++ > numLines 15+ > font FiraMono:size=11+ > caseInsensitive True+ > normalBGColor RGBColorF 0.02 0.02 0.02++ The configuration file contains one line per option.+ Each line consists of an option name and a value for the option.+ The option names are identical to the corresponding lens names.+ The values are read with @Prelude.read@ except for @Strings@ which don't need double quotes.+-}
+ src/DMenu/Color.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE UnicodeSyntax, LambdaCase #-}++module DMenu.Color (+ Color(..), showColorAsHex,+ ) where++import Numeric (showHex)++-- | Multiple representations for colors.+--+-- For example, green can be defined as+--+-- > green1 = HexColor 0x00FF00+-- > green2 = RGBColor 0 255 0+-- > green3 = RGBColorF 0 1 0+data Color+ = HexColor Int+ | RGBColor Int Int Int+ | RGBColorF Float Float Float+ deriving (Eq, Ord, Read, Show)++-- | Render a color to a hexadecimal @String@ representation. For example+--+-- >>> showColorAsHex (RGBColor 5 5 5)+-- "#050505"+showColorAsHex :: Color → String+showColorAsHex = \case+ HexColor i → "#" ++ fillLeft '0' 6 (showHex i "")+ RGBColor r g b → showColorAsHex $ HexColor $ r*256*256 + g*256 + b+ RGBColorF r g b → showColorAsHex $ RGBColor (f r) (f g) (f b)+ where+ f = floor . (* 255)+ fillLeft c i s = replicate (i - length s) c ++ s
+ src/DMenu/Options.hs view
@@ -0,0 +1,256 @@+{-# LANGUAGE UnicodeSyntax, LambdaCase #-}+{-# LANGUAGE RecordWildCards, TemplateHaskell #-}++module DMenu.Options where++import Control.Lens++import DMenu.Color++-- | Contains the binary path and command line options of dmenu.+-- The option descriptions are copied from the @dmenu@ @man@ page.+data Options = Options+ { -- | Path to the the dmenu executable file.+ -- Default looks for @dmenu@ in the @PATH@ enviroment variable.+ _binaryPath :: FilePath+ -- | @-b@; dmenu appears at the bottom of the screen.+ , _displayAtBottom :: Bool+ -- | @-f@; dmenu grabs the keyboard before reading stdin. This is faster, but will lock up X until stdin reaches end-of-file.+ , _grabKeyboardBeforeStdin :: Bool+ -- | @-i@; dmenu matches menu items case insensitively.+ , _caseInsensitive :: Bool+ -- | @-m screen@; dmenu is displayed on the monitor number supplied. Monitor numbers are starting from 0.+ , _spawnOnMonitor :: Int+ -- | @-l lines@; dmenu lists items vertically, with the given number of lines.+ , _numLines :: Int+ -- | @-p prompt@; defines the prompt to be displayed to the left of the input field.+ , _prompt :: String+ -- | @-fn font@; defines the font or font set used. eg. @\"fixed\"@ or @\"Monospace-12:normal\"@ (an xft font)+ , _font :: String+ -- | @-nb color@; defines the normal background color. @#RGB@, @#RRGGBB@, and X color names are supported.+ , _normalBGColor :: Color+ -- | @-nf color@; defines the normal foreground color.+ , _normalFGColor :: Color+ -- | @-sb color@; defines the selected background color.+ , _selectedBGColor :: Color+ -- | @-sf color@; defines the selected foreground color.+ , _selectedFGColor :: Color+ -- | @-v@; prints version information to stdout, then exits.+ , _printVersionAndExit :: Bool+ -- | Extra options only available in the dmenu2 fork.+ , _dmenu2 :: Options2+ -- | When set to @True@, the @dmenu2@ options in '_dmenu2' are ignored. This+ -- ensures compatibility with the normal @dmenu@. A user may set this flag+ -- in the configuration file.+ , _noDMenu2 :: Bool+ }++-- | Contains the command line options of @dmenu2@ which are not part of+-- @dmenu@. The @_filterMode@ option is not listed; it can be implicitly used by+-- using @DMenu.filter@ instead of @DMenu.select@. The option descriptions are+-- copied from the @dmenu2@ @man@ page.+data Options2 = Options2+ { -- | @-q@; dmenu will not show any items if the search string is empty.+ _displayNoItemsIfEmpty :: Bool+ -- | @-r@; activates filter mode. All matching items currently shown in the list will be selected, starting with the item that is highlighted and wrapping around to the beginning of the list. (/Note/: Instead of setting this flag yourself, the @dmenu@ @filter@ functions can be used instead of the @select@ functions.)+ , _filterMode :: Bool+ -- | @-z@; dmenu uses fuzzy matching. It matches items that have all characters entered, in sequence they are entered, but there may be any number of characters between matched characters. For example it takes @\"txt\"@ makes it to @\"*t*x*t\"@ glob pattern and checks if it matches.+ , _fuzzyMatching :: Bool+ -- | @-t@; dmenu uses space-separated tokens to match menu items. Using this overrides @-z@ option.+ , _tokenMatching :: Bool+ -- | @-mask@; dmenu masks input with asterisk characters (@*@).+ , _maskInputWithStar :: Bool+ -- | @-noinput@; dmenu ignores input from stdin (equivalent to: @echo | dmenu@).+ , _ignoreStdin :: Bool+ -- | @-s screen@; dmenu apears on the specified screen number. Number given corespondes to screen number in X optionsuration.+ , _spawnOnScreen :: Int+ -- | @-name name@; defines window name for dmenu. Defaults to @\"dmenu\"@.+ , _windowName :: String+ -- | @-class class@; defines window class for dmenu. Defaults to @\"Dmenu"@.+ , _windowClass :: String+ -- | @-o opacity@; defines window opacity for dmenu. Defaults to @1.0@.+ , _windowOpacity :: Double+ -- | @-dim opacity@; enables screen dimming when dmenu appers. Takes dim opacity as argument.+ , _windowDimOpacity :: Double+ -- | @-dc color@; defines color of screen dimming. Active only when @-dim@ in effect. Defautls to black (@#000000@)+ , _windowDimColor :: Color+ -- | @-h height@; defines the height of the bar in pixels.+ , _heightInPixels :: Int+ -- | @-uh height@; defines the height of the underline in pixels.+ , _underlineHeightInPixels :: Int+ -- | @-x xoffset@; defines the offset from the left border of the screen.+ , _windowOffsetX :: Int+ -- | @-y yoffset@; defines the offset from the top border of the screen.+ , _windowOffsetY :: Int+ -- | @-w width@; defines the desired menu window width.+ , _width :: Int+ -- | @-uc color@; defines the underline color.+ , _underlineColor :: Color+ -- | @-hist <histfile>@; the file to use for history+ , _historyFile :: FilePath+ }++makeLenses ''Options+makeLenses ''Options2++defOptions :: Options+defOptions = Options+ { _binaryPath = "dmenu"+ , _displayAtBottom = False+ , _grabKeyboardBeforeStdin = False+ , _caseInsensitive = False+ , _numLines = (-1)+ , _prompt = ""+ , _font = ""+ , _spawnOnMonitor = (-1)+ , _normalBGColor = HexColor (-1)+ , _normalFGColor = HexColor (-1)+ , _selectedBGColor = HexColor (-1)+ , _selectedFGColor = HexColor (-1)+ , _printVersionAndExit = False+ , _dmenu2 = defOptions2+ , _noDMenu2 = False+ }++defOptions2 :: Options2+defOptions2 = Options2+ { _filterMode = False+ , _fuzzyMatching = False+ , _displayNoItemsIfEmpty = False+ , _tokenMatching = False+ , _maskInputWithStar = False+ , _ignoreStdin = False+ , _spawnOnScreen = (-1)+ , _windowName = ""+ , _windowClass = ""+ , _windowOpacity = (-1)+ , _windowDimOpacity = (-1)+ , _windowDimColor = HexColor (-1)+ , _heightInPixels = (-1)+ , _underlineHeightInPixels = (-1)+ , _windowOffsetX = (-1)+ , _windowOffsetY = (-1)+ , _width = (-1)+ , _underlineColor = HexColor (-1)+ , _historyFile = ""+ }++optionsToArgs :: Options → [String]+optionsToArgs (Options{..}) = concat $ concat $+ [ [ [ "-b" ] | _displayAtBottom ]+ , [ [ "-f" ] | _grabKeyboardBeforeStdin ]+ , [ [ "-i" ] | _caseInsensitive ]+ , [ [ "-m", show _spawnOnMonitor ] | _spawnOnMonitor /= (-1) ]+ , [ [ "-l", show _numLines ] | _numLines /= (-1) ]+ , [ [ "-p", _prompt ] | _prompt /= "" ]+ , [ [ "-fn", _font ] | _font /= "" ]+ , [ [ "-nb", showColorAsHex _normalBGColor ] | _normalBGColor /= HexColor (-1) ]+ , [ [ "-nf", showColorAsHex _normalFGColor ] | _normalFGColor /= HexColor (-1) ]+ , [ [ "-sb", showColorAsHex _selectedBGColor ] | _selectedBGColor /= HexColor (-1) ]+ , [ [ "-sf", showColorAsHex _selectedFGColor ] | _selectedFGColor /= HexColor (-1) ]+ , [ [ "-v" ] | _printVersionAndExit ]+ ] ++ if _noDMenu2 then [] else options2ToArgs _dmenu2++options2ToArgs :: Options2 → [[[String]]]+options2ToArgs (Options2{..}) =+ [ [ [ "-q" ] | _displayNoItemsIfEmpty ]+ , [ [ "-r" ] | _filterMode ]+ , [ [ "-z" ] | _fuzzyMatching ]+ , [ [ "-t" ] | _tokenMatching ]+ , [ [ "-mask" ] | _maskInputWithStar ]+ , [ [ "-noinput" ] | _ignoreStdin ]+ , [ [ "-s", show _spawnOnScreen ] | _spawnOnScreen /= (-1) ]+ , [ [ "-name", show _windowName ] | _windowName /= "" ]+ , [ [ "-class", show _windowClass ] | _windowClass /= "" ]+ , [ [ "-o", show _windowOpacity ] | _windowOpacity /= (-1) ]+ , [ [ "-dim" ] | _windowDimOpacity /= (-1) ]+ , [ [ "-dc", showColorAsHex _windowDimColor ] | _windowDimColor /= HexColor (-1) ]+ , [ [ "-h", show _heightInPixels ] | _heightInPixels /= (-1) ]+ , [ [ "-uh", show _underlineHeightInPixels ] | _underlineHeightInPixels /= (-1) ]+ , [ [ "-x", show _windowOffsetX ] | _windowOffsetX /= (-1) ]+ , [ [ "-y", show _windowOffsetY ] | _windowOffsetY /= (-1) ]+ , [ [ "-w", show _width ] | _width /= (-1) ]+ , [ [ "-uc", showColorAsHex _underlineColor ] | _underlineColor /= HexColor (-1) ]+ , [ [ "-hist", show _historyFile ] | _historyFile /= "" ]+ ]++parseOptions :: String → Options+parseOptions = foldl f defOptions . map splitFirstWord . lines where+ f :: Options → (String, String) → Options+ f opts (cmd, args) = opts & case cmd of+ "binaryPath" → binaryPath .~ args+ "displayAtBottom" → displayAtBottom .~ read args+ "displayNoItemsIfEmpty" → dmenu2 . displayNoItemsIfEmpty .~ read args+ "grabKeyboardBeforeStdin" → grabKeyboardBeforeStdin .~ read args+ "filterMode" → dmenu2 . filterMode .~ read args+ "caseInsensitive" → caseInsensitive .~ read args+ "fuzzyMatching" → dmenu2 . fuzzyMatching .~ read args+ "tokenMatching" → dmenu2 . tokenMatching .~ read args+ "maskInputWithStar" → dmenu2 . maskInputWithStar .~ read args+ "ignoreStdin" → dmenu2 . ignoreStdin .~ read args+ "spawnOnScreen" → dmenu2 . spawnOnScreen .~ read args+ "spawnOnMonitor" → spawnOnMonitor .~ read args+ "windowName" → dmenu2 . windowName .~ args+ "windowClass" → dmenu2 . windowClass .~ args+ "windowOpacity" → dmenu2 . windowOpacity .~ read args+ "windowDimOpacity" → dmenu2 . windowDimOpacity .~ read args+ "windowDimColor" → dmenu2 . windowDimColor .~ read args+ "numLines" → numLines .~ read args+ "heightInPixels" → dmenu2 . heightInPixels .~ read args+ "underlineHeightInPixels" → dmenu2 . underlineHeightInPixels .~ read args+ "prompt" → prompt .~ args+ "font" → font .~ args+ "windowOffsetX" → dmenu2 . windowOffsetX .~ read args+ "windowOffsetY" → dmenu2 . windowOffsetY .~ read args+ "width" → dmenu2 . width .~ read args+ "normalBGColor" → normalBGColor .~ read args+ "normalFGColor" → normalFGColor .~ read args+ "selectedBGColor" → selectedBGColor .~ read args+ "selectedFGColor" → selectedFGColor .~ read args+ "underlineColor" → dmenu2 . underlineColor .~ read args+ "historyFile" → dmenu2 . historyFile .~ args+ "printVersionAndExit" → printVersionAndExit .~ read args+ "noDMenu2" → noDMenu2 .~ read args+ "" → id+ _ → error $ "Invalid command found when parsing dmenu config file: " ++ cmd++-- printOptions :: Options → String+-- printOptions Options{..} = unlines $ concat+-- [ [ "binaryPath " ++ _binaryPath | _binaryPath /= "" ]+-- , [ "displayAtBottom" | _displayAtBottom ]+-- , [ "displayNoItemsIfEmpty" | _displayNoItemsIfEmpty ]+-- , [ "grabKeyboardBeforeStdin" | _grabKeyboardBeforeStdin ]+-- , [ "filterMode" | _filterMode ]+-- , [ "caseInsensitive" | _caseInsensitive ]+-- , [ "fuzzyMatching" | _fuzzyMatching ]+-- , [ "tokenMatching" | _tokenMatching ]+-- , [ "maskInputWithStar" | _maskInputWithStar ]+-- , [ "ignoreStdin" | _ignoreStdin ]+-- , [ "spawnOnScreen " ++ show _spawnOnScreen | _spawnOnScreen /= (-1) ]+-- , [ "windowName " ++ _windowName | _windowName /= "" ]+-- , [ "windowClass " ++ _windowClass | _windowClass /= "" ]+-- , [ "windowOpacity " ++ show _windowOpacity | _windowOpacity /= (-1) ]+-- , [ "windowDimOpacity " ++ show _windowDimOpacity | _windowDimOpacity /= (-1) ]+-- , [ "windowDimColor " ++ show _windowDimColor | _windowDimColor /= HexColor (-1) ]+-- , [ "numLines " ++ show _numLines | _numLines /= (-1) ]+-- , [ "heightInPixels " ++ show _heightInPixels | _heightInPixels /= (-1) ]+-- , [ "underlineHeightInPixels " ++ show _underlineHeightInPixels | _underlineHeightInPixels /= (-1) ]+-- , [ "prompt " ++ show _prompt | _prompt /= "" ]+-- , [ "font " ++ show _font | _font /= "" ]+-- , [ "windowOffsetX " ++ show _windowOffsetX | _windowOffsetX /= (-1) ]+-- , [ "windowOffsetY " ++ show _windowOffsetY | _windowOffsetY /= (-1) ]+-- , [ "width " ++ show _width | _width /= (-1) ]+-- , [ "normalBGColor " ++ show _normalBGColor | _normalBGColor /= HexColor (-1) ]+-- , [ "normalFGColor " ++ show _normalFGColor | _normalFGColor /= HexColor (-1) ]+-- , [ "selectedBGColor " ++ show _selectedBGColor | _selectedBGColor /= HexColor (-1) ]+-- , [ "selectedFGColor " ++ show _selectedFGColor | _selectedFGColor /= HexColor (-1) ]+-- , [ "underlineColor " ++ show _underlineColor | _underlineColor /= HexColor (-1) ]+-- , [ "historyFile " ++ show _historyFile | _historyFile /= "" ]+-- , [ "printVersionAndExit" | _printVersionAndExit ]+-- ]++splitFirstWord :: String → (String, String)+splitFirstWord = go "" where+ go s [] = (s, [])+ go s (c:cs) | c `elem` [' ','\t'] = (s, dropWhile (`elem` [' ','\t']) cs)+ | otherwise = go (s++[c]) cs
+ src/DMenu/Run.hs view
@@ -0,0 +1,219 @@+{-# LANGUAGE UnicodeSyntax, LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables, ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RecordWildCards, TemplateHaskell #-}++module DMenu.Run where++import Control.Exception+import Control.Monad.State.Strict hiding (filterM)+import Control.Lens+import Data.Maybe+import System.Exit+import System.Process+import System.Directory+import Prelude hiding (filter)++import DMenu.Options++-- | A state monad transformer in which the command line options of @dmenu@ can+-- be configured.+type DMenuT = StateT Options++-- | The 'MonadIO' constraint additionally allows to spawn processes with+-- @System.Process@ in between.+type MonadDMenu m = (MonadIO m, MonadState Options m)++-- | When a spawned process fails, this type is used to represent the exit code+-- and @stderr@ output.+type ProcessError = (Int, String)++-- | Run a @StateT Options m a@ action using the command line options from the+-- config file or an empty set of options as initial state.+--+-- For example+--+-- > import qualified DMenu+-- >+-- > main :: IO ()+-- > main = DMenu.run $ do+-- > DMenu.numLines .= 10+-- > DMenu.prompt .= "run"+-- > liftIO . print =<< DMenu.selectM ["A","B","C"]+run :: MonadIO m => DMenuT m a → m a+run ma = evalStateT ma =<< readConfigOrDef =<< getDefConfigPath++getDefConfigPath :: MonadIO m => m FilePath+getDefConfigPath = (++"/.haskell-dmenu") <$> liftIO getHomeDirectory++-- | Run DMenu with the command line options from @m@ and a list of 'String's+-- from which the user should choose.+selectM+ :: MonadDMenu m+ => [String]+ -- ^ List from which the user should select.+ → m (Either ProcessError String)+ -- ^ The selection made by the user, or a 'ProcessError', if the user+ -- canceled.+selectM entries = do+ cfg ← get+ liftIO $ do+ (exitCode, sOut, sErr) ← readCreateProcessWithExitCode+ (proc (_binaryPath cfg) (optionsToArgs cfg))+ (unlines entries)+ pure $ case exitCode of+ ExitSuccess → Right $ head $ lines sOut+ ExitFailure i → Left (i, sErr)++-- | Convenience function combining 'run' and 'selectM'.+--+-- The following example has the same behavior as the example for @run@:+--+-- > import qualified DMenu+-- >+-- > main :: IO ()+-- > main = print =<< DMenu.select setOptions ["A","B","C"]+-- >+-- > setOptions :: DMenu.MonadDMenu m => m ()+-- > setOptions = do+-- > DMenu.numLines .= 10+-- > DMenu.prompt .= "run"+select+ :: MonadIO m+ => DMenuT m ()+ -- ^ @State Options@ action which changes the default command line+ -- options.+ → [String]+ -- ^ List from which the user should select.+ → m (Either ProcessError String)+ -- ^ The selection made by the user, or a 'ProcessError', if the user+ -- canceled.+select m0 entries = run $ m0 >> selectM entries++-- | Same as 'selectM', but allows the user to select from a list of arbitrary+-- elements, which have a 'String' representation.+selectWithM+ :: MonadDMenu m+ => (a → String)+ -- ^ How to display an @a@ in @dmenu@.+ → [a]+ -- ^ List from which the user should select.+ → m (Either ProcessError a)+ -- ^ The selection made by the user, or a 'ProcessError', if the user+ -- canceled.+selectWithM f xs = fmap (fromJust . flip lookup m) <$> selectM (map f xs)+ where m = [ (f x, x) | x ← xs ]++-- | Same as 'select', but allows the user to select from a list of arbitrary+-- elements, which have a 'String' representation.+--+-- For example+--+-- > import qualified DMenu+-- >+-- > main :: IO ()+-- > main = print =<< DMenu.selectWith setOptions show [1..10::Int]+-- >+-- > setOptions :: DMenu.MonadDMenu m => m ()+-- > setOptions = do+-- > DMenu.numLines .= 10+-- > DMenu.prompt .= "run"+selectWith+ :: MonadIO m+ => DMenuT m ()+ -- ^ @State Options@ action which changes the default command line+ -- options.+ → (a → String)+ -- ^ How to display an @a@ in @dmenu@.+ → [a]+ -- ^ List from which the user should select.+ → m (Either ProcessError a)+ -- ^ The selection made by the user, or a 'ProcessError', if the user+ -- canceled.+selectWith m0 f xs = run $ m0 >> selectWithM f xs++++-- | Like 'selectM' but uses the @dmenu2@ option @filterMode@, which+-- returns not only the selected item, but all items which fuzzy match the+-- input term.+filterM+ :: MonadDMenu m+ => [String]+ -- ^ List from which the user should filter.+ → m (Either ProcessError [String])+ -- ^ The selection made by the user, or a 'ProcessError', if the user+ -- canceled.+filterM entries = do+ cfg ← (dmenu2 . filterMode .~ True) <$> get+ liftIO $ do+ (exitCode, sOut, sErr) ← readCreateProcessWithExitCode+ (proc (_binaryPath cfg) (optionsToArgs cfg))+ (unlines entries)+ pure $ case exitCode of+ ExitSuccess → Right $ lines sOut+ ExitFailure i → Left (i, sErr)++-- | Like 'select' but uses the @dmenu2@ option @filterMode@, which+-- returns not only the selected item, but all items which fuzzy match the+-- input term.+filter+ :: MonadIO m+ => DMenuT m ()+ -- ^ @State Options@ action which changes the default command line+ -- options.+ → [String]+ -- ^ List from which the user should select.+ → m (Either ProcessError [String])+ -- ^ The selection made by the user, or a 'ProcessError', if the user+ -- canceled.+filter m0 entries = run $ m0 >> filterM entries++-- | Like 'selectWithM' but uses the @dmenu2@ option @filterMode@, which+-- returns not only the selected item, but all items which fuzzy match the+-- input term.+filterWithM+ :: MonadDMenu m+ => (a → String)+ -- ^ How to display an @a@ in @dmenu@.+ → [a]+ -- ^ List from which the user should select.+ → m (Either ProcessError [a])+ -- ^ The selection made by the user, or a 'ProcessError', if the user+ -- canceled.+filterWithM f xs = fmap (fmap (fromJust . flip lookup m)) <$> filterM (map f xs)+ where m = [ (f x, x) | x ← xs ]++-- | Like 'selectWith' but uses the @dmenu2@ option @filterMode@, which+-- returns not only the selected item, but all items which fuzzy match the+-- input term.+filterWith+ :: MonadIO m+ => DMenuT m ()+ -- ^ @State Options@ action which changes the default command line+ -- options.+ → (a → String)+ -- ^ How to display an @a@ in @dmenu@.+ → [a]+ -- ^ List from which the user should select.+ → m (Either ProcessError [a])+ -- ^ The selection made by the user, or a 'ProcessError', if the user+ -- canceled.+filterWith m0 f xs = run $ m0 >> filterWithM f xs+++splitFirstWord :: String → (String, String)+splitFirstWord = go "" where+ go s [] = (s, [])+ go s (c:cs) | c `elem` [' ','\t'] = (s, dropWhile (`elem` [' ','\t']) cs)+ | otherwise = go (s++[c]) cs++readFileMay :: MonadIO m => FilePath → m (Maybe String)+readFileMay path = liftIO $+ (Just <$> readFile path) `catch` (\(_ :: SomeException) → pure Nothing)++readConfigOrDef :: MonadIO m => FilePath → m Options+readConfigOrDef = fmap f . readFileMay where+ f = \case+ Nothing → defOptions+ Just content → parseOptions content