-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathMain.hs
More file actions
336 lines (285 loc) · 10.9 KB
/
Copy pathMain.hs
File metadata and controls
336 lines (285 loc) · 10.9 KB
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
{-# Language OverloadedStrings #-}
module Main where
--import Debug.Trace (trace)
import Data.Bits ((.&.))
import Data.Maybe (catMaybes, fromMaybe)
import GHC.IO.Handle (hDuplicateTo, hDuplicate)
import qualified Data.ByteString.Char8 as B
import Prelude hiding (sequence)
import System.Environment (getArgs, getEnvironment)
import System.IO (stdin, stdout, stderr, hSetBuffering, openFile,
IOMode( ReadMode ), BufferMode ( NoBuffering ) )
import System.Posix (executeFile, getFdStatus, fileMode)
import System.Process (waitForProcess, createProcess, CreateProcess(std_out), shell, StdStream( CreatePipe ))
import UI.NCurses
import Scorer
import HfArgs (compilerOpts, Flag(..))
import SimpleFormatter
import Write
import ResultSet
data Query = Query { q :: String
}
deriving (Show, Eq)
data QueriedSet = QueriedSet { query :: Query
, strat :: ScoreStrat
, results :: Results
}
deriving (Show, Eq)
data SystemState = SystemState { current :: QueriedSet
, history :: [QueriedSet]
, cursorPos :: Int
, rCount :: Int
} deriving (Show, Eq)
data Terminal = Exit
| Updated SystemState
| Selected B.ByteString
deriving (Show, Eq)
data AttrWrite = AttrWrite { write :: Write
, attrs :: [Attribute]
, highlighted :: Bool
} deriving (Show, Eq)
type UIFunc = SystemState -> Curses (Maybe B.ByteString)
iSimple :: Justify -> Row -> String -> AttrWrite
iSimple j r s = AttrWrite (simple j r s) [] False
main :: IO ()
main = do
flags <- getArgs >>= fmap fst . compilerOpts
ss <- getStrat flags
res <- readLines
bs <- initUI $ SystemState (QueriedSet (Query "") ss res) [] 0 (size res)
env <- getEnvironment
let retval = fmap (formatOutput env flags . B.unpack) bs
case retval of
Nothing -> return ()
Just s -> if ExecVP `elem` flags
then simpleExec env s
else putStrLn s
-- Execvp into the editor
simpleExec :: [(String, String)] -> String -> IO ()
simpleExec fm s = case words s of
[x] -> executeFile (fromMaybe "vim" $ fauxLookup "EDITOR" fm) True [x] (Just fm)
(x:xs) -> executeFile x True xs (Just fm)
-- Hmm, should never get here. Need to encode that
[] -> return ()
-- Format according to the format string
formatOutput :: [(String, String)] -> [Flag] -> String -> String
formatOutput _ [] o = o
formatOutput env ((SFormat sf):_) o = format sf (o:pieces) env
where pieces = words o
formatOutput env (_:xs) o = formatOutput env xs o
readLines :: IO Results
readLines = do
piped <- isPiped
bLines <- if piped
then readLinesStdin
else runFind "."
return $ build bLines
runFind :: FilePath -> IO [B.ByteString]
runFind dir = do
let cmd = concat ["find ", dir, " -type f"]
-- I can't seem to get proc to correctly build the args, so using shell
(_, Just hout, _, ph) <- createProcess (shell cmd) { std_out = CreatePipe}
input <- fmap B.lines . B.hGetContents $ hout
_ <- waitForProcess ph
return input
-- Check if our stdin is piped. conversion song and dance
isPiped :: IO Bool
isPiped = do
-- Normally, I'd like to call something like handleToFd on stdin, but
-- that has the side effect of closing the handle
fs <- getFdStatus 0
-- Have to mask the filemode
let fm = (fileMode fs) .&. 61440
-- 4096, on linux at least, indicates the input is fifo
let piped = fm .&. 4096
return $ 0 /= piped
-- Read lines from stdin
readLinesStdin :: IO [B.ByteString]
readLinesStdin = do
inp <- B.getContents
reOpenStdin
return . B.lines $ inp
-- Have to reopen stdin since getContents closes it
reOpenStdin :: IO ()
reOpenStdin = do
tty <- openFile "/dev/tty" ReadMode
hSetBuffering tty NoBuffering
hDuplicateTo tty stdin
-- Run the Curses UI
initUI :: SystemState -> IO (Maybe B.ByteString)
initUI rs = do
redirect . runCurses $ do
w <- defaultWindow
cid <- newColorID ColorGreen ColorDefault 1
ui w cid rs
-- Redirects the stdout to stderr
redirect :: IO a -> IO a
redirect io = do
oldStdout <- hDuplicate stdout
hDuplicateTo stderr stdout
res <- io
hDuplicateTo oldStdout stdout
return res
ui :: Window -> ColorID -> UIFunc
ui w cid ss@(SystemState r _ cp rc) = do
coords <- iScreenSize
let top_items = take ((fst coords) - 2) . printTopItems $ r
renderWith w $ do
clearScreen coords
let item_set = updateAt boldWrite cp top_items
applyWrites cid coords $ concat [
item_set >>= (highlight r),
[printStatus rc r],
[printQuery . query $ r]
]
event <- readInput w
-- We grab it again in case they resized their screen
c2 <- iScreenSize
renderWith w $ applyWrites cid c2 [iSimple LJustify Bottom "Searching..."]
updateState ss (length top_items) event (ui w cid)
where renderWith win up = updateWindow win up >> render
-- Handles updating the system state
updateState :: SystemState -> Int -> Event -> UIFunc -> Curses (Maybe B.ByteString)
updateState ss itemCount event f = case processEvent ss event of
Exit -> return Nothing
Selected bs -> return $ Just bs
Updated newSs -> do
let newCP = min (itemCount - 1) (cursorPos newSs)
let safeSs = newSs {cursorPos = newCP}
f safeSs
-- Update an element in the list at the given index
updateAt :: (a -> a) -> Int -> [a] -> [a]
updateAt f idx = loop idx
where loop _ [] = []
loop 0 (x:xs) = (f x):xs
loop i (x:xs) = x:(loop (i - 1) xs)
-- Because Integers are inconvenient
iScreenSize :: Curses (Int, Int)
iScreenSize = do
(r, c) <- screenSize
return (fromIntegral r, fromIntegral c)
-- Evaluates the Writes
applyWrites :: ColorID -> (Int, Int) -> [AttrWrite] -> Update ()
applyWrites cid c ws = do
let realWrites = catMaybes . fmap (constrainAW c) $ ws
mapM_ (displayWrite cid) realWrites
-- Constrains based on AttrWrite
constrainAW :: (Int, Int) -> AttrWrite -> Maybe ([Attribute], Bool, ExactWrite)
constrainAW coords (AttrWrite e atts hled) = do
ew <- constrain coords e
return (atts, hled, ew)
-- Write it out
displayWrite :: ColorID -> ([Attribute], Bool, ExactWrite) -> Update ()
displayWrite cid (atts, hl, (ExactWrite (r, col) s)) = do
moveCursor (fromIntegral r) (fromIntegral col)
applyColor cid hl $ applyAttributes atts $ drawString s
-- Apply an attribute for a given amount
applyAttributes :: [Attribute] -> Update () -> Update ()
applyAttributes atts up = do
setAttrs True atts
up
setAttrs False atts
where setAttrs b = mapM_ ((flip setAttribute) b)
applyColor :: ColorID -> Bool -> Update () -> Update ()
applyColor _ False up = up
applyColor cid _ up = do
setColor cid
up
setColor defaultColorID
-- We don't have a clear screen in this version of the library, so write one
clearScreen :: (Int, Int) -> Update ()
clearScreen (rows, cols) = do
let coords = [(fromIntegral r, fromIntegral c) | r <- [0..(rows - 1)], c <- [0..(cols - 2)]]
let clearPixel (r,c) = (moveCursor r c) >> (drawString " ")
mapM_ clearPixel coords
-- Reads from input
readInput :: Window -> Curses Event
readInput w = do
ev <- getEvent w . Just $ 1000 -- Nothing doesn't work.
case ev of
Nothing -> readInput w
-- Alt keys
Just (EventCharacter '\ESC') -> do
ev2 <- readInput w
case ev2 of
EventCharacter 'n' -> return $ EventSpecialKey KeyDownArrow
EventCharacter 'p' -> return $ EventSpecialKey KeyUpArrow
_ -> readInput w
Just ev' -> return ev'
processEvent :: SystemState -> Event -> Terminal
-- Delete
processEvent ss (EventSpecialKey KeyBackspace) = case ss of
(SystemState _ (r:rs) _ _) -> Updated $ ss { current = r, history = rs, cursorPos = 0 }
_ -> Updated ss
-- Down Arrow
processEvent ss (EventSpecialKey KeyDownArrow) = Updated $ newSS
where newSS = ss { cursorPos = (cursorPos ss) + 1 }
-- Up Arrow
processEvent ss (EventSpecialKey KeyUpArrow) = Updated $ newSS
where newSS = ss { cursorPos = max 0 ((cursorPos ss) - 1) }
-- Enter
processEvent (SystemState qs _ cp _) (EventCharacter '\n') = res
where res = case (items . results) qs of
[] -> Exit
itemSet -> Selected $ itemSet !! cp
-- Ctrl D
processEvent _ (EventCharacter '\EOT') = Exit
-- Add Char
processEvent ss@(SystemState r rs _ _) (EventCharacter c) = Updated newSS
where newQ = addChar . query $ r
sStrat = compileSS (strat r)
newR = refine (results r) . sStrat $ newQ
newQS = r { query = newQ, results = newR }
newSS = ss { current = newQS, history = r:rs, cursorPos = 0 }
addChar (Query qry) = Query (qry ++ [c])
processEvent ss _ = Updated ss
printQuery :: Query -> AttrWrite
printQuery qry = writeAtLine 0 $ "$ " ++ (fmap f . q $ qry)
where f '\t' = '~'
f c = c
boldWrite :: AttrWrite -> AttrWrite
boldWrite = addAttr AttributeBold
addAttr :: Attribute -> AttrWrite -> AttrWrite
addAttr attr aw@(AttrWrite _ attrset _)
| attr `elem` attrset = aw
| otherwise = aw { attrs = (attr:attrset) }
printTopItems :: QueriedSet -> [AttrWrite]
printTopItems = zipWith writeAtLine [1..] . topItems
where topItems = fmap B.unpack . items . results
printStatus :: Int -> QueriedSet -> AttrWrite
printStatus total = iSimple RJustify Bottom . status . count
where count = show . size . results
status c = "[" ++ c ++ "/" ++ (show total) ++ "]"
writeAtLine :: Int -> String -> AttrWrite
writeAtLine r = iSimple LJustify (Line r)
-- Get query as first argument
getStrat :: [Flag] -> IO ScoreStrat
getStrat flags = return $ if CaseSensitive `elem` flags
then ss
else CILength ss
where ss = getSearchStrat flags
getSearchStrat :: [Flag] -> ScoreStrat
getSearchStrat flags
| SlopSearch `elem` flags = SlopLength
| otherwise = InfixLength
compileSS :: ScoreStrat -> Query -> [CQuery]
compileSS ss = fmap (liftSS ss) . splitQ
where splitQ = fmap B.unpack . pieces
pieces = B.split '\t' . B.pack . q
highlight :: QueriedSet -> AttrWrite -> [AttrWrite]
highlight (QueriedSet qry ss _) at = do
let scorer = compileSS ss qry
let res = range scorer . B.pack . content $ write at
maybe [at] (splitWrites at) res
splitWrites :: AttrWrite -> [(Int, Int)] -> [AttrWrite]
splitWrites atw [] = [atw]
splitWrites atw locs = foldr loop [atw] locs
where loop loc (at:rest) = (splitWrite at loc) ++ rest
loop _ ats = ats
splitWrite :: AttrWrite -> (Int, Int) -> [AttrWrite]
splitWrite at (lIdx, rIdx) = [lift left, newCenter, lift right]
where w = write at
(remaining, right) = split rIdx w
(left, center) = split lIdx remaining
lift w2 = at { write = w2 }
newCenter = at { write = center, highlighted = True }