1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
{-# OPTIONS_GHC -Wall #-}
-----------------------------------------------------------------------------
-- |
-- Module : XMonad.Prompt.Man
-- Copyright : (c) 2007 Valery V. Vorotyntsev
-- License : BSD3-style (see LICENSE)
--
-- Maintainer : valery.vv@gmail.com
-- Stability : unstable
-- Portability : non-portable (uses \"manpath\" and \"bash\")
--
-- A manual page prompt for XMonad window manager.
--
-- TODO
--
-- * narrow completions by section number, if the one is specified
-- (like @\/etc\/bash_completion@ does)
--
-- * test with QuickCheck
-----------------------------------------------------------------------------
module XMonad.Prompt.Man (
-- * Usage
-- $usage
manPrompt
, getCommandOutput
) where
import XMonad
import XMonad.Prompt
import XMonad.Util.Run
import XMonad.Prompt.Shell (split)
import System.Directory
import System.Process
import System.IO
import qualified Control.Exception as E
import Control.Monad
import Data.List
import Data.Maybe
-- $usage
-- 1. In Config.hs add:
--
-- > import XMonad.Prompt.ManPrompt
--
-- 2. In your keybindings add something like:
--
-- > , ((modMask, xK_F1), manPrompt defaultXPConfig) -- mod-f1 %! Query for manual page to be displayed
-- %import XMonad.Prompt.XPrompt
-- %import XMonad.Prompt.ManPrompt
-- %keybind , ((modMask, xK_F1), manPrompt defaultXPConfig)
data Man = Man
instance XPrompt Man where
showXPrompt Man = "Manual page: "
-- | Query for manual page to be displayed.
manPrompt :: XPConfig -> X ()
manPrompt c = mkXPrompt Man c manCompl $ runInTerm . (++) "man "
manCompl :: String -> IO [String]
manCompl str | '/' `elem` str = do
-- XXX It may be better to use readline instead of bash's compgen...
lines `fmap` getCommandOutput ("bash -c 'compgen -A file " ++ str ++ "'")
| otherwise = do
mp <- getCommandOutput "manpath -g 2>/dev/null" `E.catch` \_ -> return []
let sects = ["man" ++ show n | n <- [1..9 :: Int]]
dirs = [d ++ "/" ++ s | d <- split ':' mp, s <- sects]
stripExt = reverse . drop 1 . dropWhile (/= '.') . reverse
mans <- forM dirs $ \d -> do
exists <- doesDirectoryExist d
if exists
then map (stripExt . stripSuffixes [".gz", ".bz2"]) `fmap`
getDirectoryContents d
else return []
mkComplFunFromList (uniqSort $ concat mans) str
-- | Run a command using shell and return its output.
--
-- XXX merge with 'Run.runProcessWithInput'?
--
-- * update documentation of the latter (there is no 'Maybe' in result)
--
-- * ask \"gurus\" whether @evaluate (length ...)@ approach is
-- better\/more idiomatic
getCommandOutput :: String -> IO String
getCommandOutput s = do
(pin, pout, perr, ph) <- runInteractiveCommand s
hClose pin
output <- hGetContents pout
E.evaluate (length output)
hClose perr
waitForProcess ph
return output
stripSuffixes :: Eq a => [[a]] -> [a] -> [a]
stripSuffixes sufs fn =
head . catMaybes $ map (flip rstrip fn) sufs ++ [Just fn]
rstrip :: Eq a => [a] -> [a] -> Maybe [a]
rstrip suf lst
| suf `isSuffixOf` lst = Just $ take (length lst - length suf) lst
| otherwise = Nothing
|