{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
{- |
Module      :  Neovim.User.Choice
Description :  Ask the user for an answer
Copyright   :  (c) Sebastian Witte
License     :  Apache-2.0

Maintainer  :  woozletoff@gmail.com
Stability   :  experimental
Portability :  GHC

-}
module Neovim.User.Choice
    where

import           Neovim
import           Neovim.API.String

import           Data.Char (toLower)
import           Data.List (isPrefixOf)


-- | Call @inputlist()@ on the neovim side and ask the user for a choice. This
-- function returns 'Nothing' if the user input was invalid or 'Just' the chosen
-- element. The elements are rendered via 'Pretty'.
oneOf :: [String] -> Neovim env (Maybe String)
oneOf :: [String] -> Neovim env (Maybe String)
oneOf [String]
cs = (Int -> String) -> Maybe Int -> Maybe String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Int
i -> [String]
cs [String] -> Int -> String
forall a. [a] -> Int -> a
!! (Int
iInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1)) (Maybe Int -> Maybe String)
-> Neovim env (Maybe Int) -> Neovim env (Maybe String)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Object] -> Neovim env (Maybe Int)
forall env. [Object] -> Neovim env (Maybe Int)
askForIndex ((String -> Int -> Object) -> [String] -> [Int] -> [Object]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith String -> Int -> Object
mkChoice [String]
cs [Int
1..])
  where
    mkChoice :: String -> Int -> Object
    mkChoice :: String -> Int -> Object
mkChoice String
c Int
i = String -> Object
forall o. NvimObject o => o -> Object
toObject (String -> Object) -> String -> Object
forall a b. (a -> b) -> a -> b
$ Int -> String
forall a. Show a => a -> String
show Int
i String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
". " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
c


-- | Ask user for a choice and 'Maybe' return the index of that choice
-- (1-based).
askForIndex :: [Object] -> Neovim env (Maybe Int)
askForIndex :: [Object] -> Neovim env (Maybe Int)
askForIndex [Object]
cs = String -> [Object] -> forall env. Neovim env Object
vim_call_function String
"inputlist" [[Object] -> Object
ObjectArray [Object]
cs] Neovim env Object
-> (Object -> Neovim env (Maybe Int)) -> Neovim env (Maybe Int)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Object
a -> case Object -> Either (Doc AnsiStyle) Int
forall o. NvimObject o => Object -> Either (Doc AnsiStyle) o
fromObject Object
a of
        Right Int
i | Int
i Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
1 Bool -> Bool -> Bool
&& Int
i Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= [Object] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Object]
cs ->
            Maybe Int -> Neovim env (Maybe Int)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe Int -> Neovim env (Maybe Int))
-> Maybe Int -> Neovim env (Maybe Int)
forall a b. (a -> b) -> a -> b
$ Int -> Maybe Int
forall a. a -> Maybe a
Just Int
i

        Right Int
_ ->
            Maybe Int -> Neovim env (Maybe Int)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe Int
forall a. Maybe a
Nothing

        Left Doc AnsiStyle
e ->
            Doc AnsiStyle -> Neovim env (Maybe Int)
forall env a. Doc AnsiStyle -> Neovim env a
err Doc AnsiStyle
e


-- | Same as 'oneOf' only that @a@ is constrained by 'Show' insted of 'Pretty'.
oneOfS :: Show a => [a] -> Neovim env (Maybe a)
oneOfS :: [a] -> Neovim env (Maybe a)
oneOfS [a]
cs = (Int -> a) -> Maybe Int -> Maybe a
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Int
i -> [a]
cs [a] -> Int -> a
forall a. [a] -> Int -> a
!! (Int
iInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
1)) (Maybe Int -> Maybe a)
-> Neovim env (Maybe Int) -> Neovim env (Maybe a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Object] -> Neovim env (Maybe Int)
forall env. [Object] -> Neovim env (Maybe Int)
askForIndex ((a -> Int -> Object) -> [a] -> [Int] -> [Object]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith a -> Int -> Object
forall a. Show a => a -> Int -> Object
mkChoice [a]
cs [Int
1..])
  where
    mkChoice :: a -> Int -> Object
mkChoice a
c Int
i = String -> Object
forall o. NvimObject o => o -> Object
toObject (String -> Object) -> String -> Object
forall a b. (a -> b) -> a -> b
$ Int -> String
forall a. Show a => a -> String
show (Int
i :: Int) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
". " String -> String -> String
forall a. [a] -> [a] -> [a]
++ a -> String
forall a. Show a => a -> String
show a
c


-- | Open @inputdialog@s inside neovim until the user has successfully typed any
-- prefix of @yes@ or @no@ or alternatively aborted the dialog. Defaults to
-- @yes@ for the empty input.
yesOrNo :: String -- ^ Question to the user
        -> Neovim env Bool
yesOrNo :: String -> Neovim env Bool
yesOrNo String
message = do
    Object
spec <- String -> [Object] -> forall env. Neovim env Object
vim_call_function
                String
"inputdialog" ([Object] -> forall env. Neovim env Object)
-> [Object] -> forall env. Neovim env Object
forall a b. (a -> b) -> a -> b
$ (String
message String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" (Y/n) ")
                    String -> [Object] -> [Object]
forall o. NvimObject o => o -> [Object] -> [Object]
+: (String
"" :: String) String -> [Object] -> [Object]
forall o. NvimObject o => o -> [Object] -> [Object]
+: (String
"no" :: String) String -> [Object] -> [Object]
forall o. NvimObject o => o -> [Object] -> [Object]
+: []
    case Object -> Either (Doc AnsiStyle) String
forall o. NvimObject o => Object -> Either (Doc AnsiStyle) o
fromObject Object
spec of
        Right String
s | (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower String
s String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
"yes" ->
            Bool -> Neovim env Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True

        Right String
s | (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower String
s String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
"no" ->
            Bool -> Neovim env Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False

        Left Doc AnsiStyle
e ->
            Doc AnsiStyle -> Neovim env Bool
forall env a. Doc AnsiStyle -> Neovim env a
err Doc AnsiStyle
e

        Either (Doc AnsiStyle) String
_ ->
            String -> Neovim env Bool
forall env. String -> Neovim env Bool
yesOrNo String
message