diff --git a/README.md b/README.md index 25daa66..0503ba5 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Clifm is a small file manager written in Haskell with a terminal-based interface > 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. ## Installation +> Note: You may need to install `ncurses` on your system before using clifm + For ArchLinux the binary from [the latest github release](https://github.com/pasqu4le/clifm/releases/latest) should work. For other Linux distro the binary may work as well, or you can build from source. @@ -25,7 +27,7 @@ $ cabal install ## Features Clifm is a [brick](https://github.com/jtdaugherty/brick) application, that in turn builds upon [vty](https://github.com/jtdaugherty/vty). As such it supports a large number of terminals, but not on Windows, handles windows resizing and more. -If your terminal supports a mouse you can use it to change Tab/Pane, click a button on the bottom or change your selection, but only using the keyboard you can perform every possible action. This is the list of all the keybindings: +If your terminal supports a mouse you can use it to change Tab/Pane, click a button on the bottom, change your selection or open it (double-click), but only using the keyboard you can perform every possible action. This is the list of all the keybindings: #### Bottom menu - L: open Se**l**ection menu diff --git a/clifm.cabal b/clifm.cabal index 945685d..805c943 100644 --- a/clifm.cabal +++ b/clifm.cabal @@ -10,7 +10,7 @@ name: clifm -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change -version: 0.4.0.0 +version: 0.4.1.0 -- A short (one-line) description of the package. synopsis: Command Line Interface File Manager diff --git a/src/Widgets/Manager.hs b/src/Widgets/Manager.hs index 14a41c6..be60a8e 100644 --- a/src/Widgets/Manager.hs +++ b/src/Widgets/Manager.hs @@ -8,6 +8,8 @@ import qualified Widgets.Prompt as Prompt import System.Process (callCommand) import Control.Exception (try, SomeException) +import Control.Monad.IO.Class (liftIO) +import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime) import Brick.Main (continue, halt, suspendAndResume) import Brick.Widgets.Core ((<+>), str, hBox, vBox, vLimit, withBorderStyle) import Brick.Types (Widget, BrickEvent(..), EventM, Next, ViewportType(..), Location(..)) @@ -24,6 +26,7 @@ data State = State {paneZipper :: PaneZipper, lastPaneName :: PaneName, bottomMenu :: Menu.Menu, prompt :: Maybe Prompt.Prompt, + lastClickedEntry :: Maybe (PaneName, Int, UTCTime), editorCommand :: String, eventChan :: BChan (ThreadEvent Tab.Tab) } @@ -33,7 +36,7 @@ type PaneZipper = PointedList Pane.Pane makeState :: FilePath -> String -> BChan (ThreadEvent Tab.Tab) -> IO State makeState path editCom eChan = do pane <- Pane.make 0 path - return $ State (singleton pane) 0 Menu.make Nothing editCom eChan + return $ State (singleton pane) 0 Menu.make Nothing Nothing editCom eChan -- rendering functions drawUi :: State -> [Widget Name] @@ -90,8 +93,8 @@ handleMain (VtyEvent ev) = case ev of EvKey KRight [] -> nextPane _ -> updateCurrentPane (Pane.handleEvent ev) handleMain (MouseUp name _ (Location pos)) = case name of - EntryList {pnName = pName} -> updateCurrentPane (Pane.moveTabToRow $ snd pos) . focusOnPane pName - Label {pnName = pName, labelNum = n} -> updateCurrentPane (Pane.updateTabZipper (Pane.moveToNth n)) . focusOnPane pName + EntryList {pnName = pName} -> clickedEntry pName (snd pos) + Label {pnName = pName, labelNum = n} -> updateCurrentPane (Pane.moveToNthTab n) . focusOnPane pName Button {keyBind = key, withCtrl = b} -> handleMain . VtyEvent $ EvKey key [MCtrl | b] _ -> continue handleMain _ = continue @@ -122,6 +125,20 @@ openPrompt func state = continue $ state {prompt = Just $ func tab pName} openPromptWithClip :: (Menu.Clipboard -> Tab.Tab -> PaneName -> Prompt.Prompt) -> State -> EventM Name (Next State) openPromptWithClip func state = openPrompt (func . Menu.clipboard $ bottomMenu state) state +clickedEntry :: PaneName -> Int -> State -> EventM Name (Next State) +clickedEntry pName row state = do + currTime <- liftIO $ getCurrentTime + let clicked = (pName, row, currTime) + doubleClick = isDoubleClick (lastClickedEntry state) clicked + if doubleClick then openEntry $ state {lastClickedEntry = Nothing} + else updateCurrentPane (Pane.moveTabToRow row) . focusOnPane pName $ state {lastClickedEntry = Just clicked} + +isDoubleClick :: Maybe (PaneName, Int, UTCTime) -> (PaneName, Int, UTCTime) -> Bool +isDoubleClick lastClick (nPane, nRow, nTime) = case lastClick of + Nothing -> False + Just (pPane, pRow, pTime) -> and [pPane == nPane, pRow == nRow, isSmallDiff] + where isSmallDiff = toRational (diffUTCTime nTime pTime) <= toRational 0.2 + openEntry :: State -> EventM Name (Next State) openEntry state = case Pane.selectedEntry $ currentPane state of Just Entry.Dir {} -> openDirEntry False state diff --git a/src/Widgets/Pane.hs b/src/Widgets/Pane.hs index 2417845..587e9f5 100644 --- a/src/Widgets/Pane.hs +++ b/src/Widgets/Pane.hs @@ -75,6 +75,9 @@ openDirEntry inNew pane = case selectedEntry pane of replaceCurrentTab :: Tab.Tab -> Pane -> EventM Name Pane replaceCurrentTab tab = updateTabZipper (replace tab) +moveToNthTab :: Int -> Pane -> EventM Name Pane +moveToNthTab n = updateTabZipper (moveToNth n) + -- tab and tabZipper utility functions selectedEntry :: Pane -> Maybe Entry.Entry selectedEntry = Tab.selectedEntry . currentTab