-
Notifications
You must be signed in to change notification settings - Fork 200
/
FuzzyOpen.hs
151 lines (134 loc) · 5.36 KB
/
FuzzyOpen.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK show-extensions #-}
-- |
-- Module : Yi.FuzzyOpen
-- License : GPL-2
-- Maintainer : yi-devel@googlegroups.com
-- Stability : experimental
-- Portability : portable
--
-- This file aims to provide (the essential subset of) the same functionality
-- that vim plugins ctrlp and command-t provide.
--
-- Setup:
--
-- Add something like this to your config:
--
-- (ctrlCh 'p' ?>>! fuzzyOpen)
--
-- Usage:
--
-- <C-p> (or whatever mapping user chooses) starts fuzzy open dialog.
--
-- Typing something filters filelist.
--
-- <Enter> opens currently selected file
-- in current (the one that fuzzyOpen was initited from) window.
--
-- <C-t> opens currently selected file in a new tab.
-- <C-s> opens currently selected file in a split.
--
-- <KUp> and <C-p> moves selection up
-- <KDown> and <C-n> moves selection down
--
-- Readline shortcuts <C-a> , <C-e>, <C-u> and <C-k> work as usual.
module Yi.FuzzyOpen (fuzzyOpen) where
import Control.Applicative
import Control.Monad (replicateM, replicateM_, forM, void)
import Control.Monad.Base
import Data.Monoid
import qualified Data.Text as T
import System.Directory (doesDirectoryExist, getDirectoryContents)
import System.FilePath ((</>))
import Yi
import Yi.Completion
import Yi.MiniBuffer
import qualified Yi.Rope as R
import Yi.Utils ()
fuzzyOpen :: YiM ()
fuzzyOpen = do
withEditor splitE
bufRef <- withEditor newTempBufferE
fileList <- liftBase $ getRecursiveContents "."
updateMatchList bufRef fileList
void $ withEditor $ spawnMinibufferE "" $ const $ localKeymap bufRef fileList
-- shamelessly stolen from Chapter 9 of Real World Haskell
-- takes about 3 seconds to traverse linux kernel, which is not too outrageous
-- TODO: check if it works at all with cyclic links
-- TODO: perform in background, limit file count or directory depth
getRecursiveContents :: FilePath -> IO [FilePath]
getRecursiveContents topdir = do
names <- getDirectoryContents topdir
let properNames = filter (`notElem` [".", "..", ".git", ".svn"]) names
paths <- forM properNames $ \name -> do
let path = topdir </> name
isDirectory <- doesDirectoryExist path
if isDirectory
then getRecursiveContents path
else return [path]
return (concat paths)
localKeymap :: BufferRef -> [FilePath] -> Keymap
localKeymap bufRef fileList =
choice [spec KEnter ?>>! openInThisWindow bufRef
, ctrlCh 't' ?>>! openInNewTab bufRef
, ctrlCh 's' ?>>! openInSplit bufRef
, spec KEsc ?>>! replicateM 2 closeBufferAndWindowE
, ctrlCh 'h' ?>>! updatingB (deleteB Character Backward)
, spec KBS ?>>! updatingB (deleteB Character Backward)
, spec KDel ?>>! updatingB (deleteB Character Backward)
, ctrlCh 'a' ?>>! moveToSol
, ctrlCh 'e' ?>>! moveToEol
, spec KLeft ?>>! moveXorSol 1
, spec KRight ?>>! moveXorEol 1
, ctrlCh 'p' ?>>! moveSelectionUp bufRef
, spec KUp ?>>! moveSelectionUp bufRef
, ctrlCh 'n' ?>>! moveSelectionDown bufRef
, spec KDown ?>>! moveSelectionDown bufRef
, ctrlCh 'w' ?>>! updatingB (deleteB unitWord Backward)
, ctrlCh 'u' ?>>! updatingB (moveToSol >> deleteToEol)
, ctrlCh 'k' ?>>! updatingB deleteToEol
]
<|| (insertChar >>! update)
where update = updateMatchList bufRef fileList
updatingB bufAction = withBuffer bufAction >> update
showFileList :: [FilePath] -> R.YiString
showFileList = R.fromText . T.intercalate "\n" . map (mappend " " . T.pack)
{- Implementation detail:
The index of selected file is stored as vertical cursor position.
Asterisk position is always synchronized with cursor position.
TODO: store index of selected file explicitly to make things more obvious.
-}
updateMatchList :: BufferRef -> [FilePath] -> YiM ()
updateMatchList bufRef fileList = do
needle <- R.toString <$> withBuffer elemsB
let filteredFiles = filter (subsequenceMatch needle) fileList
withEditor $ withGivenBuffer0 bufRef $ do
replaceBufferContent $ showFileList filteredFiles
moveTo 0
replaceCharB '*'
return ()
openInThisWindow :: BufferRef -> YiM ()
openInThisWindow = openRoutine (return ())
openInSplit :: BufferRef -> YiM ()
openInSplit = openRoutine splitE
openInNewTab :: BufferRef -> YiM ()
openInNewTab = openRoutine newTabE
openRoutine :: EditorM () -> BufferRef -> YiM ()
openRoutine preOpenAction bufRef = do
chosenFile <- withEditor $ withGivenBuffer0 bufRef readLnB
withEditor $ do
replicateM_ 2 closeBufferAndWindowE
preOpenAction
void . editFile . drop 2 . R.toString $ chosenFile
insertChar :: Keymap
insertChar = textChar >>= write . insertB
moveSelectionUp :: BufferRef -> EditorM ()
moveSelectionUp bufRef = withGivenBuffer0 bufRef $ do
replaceCharB ' '
lineUp
replaceCharB '*'
moveSelectionDown :: BufferRef -> EditorM ()
moveSelectionDown bufRef = withGivenBuffer0 bufRef $ do
replaceCharB ' '
lineDown
replaceCharB '*'