diff --git a/feedback/feedback.cabal b/feedback/feedback.cabal index 455c794..031c498 100644 --- a/feedback/feedback.cabal +++ b/feedback/feedback.cabal @@ -24,6 +24,7 @@ library Feedback.Common.Output Feedback.Common.Process Feedback.Loop + Feedback.Loop.Filter Feedback.Loop.OptParse Feedback.Test Feedback.Test.OptParse diff --git a/feedback/src/Feedback/Loop.hs b/feedback/src/Feedback/Loop.hs index f67e07e..88e2b8c 100644 --- a/feedback/src/Feedback/Loop.hs +++ b/feedback/src/Feedback/Loop.hs @@ -1,30 +1,22 @@ {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} module Feedback.Loop where import Control.Monad -import Data.Conduit -import qualified Data.Conduit.Combinators as C -import Data.List -import Data.Maybe -import Data.Set -import qualified Data.Set as S import qualified Data.Text as T -import qualified Data.Text.Encoding as TE import Data.Word import Feedback.Common.OptParse import Feedback.Common.Output import Feedback.Common.Process +import Feedback.Loop.Filter import Feedback.Loop.OptParse import GHC.Clock (getMonotonicTimeNSec) import Path import Path.IO import System.Exit import System.FSNotify as FS -import System.Process.Typed as Typed import Text.Colour import Text.Colour.Capabilities.FromEnv (getTerminalCapabilitiesFromEnv) import UnliftIO @@ -55,97 +47,6 @@ runFeedbackLoop = do (outputWorker loopSettingOutputSettings outputChan) stopListeningAction -getStdinFiles :: Path Abs Dir -> IO (Maybe (Set FilePath)) -getStdinFiles here = do - isTerminal <- hIsTerminalDevice stdin - if isTerminal - then pure Nothing - else - (Just <$> handleFileSet here stdin) - `catch` (\(_ :: IOException) -> pure Nothing) - -mkEventFilter :: Path Abs Dir -> Maybe (Set FilePath) -> FilterSettings -> IO (FS.Event -> Bool) -mkEventFilter here mStdinFiles FilterSettings {..} = do - let mFilter mSet event = maybe True (eventPath event `S.member`) mSet - let stdinFilter = mFilter mStdinFiles - mFindFiles <- mapM (filesFromFindArgs here) filterSettingFind - let findFilter = mFilter mFindFiles - mGitFiles <- - if filterSettingGitingore - then gitLsFiles here - else pure Nothing - let gitFilter = mFilter mGitFiles - let standardFilter = standardEventFilter here - pure $ - if isJust mStdinFiles - then stdinFilter - else - if isJust mFindFiles - then findFilter - else combineFilters [standardFilter, gitFilter] - -combineFilters :: [FS.Event -> Bool] -> FS.Event -> Bool -combineFilters filters event = all ($ event) filters - -gitLsFiles :: Path Abs Dir -> IO (Maybe (Set FilePath)) -gitLsFiles here = do - let processConfig = setStdout createPipe $ shell "git ls-files" - process <- startProcess processConfig - ec <- waitExitCode process - case ec of - ExitFailure _ -> pure Nothing - ExitSuccess -> Just <$> handleFileSet here (getStdout process) - -filesFromFindArgs :: Path Abs Dir -> String -> IO (Set FilePath) -filesFromFindArgs here args = do - let processConfig = setStdout createPipe $ shell $ "find " <> args - process <- startProcess processConfig - ec <- waitExitCode process - case ec of - ExitFailure _ -> die $ "Find failed: " <> show ec - ExitSuccess -> handleFileSet here (getStdout process) - -handleFileSet :: Path Abs Dir -> Handle -> IO (Set FilePath) -handleFileSet here h = - runConduit $ - C.sourceHandle h - .| C.linesUnboundedAscii - .| C.concatMap TE.decodeUtf8' - .| C.map T.unpack - .| C.mapM (resolveFile here) - .| C.map fromAbsFile - .| C.foldMap S.singleton - -standardEventFilter :: Path Abs Dir -> FS.Event -> Bool -standardEventFilter here fsEvent = - and - [ -- It's not one of those files that vim makes - (filename <$> parseAbsFile (eventPath fsEvent)) /= Just [relfile|4913|], - not $ "~" `isSuffixOf` eventPath fsEvent, - -- It's not a hidden file - not $ hiddenHere here (eventPath fsEvent) - ] - -hiddenHere :: Path Abs Dir -> FilePath -> Bool -hiddenHere here filePath = - (hidden <$> (parseAbsFile filePath >>= stripProperPrefix here)) /= Just False - -hidden :: Path Rel File -> Bool -hidden = goFile - where - goFile :: Path Rel File -> Bool - goFile f = isHiddenIn (parent f) f || goDir (parent f) - goDir :: Path Rel Dir -> Bool - goDir f - | parent f == f = False - | otherwise = isHiddenIn (parent f) f || goDir (parent f) - -isHiddenIn :: Path b Dir -> Path b t -> Bool -isHiddenIn curdir ad = - case stripProperPrefix curdir ad of - Nothing -> False - Just rp -> "." `isPrefixOf` toFilePath rp - processWorker :: RunSettings -> Chan FS.Event -> Chan Output -> IO () processWorker runSettings eventChan outputChan = do let sendOutput = writeChan outputChan diff --git a/feedback/src/Feedback/Loop/Filter.hs b/feedback/src/Feedback/Loop/Filter.hs new file mode 100644 index 0000000..864d641 --- /dev/null +++ b/feedback/src/Feedback/Loop/Filter.hs @@ -0,0 +1,113 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Feedback.Loop.Filter where + +import Data.Conduit +import qualified Data.Conduit.Combinators as C +import Data.List +import Data.Maybe +import Data.Set +import qualified Data.Set as S +import qualified Data.Text as T +import qualified Data.Text.Encoding as TE +import Feedback.Common.OptParse +import Path +import Path.IO +import System.Exit +import System.FSNotify as FS +import System.Process.Typed as Typed +import UnliftIO + +getStdinFiles :: Path Abs Dir -> IO (Maybe (Set FilePath)) +getStdinFiles here = do + isTerminal <- hIsTerminalDevice stdin + if isTerminal + then pure Nothing + else + (Just <$> handleFileSet here stdin) + `catch` (\(_ :: IOException) -> pure Nothing) + +mkEventFilter :: Path Abs Dir -> Maybe (Set FilePath) -> FilterSettings -> IO (FS.Event -> Bool) +mkEventFilter here mStdinFiles FilterSettings {..} = do + let mFilter mSet event = maybe True (eventPath event `S.member`) mSet + let stdinFilter = mFilter mStdinFiles + mFindFiles <- mapM (filesFromFindArgs here) filterSettingFind + let findFilter = mFilter mFindFiles + mGitFiles <- + if filterSettingGitingore + then gitLsFiles here + else pure Nothing + let gitFilter = mFilter mGitFiles + let standardFilter = standardEventFilter here + pure $ + if isJust mStdinFiles + then stdinFilter + else + if isJust mFindFiles + then findFilter + else combineFilters [standardFilter, gitFilter] + +combineFilters :: [FS.Event -> Bool] -> FS.Event -> Bool +combineFilters filters event = all ($ event) filters + +gitLsFiles :: Path Abs Dir -> IO (Maybe (Set FilePath)) +gitLsFiles here = do + let processConfig = setStdout createPipe $ shell "git ls-files" + process <- startProcess processConfig + ec <- waitExitCode process + case ec of + ExitFailure _ -> pure Nothing + ExitSuccess -> Just <$> handleFileSet here (getStdout process) + +filesFromFindArgs :: Path Abs Dir -> String -> IO (Set FilePath) +filesFromFindArgs here args = do + let processConfig = setStdout createPipe $ shell $ "find " <> args + process <- startProcess processConfig + ec <- waitExitCode process + case ec of + ExitFailure _ -> die $ "Find failed: " <> show ec + ExitSuccess -> handleFileSet here (getStdout process) + +handleFileSet :: Path Abs Dir -> Handle -> IO (Set FilePath) +handleFileSet here h = + runConduit $ + C.sourceHandle h + .| C.linesUnboundedAscii + .| C.concatMap TE.decodeUtf8' + .| C.map T.unpack + .| C.mapM (resolveFile here) + .| C.map fromAbsFile + .| C.foldMap S.singleton + +standardEventFilter :: Path Abs Dir -> FS.Event -> Bool +standardEventFilter here fsEvent = + and + [ -- It's not one of those files that vim makes + (filename <$> parseAbsFile (eventPath fsEvent)) /= Just [relfile|4913|], + not $ "~" `isSuffixOf` eventPath fsEvent, + -- It's not a hidden file + not $ hiddenHere here (eventPath fsEvent) + ] + +hiddenHere :: Path Abs Dir -> FilePath -> Bool +hiddenHere here filePath = + (hidden <$> (parseAbsFile filePath >>= stripProperPrefix here)) /= Just False + +hidden :: Path Rel File -> Bool +hidden = goFile + where + goFile :: Path Rel File -> Bool + goFile f = isHiddenIn (parent f) f || goDir (parent f) + goDir :: Path Rel Dir -> Bool + goDir f + | parent f == f = False + | otherwise = isHiddenIn (parent f) f || goDir (parent f) + +isHiddenIn :: Path b Dir -> Path b t -> Bool +isHiddenIn curdir ad = + case stripProperPrefix curdir ad of + Nothing -> False + Just rp -> "." `isPrefixOf` toFilePath rp