{- |
   Module      :  XMonad.Actions.Search
   Description :  Easily run Internet searches on web sites through xmonad.
   Copyright   :  (C) 2007 Gwern Branwen
   License     :  None; public domain

   Maintainer  :  <gwern0@gmail.com>
   Stability   :  unstable
   Portability :  unportable; depends on XSelection, XPrompt

   A module for easily running Internet searches on web sites through xmonad.
   Modeled after the handy Surfraw CLI search tools at <https://secure.wikimedia.org/wikipedia/en/wiki/Surfraw>.

   Additional sites welcomed. -}
module XMonad.Actions.Search (   -- * Usage
                                 -- $usage
                                 search,
                                 SearchEngine(..),
                                 searchEngine,
                                 searchEngineF,
                                 promptSearch,
                                 promptSearchBrowser,
                                 promptSearchBrowser',
                                 selectSearch,
                                 selectSearchBrowser,
                                 isPrefixOf,
                                 escape,
                                 use,
                                 intelligent,
                                 (!>),
                                 prefixAware,
                                 namedEngine,

                                 alpha,
                                 amazon,
                                 arXiv,
                                 aur,
                                 clojureDocs,
                                 codesearch,
                                 cratesIo,
                                 deb,
                                 debbts,
                                 debpts,
                                 dictionary,
                                 duckduckgo,
                                 ebay,
                                 flora,
                                 github,
                                 google,
                                 hackage,
                                 homeManager,
                                 hoogle,
                                 images,
                                 imdb,
                                 lucky,
                                 maps,
                                 mathworld,
                                 ncatlab,
                                 nixos,
                                 noogle,
                                 openstreetmap,
                                 protondb,
                                 rosettacode,
                                 rustStd,
                                 scholar,
                                 sourcehut,
                                 stackage,
                                 steam,
                                 thesaurus,
                                 vocabulary,
                                 voidpgks_x86_64,
                                 voidpgks_x86_64_musl,
                                 wayback,
                                 wikipedia,
                                 wiktionary,
                                 youtube,
                                 zbmath,
                                 multi,
                                  -- * Use case: searching with a submap
                                  -- $tip

                                  -- * Types
                                 Browser, Site, Query, Name, Search
                          ) where

import           Codec.Binary.UTF8.String (encode)
import           Text.Printf
import           XMonad                   (X (), liftIO)
import           XMonad.Prompt            (XPConfig (), XPrompt (showXPrompt, nextCompletion, commandToComplete),
                                           getNextCompletion,
                                           historyCompletionP, mkXPrompt)
import           XMonad.Prelude           (isAlphaNum, isAscii, isPrefixOf)
import           XMonad.Prompt.Shell      (getBrowser)
import           XMonad.Util.Run          (safeSpawn)
import           XMonad.Util.XSelection   (getSelection)


{- $usage

   This module is intended to allow easy access to databases on the
   Internet through xmonad's interface. The idea is that one wants to
   run a search but the query string and the browser to use must come
   from somewhere. There are two places the query string can come from
   - the user can type it into a prompt which pops up, or the query
   could be available already in the X Windows copy\/paste buffer
   (perhaps you just highlighted the string of interest).

   Thus, there are two main functions: 'promptSearch', and
   'selectSearch' (implemented using the more primitive 'search'). To
   each of these is passed an engine function; this is a function that
   knows how to search a particular site.

   For example, the 'google' function knows how to search Google, and
   so on. You pass 'promptSearch' and 'selectSearch' the engine you
   want, the browser you want, and anything special they might need;
   this whole line is then bound to a key of you choosing in your
   xmonad.hs. For specific examples, see each function.  This module
   is easily extended to new sites by using 'searchEngine'.

   The currently available search engines are:

* 'alpha' -- Wolfram|Alpha query.

* 'amazon' -- Amazon keyword search.

* 'arXiv' -- Open-access preprint archive.

* 'aur' -- Arch User Repository.

* 'clojureDocs' -- Documentation and examples repository for Clojure.

* 'codesearch' -- Google Labs Code Search search.

* 'cratesIo' -- Rust crate registry.

* 'deb'    -- Debian package search.

* 'debbts' -- Debian Bug Tracking System.

* 'debpts'  -- Debian Package Tracking System.

* 'dictionary' -- dictionary.reference.com search.

* 'duckduckgo' -- DuckDuckGo search engine.

* 'ebay' -- Ebay keyword search.

* 'flora' -- Prettier Haskell package database.

* 'github' -- GitHub keyword search.

* 'google' -- basic Google search.

* 'hackage' -- Hackage, the Haskell package database.

* 'homeManager' -- Search Nix's home-manager's options.

* 'hoogle' -- Hoogle, the Haskell libraries API search engine.

* 'images' -- Google images.

* 'imdb'   -- the Internet Movie Database.

* 'lucky' -- Google "I'm feeling lucky" search.

* 'maps'   -- Google maps.

* 'mathworld' -- Wolfram MathWorld search.

* 'ncatlab' -- Higer Algebra, Homotopy and Category Theory Wiki.

* 'nixos' -- Search NixOS packages and options.

* 'noogle' -- 'hoogle'-like Nix API search engine.

* 'openstreetmap' -- OpenStreetMap free wiki world map.

* 'protondb' -- Steam Proton Game Database.

* 'rosettacode' -- Programming chrestomathy wiki.

* 'rustStd' -- Rust standard library documentation.

* 'scholar' -- Google scholar academic search.

* 'sourcehut' -- Sourcehut projects search.

* 'stackage' -- Stackage, An alternative Haskell libraries API search engine.

* 'steam' -- Steam games search.

* 'thesaurus' -- thesaurus.com search.

* 'vocabulary' -- Dictionary search.

* 'voidpgks_x86_64' -- Void Linux packages search for @x86_64@.

* 'voidpgks_x86_64_musl' -- Void Linux packages search for @x86_64-musl@.

* 'wayback' -- the Wayback Machine.

* 'wikipedia' -- basic Wikipedia search.

* 'wiktionary' -- Wiktionary search.

* 'youtube' -- Youtube video search.

* 'zbmath' -- Open alternative to MathSciNet.

* 'multi' -- Search based on the prefix. \"amazon:Potter\" will use amazon, etc. With no prefix searches google.

Feel free to add more! -}

{- $tip

In combination with "XMonad.Actions.Submap" you can create a powerful
and easy way to search without adding a whole bunch of bindings.

First import the necessary modules:

> import qualified XMonad.Prompt         as P
> import qualified XMonad.Actions.Submap as SM
> import qualified XMonad.Actions.Search as S

Then add the following to your key bindings:

> ...
> -- Search commands
> , ((modm, xK_s), SM.submap $ searchEngineMap $ S.promptSearch P.def)
> , ((modm .|. shiftMask, xK_s), SM.submap $ searchEngineMap $ S.selectSearch)
>
> ...
>
> searchEngineMap method = M.fromList $
>       [ ((0, xK_g), method S.google)
>       , ((0, xK_h), method S.hoogle)
>       , ((0, xK_w), method S.wikipedia)
>       ]

Or in combination with XMonad.Util.EZConfig:

> ...
> ] -- end of regular keybindings
> -- Search commands
> ++ [("M-s " ++ k, S.promptSearch P.def f) | (k,f) <- searchList ]
> ++ [("M-S-s " ++ k, S.selectSearch f) | (k,f) <- searchList ]
>
> ...
>
> searchList :: [(String, S.SearchEngine)]
> searchList = [ ("g", S.google)
>              , ("h", S.hoogle)
>              , ("w", S.wikipedia)
>              ]

Make sure to set firefox to open new pages in a new window instead of
in a new tab: @Firefox -> Edit -> Preferences -> Tabs -> New pages
should be opened in...@

Now /mod-s/ + /g/\//h/\//w/ prompts you for a search string, then
opens a new firefox window that performs the search on Google, Hoogle
or Wikipedia respectively.

If you select something in whatever application and hit /mod-shift-s/ +
/g/\//h/\//w/ it will search the selected string with the specified
engine.

Happy searching! -}

-- | A customized prompt indicating we are searching, and the name of the site.
newtype Search = Search Name
instance XPrompt Search where
    showXPrompt :: Search -> String
showXPrompt (Search String
name)= String
"Search [" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"]: "
    nextCompletion :: Search -> String -> [String] -> String
nextCompletion Search
_ = String -> [String] -> String
getNextCompletion
    commandToComplete :: Search -> String -> String
commandToComplete Search
_ String
c = String
c

-- | Escape the search string so search engines understand it. Only
-- digits and ASCII letters are not encoded. All non ASCII characters
-- which are encoded as UTF8
escape :: String -> String
escape :: String -> String
escape = (Char -> String) -> String -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Char -> String
escapeURIChar

escapeURIChar :: Char -> String
escapeURIChar :: Char -> String
escapeURIChar Char
c | Char -> Bool
isAscii Char
c Bool -> Bool -> Bool
&& Char -> Bool
isAlphaNum Char
c = [Char
c]
                | Bool
otherwise                 = (Word8 -> String) -> [Word8] -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (String -> Word8 -> String
forall r. PrintfType r => String -> r
printf String
"%%%02X") ([Word8] -> String) -> [Word8] -> String
forall a b. (a -> b) -> a -> b
$ String -> [Word8]
encode [Char
c]

type Browser      = FilePath
type Query        = String
type Site         = String -> String
type Name         = String
data SearchEngine = SearchEngine Name Site

-- | Given an already defined search engine, extracts its transformation
--   function, making it easy to create compound search engines.
--   For an instance you can use @use google@ to get a function which
--   makes the same transformation as the google search engine would.
use :: SearchEngine -> Site
use :: SearchEngine -> String -> String
use (SearchEngine String
_ String -> String
engine) = String -> String
engine

-- | Given a browser, a search engine's transformation function, and a search term, perform the
--   requested search in the browser.
search :: Browser -> Site -> Query -> X ()
search :: String -> (String -> String) -> String -> X ()
search String
browser String -> String
site String
query = String -> [String] -> X ()
forall (m :: * -> *). MonadIO m => String -> [String] -> m ()
safeSpawn String
browser [String -> String
site String
query]

{- | Given a base URL, create the 'SearchEngine' that escapes the query and
   appends it to the base. You can easily define a new engine locally using
   exported functions without needing to modify "XMonad.Actions.Search":

> myNewEngine = searchEngine "site" "https://site.com/search="

   The important thing is that the site has a interface which accepts the escaped query
   string as part of the URL. Alas, the exact URL to feed searchEngine varies
   from site to site, often considerably, so there\'s no general way to cover this.

   Generally, examining the resultant URL of a search will allow you to reverse-engineer
   it if you can't find the necessary URL already described in other projects such as Surfraw. -}
searchEngine :: Name -> String -> SearchEngine
searchEngine :: String -> String -> SearchEngine
searchEngine String
name String
site = String -> (String -> String) -> SearchEngine
searchEngineF String
name (\String
s -> String
site String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
escape String
s)

{- | If your search engine is more complex than this (you may want to identify
   the kind of input and make the search URL dependent on the input or put the query
   inside of a URL instead of in the end) you can use the alternative 'searchEngineF' function.

> searchFunc :: String -> String
> searchFunc s | "wiki:"    `isPrefixOf` s = "https://en.wikipedia.org/wiki/" ++ (escape $ drop 1 $ snd $ break (==':') s)
>              | "https://" `isPrefixOf` s = s
>              | otherwise                 = (use google) s
> myNewEngine = searchEngineF "mymulti" searchFunc

   @searchFunc@ here searches for a word in wikipedia if it has a prefix
   of \"wiki:\" (you can use the 'escape' function to escape any forbidden characters), opens an address
   directly if it starts with \"https:\/\/\" and otherwise uses the provided google search engine.
   You can use other engines inside of your own through the 'use' function as shown above to make
   complex searches.

   The user input will be automatically escaped in search engines created with 'searchEngine',
   'searchEngineF', however, completely depends on the transformation function passed to it. -}
searchEngineF :: Name -> Site -> SearchEngine
searchEngineF :: String -> (String -> String) -> SearchEngine
searchEngineF = String -> (String -> String) -> SearchEngine
SearchEngine

-- The engines.
alpha, amazon, arXiv, aur, clojureDocs, codesearch, cratesIo, deb, debbts, debpts, dictionary, duckduckgo, ebay, flora,
  github, google, hackage, homeManager, hoogle, images, imdb, lucky, maps, mathworld, ncatlab, nixos, noogle, openstreetmap, protondb, 
  rosettacode, rustStd, scholar, sourcehut, stackage, steam, thesaurus, vocabulary, voidpgks_x86_64, voidpgks_x86_64_musl, wayback, 
  wikipedia, wiktionary, youtube, zbmath :: SearchEngine
alpha :: SearchEngine
alpha         = String -> String -> SearchEngine
searchEngine String
"alpha"         String
"https://www.wolframalpha.com/input/?i="
amazon :: SearchEngine
amazon        = String -> String -> SearchEngine
searchEngine String
"amazon"        String
"https://www.amazon.com/s/ref=nb_sb_noss_2?url=search-alias%3Daps&field-keywords="
arXiv :: SearchEngine
arXiv         = String -> (String -> String) -> SearchEngine
searchEngineF String
"arXiv"        (\String
s -> String
"https://arxiv.org/search/?query=" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
s String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"&searchtype=all")
aur :: SearchEngine
aur           = String -> String -> SearchEngine
searchEngine String
"aur"           String
"https://aur.archlinux.org/packages?&K="
clojureDocs :: SearchEngine
clojureDocs   = String -> String -> SearchEngine
searchEngine String
"clojureDocs"   String
"https://clojuredocs.org/search?q="
codesearch :: SearchEngine
codesearch    = String -> String -> SearchEngine
searchEngine String
"codesearch"    String
"https://developers.google.com/s/results/code-search?q="
cratesIo :: SearchEngine
cratesIo      = String -> String -> SearchEngine
searchEngine  String
"cratesIo"     String
"https://crates.io/search?q="
deb :: SearchEngine
deb           = String -> String -> SearchEngine
searchEngine String
"deb"           String
"https://packages.debian.org/"
debbts :: SearchEngine
debbts        = String -> String -> SearchEngine
searchEngine String
"debbts"        String
"https://bugs.debian.org/"
debpts :: SearchEngine
debpts        = String -> String -> SearchEngine
searchEngine String
"debpts"        String
"https://packages.qa.debian.org/"
dictionary :: SearchEngine
dictionary    = String -> String -> SearchEngine
searchEngine String
"dict"          String
"https://dictionary.reference.com/browse/"
duckduckgo :: SearchEngine
duckduckgo    = String -> String -> SearchEngine
searchEngine String
"duckduckgo"    String
"https://duckduckgo.com/?t=lm&q="
ebay :: SearchEngine
ebay          = String -> String -> SearchEngine
searchEngine String
"ebay"          String
"https://www.ebay.com/sch/i.html?_nkw="
flora :: SearchEngine
flora         = String -> String -> SearchEngine
searchEngine String
"flora"         String
"https://flora.pm/search?q="
github :: SearchEngine
github        = String -> String -> SearchEngine
searchEngine String
"github"        String
"https://github.com/search?q="
google :: SearchEngine
google        = String -> String -> SearchEngine
searchEngine String
"google"        String
"https://www.google.com/search?q="
hackage :: SearchEngine
hackage       = String -> String -> SearchEngine
searchEngine String
"hackage"       String
"https://hackage.haskell.org/package/"
homeManager :: SearchEngine
homeManager   = String -> String -> SearchEngine
searchEngine String
"homeManager"   String
"https://mipmip.github.io/home-manager-option-search/?query="
hoogle :: SearchEngine
hoogle        = String -> String -> SearchEngine
searchEngine String
"hoogle"        String
"https://hoogle.haskell.org/?hoogle="
images :: SearchEngine
images        = String -> String -> SearchEngine
searchEngine String
"images"        String
"https://images.google.fr/images?q="
imdb :: SearchEngine
imdb          = String -> String -> SearchEngine
searchEngine String
"imdb"          String
"https://www.imdb.com/find?s=all&q="
lucky :: SearchEngine
lucky         = String -> String -> SearchEngine
searchEngine String
"lucky"         String
"https://www.google.com/search?btnI&q="
maps :: SearchEngine
maps          = String -> String -> SearchEngine
searchEngine String
"maps"          String
"https://maps.google.com/maps?q="
mathworld :: SearchEngine
mathworld     = String -> String -> SearchEngine
searchEngine String
"mathworld"     String
"https://mathworld.wolfram.com/search/?query="
ncatlab :: SearchEngine
ncatlab       = String -> String -> SearchEngine
searchEngine String
"ncatlab"       String
"https://ncatlab.org/nlab/search?query="
nixos :: SearchEngine
nixos         = String -> String -> SearchEngine
searchEngine String
"nixos"         String
"https://search.nixos.org/packages?channel=unstable&from=0&size=200&sort=relevance&type=packages&query="
noogle :: SearchEngine
noogle        = String -> (String -> String) -> SearchEngine
searchEngineF String
"noogle"       (\String
s -> String
"https://noogle.dev/?search=" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
s String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"&page=1&to=any&from=any")
openstreetmap :: SearchEngine
openstreetmap = String -> String -> SearchEngine
searchEngine String
"openstreetmap" String
"https://www.openstreetmap.org/search?query="
protondb :: SearchEngine
protondb      = String -> String -> SearchEngine
searchEngine String
"protondb"      String
"https://www.protondb.com/search?q="
rosettacode :: SearchEngine
rosettacode   = String -> String -> SearchEngine
searchEngine String
"rosettacode"   String
"https://rosettacode.org/w/index.php?search="
rustStd :: SearchEngine
rustStd       = String -> String -> SearchEngine
searchEngine String
"rustStd"       String
"https://doc.rust-lang.org/std/index.html?search="
scholar :: SearchEngine
scholar       = String -> String -> SearchEngine
searchEngine String
"scholar"       String
"https://scholar.google.com/scholar?q="
sourcehut :: SearchEngine
sourcehut     = String -> String -> SearchEngine
searchEngine String
"sourcehut"     String
"https://sr.ht/projects?search="
stackage :: SearchEngine
stackage      = String -> String -> SearchEngine
searchEngine String
"stackage"      String
"https://www.stackage.org/lts/hoogle?q="
steam :: SearchEngine
steam         = String -> String -> SearchEngine
searchEngine String
"steam"         String
"https://store.steampowered.com/search/?term="
thesaurus :: SearchEngine
thesaurus     = String -> String -> SearchEngine
searchEngine String
"thesaurus"     String
"https://thesaurus.com/browse/"
vocabulary :: SearchEngine
vocabulary    = String -> String -> SearchEngine
searchEngine String
"vocabulary"    String
"https://www.vocabulary.com/search?q="
voidpgks_x86_64 :: SearchEngine
voidpgks_x86_64      = String -> String -> SearchEngine
searchEngine String
"voidpackages" String
"https://voidlinux.org/packages/?arch=x86_64&q="
voidpgks_x86_64_musl :: SearchEngine
voidpgks_x86_64_musl = String -> String -> SearchEngine
searchEngine String
"voidpackages" String
"https://voidlinux.org/packages/?arch=x86_64-musl&q="
wayback :: SearchEngine
wayback       = String -> (String -> String) -> SearchEngine
searchEngineF String
"wayback"      (String
"https://web.archive.org/web/*/"String -> String -> String
forall a. [a] -> [a] -> [a]
++)
wikipedia :: SearchEngine
wikipedia     = String -> String -> SearchEngine
searchEngine String
"wiki"          String
"https://en.wikipedia.org/wiki/Special:Search?go=Go&search="
wiktionary :: SearchEngine
wiktionary    = String -> String -> SearchEngine
searchEngine String
"wikt"          String
"https://en.wiktionary.org/wiki/Special:Search?go=Go&search="
youtube :: SearchEngine
youtube       = String -> String -> SearchEngine
searchEngine String
"youtube"       String
"https://www.youtube.com/results?search_type=search_videos&search_query="
zbmath :: SearchEngine
zbmath        = String -> String -> SearchEngine
searchEngine String
"zbmath"        String
"https://zbmath.org/?q="

multi :: SearchEngine
multi :: SearchEngine
multi = String -> SearchEngine -> SearchEngine
namedEngine String
"multi" (SearchEngine -> SearchEngine) -> SearchEngine -> SearchEngine
forall a b. (a -> b) -> a -> b
$ (SearchEngine -> SearchEngine -> SearchEngine)
-> [SearchEngine] -> SearchEngine
forall a. (a -> a -> a) -> [a] -> a
forall (t :: * -> *) a. Foldable t => (a -> a -> a) -> t a -> a
foldr1 SearchEngine -> SearchEngine -> SearchEngine
(!>) [SearchEngine
alpha, SearchEngine
amazon, SearchEngine
aur, SearchEngine
codesearch, SearchEngine
deb, SearchEngine
debbts, SearchEngine
debpts, SearchEngine
dictionary, SearchEngine
duckduckgo, SearchEngine
ebay, SearchEngine
flora, SearchEngine
github, SearchEngine
hackage, SearchEngine
hoogle, SearchEngine
images, SearchEngine
imdb, SearchEngine
lucky, SearchEngine
maps, SearchEngine
mathworld, SearchEngine
ncatlab, SearchEngine
openstreetmap, SearchEngine
protondb, SearchEngine
rosettacode, SearchEngine
scholar, SearchEngine
sourcehut, SearchEngine
stackage, SearchEngine
steam, SearchEngine
thesaurus, SearchEngine
vocabulary, SearchEngine
voidpgks_x86_64, SearchEngine
voidpgks_x86_64_musl, SearchEngine
wayback, SearchEngine
wikipedia, SearchEngine
wiktionary, SearchEngine
youtube, SearchEngine -> SearchEngine
prefixAware SearchEngine
google]

{- | This function wraps up a search engine and creates a new one, which works
   like the argument, but goes directly to a URL if one is given rather than
   searching.

> myIntelligentGoogleEngine = intelligent google

   Now if you search for https:\/\/xmonad.org it will directly open in your browser-}
intelligent :: SearchEngine -> SearchEngine
intelligent :: SearchEngine -> SearchEngine
intelligent (SearchEngine String
name String -> String
site) = String -> (String -> String) -> SearchEngine
searchEngineF String
name (\String
s -> if (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
':') String
s String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"http", String
"https", String
"ftp"] then String
s else String -> String
site String
s)

-- | > removeColonPrefix "foo://bar" ~> "//bar"
-- > removeColonPrefix "foo//bar" ~> "foo//bar"
removeColonPrefix :: String -> String
removeColonPrefix :: String -> String
removeColonPrefix String
s = if Char
':' Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
s then Int -> String -> String
forall a. Int -> [a] -> [a]
drop Int
1 (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char
':' Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/=) String
s else String
s

{- | Connects a few search engines into one. If the search engines\' names are
   \"s1\", \"s2\" and \"s3\", then the resulting engine will use s1 if the query
   is @s1:word@, s2 if you type @s2:word@ and s3 in all other cases.

   Example:

> multiEngine = intelligent (wikipedia !> mathworld !> (prefixAware google))

  Now if you type \"wiki:Haskell\" it will search for \"Haskell\" in Wikipedia,
  \"mathworld:integral\" will search mathworld, and everything else will fall back to
  google. The use of intelligent will make sure that URLs are opened directly. -}
(!>) :: SearchEngine -> SearchEngine -> SearchEngine
(SearchEngine String
name1 String -> String
site1) !> :: SearchEngine -> SearchEngine -> SearchEngine
!> (SearchEngine String
name2 String -> String
site2) = String -> (String -> String) -> SearchEngine
searchEngineF (String
name1 String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"/" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name2) (\String
s -> if (String
name1String -> String -> String
forall a. [a] -> [a] -> [a]
++String
":") String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s then String -> String
site1 (String -> String
removeColonPrefix String
s) else String -> String
site2 String
s)
infixr 6 !>

{- | Makes a search engine prefix-aware. Especially useful together with '!>'.
   It will automatically remove the prefix from a query so that you don\'t end
     up searching for google:xmonad if google is your fallback engine and you
     explicitly add the prefix. -}
prefixAware :: SearchEngine -> SearchEngine
prefixAware :: SearchEngine -> SearchEngine
prefixAware (SearchEngine String
name String -> String
site) = String -> (String -> String) -> SearchEngine
SearchEngine String
name (\String
s -> if (String
nameString -> String -> String
forall a. [a] -> [a] -> [a]
++String
":") String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s then String -> String
site (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String -> String
removeColonPrefix String
s else String -> String
site String
s)

{- | Changes search engine's name -}
namedEngine :: Name -> SearchEngine -> SearchEngine
namedEngine :: String -> SearchEngine -> SearchEngine
namedEngine String
name (SearchEngine String
_ String -> String
site) = String -> (String -> String) -> SearchEngine
searchEngineF String
name String -> String
site

{- | Like 'search', but for use with the output from a Prompt; it grabs the
   Prompt's result, passes it to a given searchEngine and opens it in a given
   browser. -}
promptSearchBrowser :: XPConfig -> Browser -> SearchEngine -> X ()
promptSearchBrowser :: XPConfig -> String -> SearchEngine -> X ()
promptSearchBrowser XPConfig
config String
browser (SearchEngine String
name String -> String
site) = do
    ComplFunction
hc <- XPConfig -> (String -> Bool) -> X ComplFunction
historyCompletionP XPConfig
config (String
"Search [" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf`)
    Search -> XPConfig -> ComplFunction -> (String -> X ()) -> X ()
forall p.
XPrompt p =>
p -> XPConfig -> ComplFunction -> (String -> X ()) -> X ()
mkXPrompt (String -> Search
Search String
name) XPConfig
config ComplFunction
hc ((String -> X ()) -> X ()) -> (String -> X ()) -> X ()
forall a b. (a -> b) -> a -> b
$ String -> (String -> String) -> String -> X ()
search String
browser String -> String
site

{- | Like 'promptSearchBrowser', but only suggest previous searches for the
   given 'SearchEngine' in the prompt. -}
promptSearchBrowser' :: XPConfig -> Browser -> SearchEngine -> X ()
promptSearchBrowser' :: XPConfig -> String -> SearchEngine -> X ()
promptSearchBrowser' XPConfig
config String
browser (SearchEngine String
name String -> String
site) = do
    ComplFunction
hc <- XPConfig -> (String -> Bool) -> X ComplFunction
historyCompletionP XPConfig
config (String
searchName String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf`)
    Search -> XPConfig -> ComplFunction -> (String -> X ()) -> X ()
forall p.
XPrompt p =>
p -> XPConfig -> ComplFunction -> (String -> X ()) -> X ()
mkXPrompt (String -> Search
Search String
name) XPConfig
config ComplFunction
hc ((String -> X ()) -> X ()) -> (String -> X ()) -> X ()
forall a b. (a -> b) -> a -> b
$ String -> (String -> String) -> String -> X ()
search String
browser String -> String
site
  where
    searchName :: String
searchName = Search -> String
forall t. XPrompt t => t -> String
showXPrompt (String -> Search
Search String
name)

{- | Like 'search', but in this case, the string is not specified but grabbed
 from the user's response to a prompt. Example:

> , ((modm, xK_g), promptSearch greenXPConfig google)

   This specializes "promptSearchBrowser" by supplying the browser argument as
   supplied by 'getBrowser' from "XMonad.Prompt.Shell". -}
promptSearch :: XPConfig -> SearchEngine -> X ()
promptSearch :: XPConfig -> SearchEngine -> X ()
promptSearch XPConfig
config SearchEngine
engine = IO String -> X String
forall a. IO a -> X a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO String
getBrowser X String -> (String -> X ()) -> X ()
forall a b. X a -> (a -> X b) -> X b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \ String
browser -> XPConfig -> String -> SearchEngine -> X ()
promptSearchBrowser XPConfig
config String
browser SearchEngine
engine

-- | Like 'search', but for use with the X selection; it grabs the selection,
--   passes it to a given searchEngine and opens it in a given browser.
selectSearchBrowser :: Browser -> SearchEngine -> X ()
selectSearchBrowser :: String -> SearchEngine -> X ()
selectSearchBrowser String
browser (SearchEngine String
_ String -> String
site) = String -> (String -> String) -> String -> X ()
search String
browser String -> String
site (String -> X ()) -> X String -> X ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< X String
forall (m :: * -> *). MonadIO m => m String
getSelection

{- | Like 'search', but for use with the X selection; it grabs the selection,
   passes it to a given searchEngine and opens it in the default browser . Example:

> , ((modm .|. shiftMask, xK_g), selectSearch google)

   This specializes "selectSearchBrowser" by supplying the browser argument as
   supplied by 'getBrowser' from "XMonad.Prompt.Shell". -}
selectSearch :: SearchEngine -> X ()
selectSearch :: SearchEngine -> X ()
selectSearch SearchEngine
engine = IO String -> X String
forall a. IO a -> X a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO String
getBrowser X String -> (String -> X ()) -> X ()
forall a b. X a -> (a -> X b) -> X b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \String
browser -> String -> SearchEngine -> X ()
selectSearchBrowser String
browser SearchEngine
engine