packages feed

clifm 0.5.0.0 → 0.5.2.1

raw patch · 6 files changed

+61/−83 lines, 6 files

Files

README.md view
@@ -1,7 +1,7 @@ # Command Line Interface File Manager Clifm is a small file manager written in Haskell with a terminal-based interface. It allows you to explore directories in multiple Panes/Tabs and perform basic operations. -![screenshot](screenshot.png)+![screenshot](https://github.com/pasqu4le/clifm/raw/master/screenshot.png)  > Note: this is still an experiment. Directory navigation will do no harm, but double-check before starting operations on your file system. I take no responsibility for what you do with this software. 
clifm.cabal view
@@ -10,7 +10,7 @@ -- PVP summary:      +-+------- breaking API changes --                   | | +----- non-breaking API additions --                   | | | +--- code changes with no API change-version:             0.5.0.0+version:             0.5.2.1  -- A short (one-line) description of the package. synopsis:            Command Line Interface File Manager
src/Widgets/Menu.hs view
@@ -27,8 +27,8 @@ menuType :: Lens' Menu MenuType menuType = lens _menuType (\menu x -> menu {_menuType = x}) -fromEntry :: Lens' Clipboard Entry.Entry-fromEntry = lens _fromEntry (\board x -> board {_fromEntry = x})+fromEntry :: Traversal' Clipboard Entry.Entry+fromEntry = filtered (not . isEmpty) . lens _fromEntry (\board x -> board {_fromEntry = x})  -- creation make :: Menu@@ -77,7 +77,7 @@   | Tab.isDir tab = dirTabButtons ++ entryTabButtons orderType ++ anyTabButtons   | Tab.isSearch tab = entryTabButtons orderType ++ anyTabButtons   | otherwise = anyTabButtons-  where orderType = view Tab.orderType tab+  where (Just orderType) = preview Tab.orderType tab  dirTabButtons :: [(Widget Name, Maybe String, Name)] dirTabButtons = [
src/Widgets/Pane.hs view
@@ -29,9 +29,8 @@ currentTab :: Lens' Pane Tab.Tab currentTab = tabZipper.focus ---NOTE: needs to filter empty tabs, or it will fail entries :: Traversal' Pane Entry.Entry-entries = tabZipper.traverse.filtered (not . Tab.isEmpty).Tab.entries+entries = tabZipper.traverse.Tab.entries  -- creation empty :: PaneName -> Pane
src/Widgets/Prompt.hs view
@@ -8,6 +8,7 @@ import Data.Monoid ((<>)) import Data.Functor (($>)) import Control.Monad(when,forM_)+import Data.Maybe (fromJust) import Control.Monad.IO.Class (liftIO) import Control.Concurrent (forkFinally, ThreadId, killThread) import Control.Exception (try, throw, displayException, SomeException, fromException)@@ -21,9 +22,10 @@ import Brick.BChan (BChan, writeBChan) import Graphics.Vty (Event(EvKey), Key(..)) import Data.Time.Format (formatTime, defaultTimeLocale)-import System.FilePath (isValid, takeDirectory, (</>), takeFileName)+import Conduit+import System.FilePath (isValid, takeDirectory, (</>), takeFileName, makeRelative) import System.Directory (doesFileExist, doesDirectoryExist, createDirectory, renameFile,-  copyFileWithMetadata, removeFile, removeDirectoryRecursive, getDirectoryContents,+  copyFileWithMetadata, removeFile, removeDirectoryRecursive, createDirectoryIfMissing,   readable, writable, executable, searchable)  data Prompt = Prompt {_originTab :: Tab.Tab, _originPane :: PaneName, _action :: Action} deriving Show@@ -69,8 +71,8 @@     | Menu.isEmpty clip = DisplayError "The clipboard is empty"     | Tab.isEmpty tab = DisplayError "You cannot paste into an empty tab"     | Tab.isSearch tab = DisplayError "You cannot paste into a search tab"-    | Menu.isCopy clip = Copy (clip ^. Menu.fromEntry) (tab ^. Tab.path)-    | Menu.isCut clip = Cut (clip ^. Menu.fromEntry) (tab ^. Tab.path)+    | Menu.isCopy clip = Copy (fromJust $ preview Menu.fromEntry clip) (tab ^. Tab.path)+    | Menu.isCut clip = Cut (fromJust $ preview Menu.fromEntry clip) (tab ^. Tab.path)     | otherwise = DisplayError "Cannot paste: unknown error"   in Prompt tab pName checked @@ -309,31 +311,18 @@  -- files functions not covered by System.Directory nor System.FilePath moveFileWithMetadata :: FilePath -> FilePath -> IO ()-moveFileWithMetadata o d = do-  copyFileWithMetadata o d-  removeFile o+moveFileWithMetadata src dst = do+  copyFileWithMetadata src dst+  removeFile src  moveDirectoryRecursive :: FilePath -> FilePath -> IO ()-moveDirectoryRecursive o d = do-  copyDirectoryRecursive o d-  removeDirectoryRecursive o+moveDirectoryRecursive src dst = do+  copyDirectoryRecursive src dst+  removeDirectoryRecursive src --- taken from https://stackoverflow.com/questions/6807025/what-is-the-haskell-way-to-copy-a-directory copyDirectoryRecursive ::  FilePath -> FilePath -> IO ()-copyDirectoryRecursive src dst = do-  whenM (not <$> doesDirectoryExist src) $ throw (userError "source does not exist")-  whenM (doesFileOrDirectoryExist dst) $ throw (userError "destination already exists")-  createDirectory dst-  content <- getDirectoryContents src-  let xs = filter (`notElem` [".", ".."]) content-  forM_ xs $ \name -> do-    let srcPath = src </> name-    let dstPath = dst </> name-    isDirectory <- doesDirectoryExist srcPath-    if isDirectory-      then copyDirectoryRecursive srcPath dstPath-      else copyFileWithMetadata srcPath dstPath-  where-    doesFileOrDirectoryExist x = orM [doesDirectoryExist x, doesFileExist x]-    orM xs = or <$> sequence xs-    whenM s r = s >>= flip when r+copyDirectoryRecursive src dst = runConduitRes+  $ sourceDirectoryDeep False src+  .| mapC (\fPath -> (fPath, dst </> (makeRelative src fPath)))+  .| iterMC (liftIO . createDirectoryIfMissing True . takeDirectory . snd)+  .| mapM_C (\(srcFile, dstFile)  -> liftIO $ copyFileWithMetadata srcFile dstFile)
src/Widgets/Tab.hs view
@@ -5,9 +5,10 @@ import Control.Lens hiding (Empty) import Data.List (sortOn, isInfixOf, elemIndex) import Data.Char (toLower)-import Data.Maybe (fromMaybe)+import Data.Maybe (fromMaybe, fromJust) import Data.Time.Clock (UTCTime(..), secondsToDiffTime) import Data.Time.Calendar (Day(ModifiedJulianDay))+import Conduit import System.FilePath (takeFileName, takeDirectory, (</>)) import System.Directory (doesDirectoryExist, doesFileExist, listDirectory) import Brick.Types (Widget, EventM)@@ -41,25 +42,25 @@   show ModificationTime = "modified"  -- lenses-name :: Lens' Tab String-name = lens _name (\tab n -> tab {_name = n})+name :: Traversal' Tab String+name = unlessEmpty . lens _name (\tab n -> tab {_name = n}) -path :: Lens' Tab FilePath-path = lens _path (\tab n -> tab {_path = n})+path :: Traversal' Tab FilePath+path = unlessEmpty . lens _path (\tab n -> tab {_path = n}) -entryList :: Lens' Tab (List Name Entry.Entry)-entryList = lens _entryList (\tab n -> tab {_entryList = n})+entryList :: Traversal' Tab (List Name Entry.Entry)+entryList = unlessEmpty . lens _entryList (\tab n -> tab {_entryList = n}) -entryOrder :: Lens' Tab EntryOrder-entryOrder = lens _entryOrder (\tab n -> tab {_entryOrder = n})+entryOrder :: Traversal' Tab EntryOrder+entryOrder = unlessEmpty . lens _entryOrder (\tab n -> tab {_entryOrder = n}) -query :: Lens' Tab String-query = lens _query (\tab n -> tab {_query = n})+query :: Traversal' Tab String+query = onSearch . lens _query (\tab n -> tab {_query = n}) -orderType :: Lens' Tab OrderType+orderType :: Traversal' Tab OrderType orderType = entryOrder.entryOrderType -orderInverted :: Lens' Tab Bool+orderInverted :: Traversal' Tab Bool orderInverted = entryOrder.entryOrderInverted  entryOrderType :: Lens' EntryOrder OrderType@@ -68,7 +69,12 @@ entryOrderInverted :: Lens' EntryOrder Bool entryOrderInverted = lens _inverted (\tab n -> tab {_inverted = n}) ---NOTE: remember that it will fail for Empty tabs+onSearch :: Traversal' Tab Tab+onSearch = filtered isSearch++unlessEmpty :: Traversal' Tab Tab+unlessEmpty = filtered (not . isEmpty)+ entries :: Traversal' Tab Entry.Entry entries = entryList.traverse @@ -132,9 +138,7 @@   ]  renderEntryOrder :: Tab -> Widget Name-renderEntryOrder tab = str $ case tab of-  Empty -> ""-  _ -> " by " ++ views entryOrder show tab+renderEntryOrder = str . maybe "" (" by " ++) . previews entryOrder show  renderPath :: Tab -> Widget Name renderPath tab = str $ case tab of@@ -143,7 +147,10 @@   Search {_path = p, _query = q} -> " search for " ++ q ++ " in " ++ takeFileName p  renderContent :: Bool -> Tab -> Widget Name-renderContent _ Empty = vBox (lns ++ [fill ' '])+renderContent hasFoc = maybe emptyContent (renderList Entry.render hasFoc) . preview entryList++emptyContent :: Widget Name+emptyContent = vBox (lns ++ [fill ' '])   where lns = map strWrap $ lines "Command Line Interface File Manager\n \n\     \clifm allows you to explore directories on multiple tabs.\nIf your terminal\     \ has mouse support you can click on some elements to interact with them, \@@ -154,33 +161,29 @@     \BackTab key or use Ctrl + Left or Right arrow key to swap them.\n \nYou can \     \see every other possible action as a button in the bottom, or you can use \     \them as Keys combination.\n \nTo see them all please refer to the README"-renderContent hasFocus tab = views entryList (renderList Entry.render hasFocus) tab  -- event handling and state-changing handleEvent :: Event -> Tab -> EventM Name Tab-handleEvent _ Empty = return Empty-handleEvent event tab = case event of-  EvKey (KChar 'o') [] -> return $ changeOrder tab-  EvKey (KChar 'i') [] -> return $ invertOrder tab-  _ -> entryList (handleListEvent event) tab+handleEvent event = case event of+  EvKey (KChar 'o') [] -> return . changeOrder+  EvKey (KChar 'i') [] -> return . invertOrder+  _ -> entryList (handleListEvent event)  changeOrder :: Tab -> Tab-changeOrder Empty = Empty changeOrder tab = tab   & entryOrder .~ newOrder   & entryList.listElementsL._tail .~ sorted   & entryList.listSelectedL ?~ maybe 0 (+1) ((`Vect.elemIndex` sorted) =<< selectedEntry tab)   where-    newOrder = over entryOrderType nextOrderType $ view entryOrder tab+    newOrder = over entryOrderType nextOrderType . fromJust $ preview entryOrder tab     sorted = Vect.fromList . sortEntries newOrder . toList $ view (entryList.listElementsL._tail) tab  invertOrder :: Tab -> Tab-invertOrder Empty = Empty-invertOrder tab = tab +invertOrder tab = tab   & orderInverted %~ not    & entryList.listElementsL._tail %~ Vect.reverse   & entryList.listSelectedL %~ fmap (\idx -> if idx == 0 then 0 else size - idx)-  where size = views (entryList.listElementsL) Vect.length tab+  where size = fromJust $ previews (entryList.listElementsL) Vect.length tab  reload :: PaneName -> Tab -> IO Tab reload pName tab = case tab of@@ -195,8 +198,7 @@   where newElems = listElements newList  moveToRow :: Int -> Tab -> Tab-moveToRow _ Empty = Empty-moveToRow row tab = tab & entryList %~ listMoveTo row+moveToRow row = over entryList (listMoveTo row)  -- utility isDir :: Tab -> Bool@@ -213,7 +215,7 @@  selectedEntry :: Tab -> Maybe Entry.Entry selectedEntry Empty = Nothing-selectedEntry tab = snd <$> listSelectedElement (view entryList tab)+selectedEntry tab = snd <$> listSelectedElement (fromJust $ preview entryList tab)  nextOrderType :: OrderType -> OrderType nextOrderType order@@ -235,19 +237,7 @@ zeroTime = UTCTime (ModifiedJulianDay 0) (secondsToDiffTime 0)  searchRecursive :: FilePath -> String -> IO [FilePath]-searchRecursive filePath searchQuery = do-  subNames <- listDirectory filePath-  filePaths <- listRecursive $ map (filePath </>) subNames-  return $ filter (isInfixOf searchQuery . takeFileName) filePaths--listRecursive :: [FilePath] -> IO [FilePath]-listRecursive [] = return []-listRecursive (filePath:filePaths) = do-  isDir <- doesDirectoryExist filePath-  isFile <- doesFileExist filePath-  if isFile then (filePath :) <$> listRecursive filePaths-  else if isDir then do-    subNames <- listDirectory filePath-    let subPaths = map (filePath </>) subNames-    (filePath :) <$> listRecursive (subPaths ++ filePaths)-  else listRecursive filePaths+searchRecursive filePath searchQuery = runConduitRes+  $ sourceDirectoryDeep False filePath+  .| filterC (isInfixOf searchQuery . takeFileName)+  .| sinkList