Use a split for fzf to show previous work. (tput doesn't seem to work on windows terminal) (#2984)

* Use a split for fzf to show previous work.

* Handle most errors by just silently returning empty results.
This commit is contained in:
Chris Penner 2022-03-19 12:54:28 -06:00 committed by GitHub
parent fad4c0d86e
commit 863d989856
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -14,12 +14,11 @@ import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import GHC.IO.Handle (hDuplicateTo)
import System.Exit (ExitCode (ExitFailure, ExitSuccess))
import System.IO (BufferMode (NoBuffering), hPutStrLn, stderr)
import Unison.Prelude
import UnliftIO (handleAny)
import qualified UnliftIO
import UnliftIO.Directory (findExecutable)
import UnliftIO.Exception (bracket, bracket_)
import UnliftIO.Exception (bracket)
import UnliftIO.IO (hGetBuffering, hSetBuffering, stdin)
import qualified UnliftIO.Process as Proc
@ -47,24 +46,19 @@ optsToArgs opts =
-- This allows us to prepend each line with a number, and use that number to determine
-- which values from the input list were selected.
[ "--with-nth",
"2.."
"2..",
-- Use only half the screen (it's nice to see what you were working on when searching)
"--height=50%",
-- But if 50% of the screen is too small, ensure show at least 10 results.
"--min-height=10"
]
-- | Run the given IO block within a fresh terminal screen, clean it up and restore the
-- previous screen after the block is finished.
withTempScreen :: IO a -> IO a
withTempScreen =
bracket_
(Proc.callCommand "tput smcup") -- Stash existing screen, create a new one
(Proc.callCommand "tput rmcup") -- Delete the temporary screen, restore the original.
-- | Allows prompting the user to interactively fuzzy-select a result from a list of options, currently shells out to `fzf` under the hood.
-- If fzf is missing, or an error (other than ctrl-c) occurred, returns Nothing.
fuzzySelect :: forall a. Options -> (a -> Text) -> [a] -> IO (Maybe [a])
fuzzySelect opts intoSearchText choices =
handleAny handleException
UnliftIO.handleAny handleException
. handleError
. withTempScreen
. restoreBuffering
. runExceptT
$ do
@ -88,17 +82,16 @@ fuzzySelect opts intoSearchText choices =
-- Generally no-buffering is helpful for highly interactive processes.
hSetBuffering stdin NoBuffering
hSetBuffering stdin' NoBuffering
-- Dump the search terms into fzf's stdin
liftIO $ traverse (Text.hPutStrLn stdin') searchTexts
-- Wire up the interactive terminal to fzf now that the inputs have been loaded.
liftIO $ hDuplicateTo stdin stdin'
exitCode <- Proc.waitForProcess procHandle
case exitCode of
ExitSuccess -> pure ()
-- Thrown on ctrl-c in fzf
ExitFailure 130 -> pure () -- output handle will be empty and no results will be returned.
ExitFailure _ -> throwError "Oops, something went wrong. No input selected."
selections <- Text.lines <$> liftIO (Text.hGetContents stdout')
result <- liftIO . UnliftIO.tryAny $ do
-- Dump the search terms into fzf's stdin
traverse (Text.hPutStrLn stdin') searchTexts
-- Wire up the interactive terminal to fzf now that the inputs have been loaded.
hDuplicateTo stdin stdin'
void $ Proc.waitForProcess procHandle
Text.lines <$> liftIO (Text.hGetContents stdout')
-- Ignore any errors from fzf, or from trying to write to pipes which may have been
-- closed by a ctrl-c, just treat it as an empty selection.
let selections = fromRight [] result
-- Since we prefixed every search term with its number earlier, we know each result
-- is prefixed with a number, we need to parse it and use it to select the matching
-- value from our input list.
@ -109,7 +102,7 @@ fuzzySelect opts intoSearchText choices =
pure $ mapMaybe (\(n, a) -> if n `Set.member` selectedNumbers then Just a else Nothing) numberedChoices
where
handleException :: SomeException -> IO (Maybe [a])
handleException _ = hPutStrLn stderr "Oops, something went wrong. No input selected." *> pure Nothing
handleException err = traceShowM err *> hPutStrLn stderr "Oops, something went wrong. No input selected." *> pure Nothing
handleError :: IO (Either Text [a]) -> IO (Maybe [a])
handleError m =
m >>= \case