summaryrefslogtreecommitdiff
path: root/interactive-edit-lens/src/Interact.hs
diff options
context:
space:
mode:
Diffstat (limited to 'interactive-edit-lens/src/Interact.hs')
-rw-r--r--interactive-edit-lens/src/Interact.hs271
1 files changed, 271 insertions, 0 deletions
diff --git a/interactive-edit-lens/src/Interact.hs b/interactive-edit-lens/src/Interact.hs
new file mode 100644
index 0000000..3aab5c2
--- /dev/null
+++ b/interactive-edit-lens/src/Interact.hs
@@ -0,0 +1,271 @@
1{-# LANGUAGE ScopedTypeVariables
2 , OverloadedStrings
3 #-}
4
5module Interact
6 ( interactiveEditLens
7 , module Interact.Types
8 , module Config.Dyre
9 ) where
10
11import Prelude hiding (init)
12
13import Interact.Types
14
15import Data.Text (Text)
16import qualified Data.Text as Text
17
18import Data.Text.Zipper
19
20import Data.Sequence (Seq)
21import qualified Data.Sequence as Seq
22
23import Control.Lens
24import Numeric.Lens
25import System.IO
26import Control.Monad
27import Control.Monad.RWS hiding (Last(..), (<>))
28import Control.Monad.Trans.Maybe
29import Control.Monad.Trans.Reader (runReaderT)
30
31import Data.Bool (bool)
32import Data.Tuple (swap)
33import Data.Maybe (fromMaybe)
34import Data.List (groupBy)
35import Data.Function (on)
36import Data.Char (isSpace)
37
38import Data.Foldable (Foldable(toList))
39
40import Brick hiding (on)
41import Brick.Focus
42import Brick.Widgets.Center
43import Brick.Widgets.Border
44import Graphics.Vty hiding (showCursor)
45
46import Config.Dyre
47
48import System.IO.Unsafe
49import Debug.Trace
50
51interactiveEditLens :: (Params (InteractConfig c) -> Params (InteractConfig c)) -> InteractConfig c -> IO ()
52interactiveEditLens f = wrapMain . f $ defaultParams
53 { projectName = "interact-edit-lens"
54 , showError = \s err -> s & compileError .~ Just err
55 , realMain = interactiveEditLens'
56 }
57
58interactiveEditLens' :: forall c. InteractConfig c -> IO ()
59interactiveEditLens' cfg@InteractConfig{..}
60 | Just err <- icfgCompileError
61 = hPutStrLn stderr err
62 | otherwise
63 = void . defaultMain app $! initialState &?~ do
64 let
65 a :: Lens' (InteractState c) (Last Validity, Last (Seq Char, Int), StringEdits Natural Char)
66 a = case icfgInitial of
67 InitialRight _ -> right
68 _other -> left
69 b :: Lens' (InteractState c) (Last Validity, Last (Seq Char, Int), StringEdits Natural Char)
70 b = case icfgInitial of
71 InitialRight _ -> left
72 _other -> right
73 dir :: InteractDirection
74 dir = case icfgInitial of
75 InitialRight _ -> PropagateLeft
76 _other -> PropagateRight
77 aDom :: Seq Char
78 (view charseq -> aDom) = case icfgInitial of
79 InitialRight t -> t
80 InitialLeft t -> t
81 InitialEmpty -> ""
82 doEdit $ divInit aDom & stringEdits . sePos %~ (fromIntegral :: Natural -> Integer)
83 -- a .= (Last Valid, Last (aDom, 0))
84 -- bEdit <- prop dir $ divInit aDom
85 -- (b %=) . maybe id (<>) <=< runMaybeT $ do
86 -- bDom <- use $ b . _2 . _Wrapped . _1
87 -- bDom' <- MaybeT . return $ bDom `apply` bEdit
88 -- return $ (Last Valid, Last (bDom', 0))
89 where
90 infix 1 &?~
91
92 (&?~) :: a -> RWS (InteractConfig c) () a b -> a
93 st &?~ act = (\(s, ()) -> s) $ execRWS act cfg st
94
95 actOn = (&?~)
96
97 initialState :: InteractState c
98 initialState = InteractState
99 { istComplement = ground icfgLens
100 , istLeft = (Last Valid, Last (init @(StringEdits Natural Char), 0), mempty)
101 , istRight = (Last Valid, Last (init @(StringEdits Natural Char), 0), mempty)
102 , istFocus = focusRing [LeftEditor, RightEditor] &
103 focusSetCurrent (case icfgInitial of InitialRight _ -> RightEditor; _other -> LeftEditor)
104 }
105
106 app :: InteractApp c
107 app = App{..}
108
109 appDraw :: InteractState c -> [Widget InteractName]
110 appDraw InteractState{..} = [ editors ]
111 where
112 editors = hBox
113 [ mbInvalid (withFocusRing istFocus renderEditor') (istLeft `WithName` LeftEditor)
114 , vBorder
115 , mbInvalid (withFocusRing istFocus renderEditor') (istRight `WithName` RightEditor)
116 ]
117 renderEditor' :: Bool -> (Seq Char, Int) `WithName` InteractName -> Widget InteractName
118 renderEditor' foc ((content, cPos) `WithName` n)
119 = txt (review charseq content)
120 & bool id (showCursor n cPos') foc
121 & visibleRegion cPos' (1, 1)
122 & viewport n Both
123 where
124 (cPrefix, _) = Seq.splitAt cPos content
125 newls = Seq.findIndicesR (== '\n') cPrefix
126 cPos' = case newls of
127 (p:_) -> Location (pred $ cPos - p, length newls)
128 [] -> Location (cPos, 0)
129 mbInvalid _ ((Last Invalid, _ , _) `WithName` _)
130 = txt "Invalid"
131 & border
132 & center
133 mbInvalid f ((Last Valid , Last x, _) `WithName` n) = f $ x `WithName` n
134
135 appHandleEvent :: InteractState c -> BrickEvent InteractName InteractEvent -> EventM InteractName (Next (InteractState c))
136 appHandleEvent st@InteractState{..} (VtyEvent ev) = case ev of
137 EvKey KEsc [] -> halt st
138 EvKey (KChar 'c') [MCtrl] -> halt st
139 EvKey (KChar '\t') [] -> continue $ st & focus %~ focusNext
140 EvKey KBackTab [] -> continue $ st & focus %~ focusPrev
141 EvKey (KChar 'a') [MCtrl] -> continue $ st &?~ doMove
142 (moveSplit (== '\n') $ \(c, (l, p)) -> if any (== '\n') (c l) || Seq.null (c l) then (pred l, 0) else (l, 0))
143 EvKey (KChar 'e') [MCtrl] -> continue $ st &?~ doMove
144 (moveSplit (== '\n') $ \(c, (l, p)) -> if any (== '\n') $ c l then (pred l, Seq.length . c $ pred l) else (l, Seq.length $ c l))
145 EvKey KLeft [MCtrl] -> continue $ st &?~ doMove
146 (moveSplit isSpace $ \(c, (l, _)) -> if any isSpace (c l) || Seq.null (c l) then (pred l, 0) else (l - 2, 0))
147 EvKey KRight [MCtrl] -> continue $ st &?~ doMove
148 (moveSplit isSpace $ \(c, (l, _)) -> if any isSpace $ c l then (succ l, 0) else (l + 2, 0))
149 EvKey KUp [MCtrl] -> continue $ st &?~ doMove
150 (moveSplit (== '\n') $ \(c, (l, p)) -> if any (== '\n') (c l) || Seq.null (c l) then (pred l, p) else (l - 2, p))
151 EvKey KDown [MCtrl] -> continue $ st &?~ doMove
152 (moveSplit (== '\n') $ \(c, (l, p)) -> if any (== '\n') (c l) then (succ l, p) else (l + 2, p))
153 EvKey KLeft [] -> continue $ st &?~ doMove moveLeft
154 EvKey KRight [] -> continue $ st &?~ doMove moveRight
155 EvKey KDel [] -> continue $ st &?~ doEdit (delete 0)
156 EvKey KBS [] -> continue . actOn st $ do
157 focused' <- preuse $ focused . _2 . _Wrapped
158 doEdit . delete $ -1
159 unless (maybe False ((==) <$> view _2 <*> view (_1 . to Seq.length)) focused') $
160 doMove moveLeft
161 EvKey (KChar c) [] -> continue . actOn st $ do
162 doEdit $ insert 0 c
163 doMove moveRight
164 EvKey KEnter [] -> continue . actOn st $ do
165 doEdit $ insert 0 '\n'
166 doMove moveRight
167 other -> suspendAndResume $ do
168 traceIO $ "Unhandled event:\n\t" ++ show other
169 return st
170 -- where
171 -- editorMovement f = continue $ st & focused . _Just . editContentsL %~ f
172 appHandleEvent st _ = continue st
173
174 doMove = zoom $ focused . _2 . _Wrapped
175
176 moveLeft, moveRight :: MonadState (Seq Char, Int) m => m ()
177 moveLeft = modify $ \now@(_, nowP) -> if
178 | nowP > 0 -> now & _2 %~ pred
179 | otherwise -> now
180 moveRight = modify $ \now@(contents, nowP) -> if
181 | nowP < length contents -> now & _2 %~ succ
182 | otherwise -> now
183
184 moveSplit :: MonadState (Seq Char, Int) m
185 => (Char -> Bool) -- ^ Separator predicate
186 -> (((Int -> Seq Char), (Int, Int)) -> (Int, Int)) -- ^ Move in split coordinates (e.g. @(line, charInLine)@) with access to the focused fragment
187 -> m ()
188 moveSplit splitPred relMove = modify $ \now@(toList -> contentsStr, nowP)
189 -> let splitContents = groupBy ((==) `on` splitPred) contentsStr
190 traceShow x y = flip seq y . unsafePerformIO . appendFile "interact.log" . (<> "\n\n") $ show x
191 (before, mCurrent, after) = snd . (\x -> traceShow (nowP, x) x) $ foldl go (0, ([], Nothing, [])) splitContents
192 go acc@(i, st) cGroup
193 | i <= nowP, nowP < i + length cGroup = (i + length cGroup, st & _2 .~ Just cGroup)
194 | i + length cGroup <= nowP = (i + length cGroup, st & _1 %~ (flip snoc cGroup))
195 | otherwise = (i + length cGroup, st & _3 %~ (flip snoc cGroup))
196 relPos = (length before, nowP - sum (map length before))
197 (newL, newS) = relMove (\i -> if 0 <= i && i < length splitContents then Seq.fromList $ splitContents !! i else Seq.empty, relPos)
198 newPos
199 | null splitContents
200 , newL /= 0 || newS /= 0 = (0, 0)
201 | newL >= length splitContents = (pred $ length splitContents, length $ last splitContents)
202 | newL < 0 = (0, 0)
203 | newS < 0 = (newL, 0)
204 | newS > length (splitContents !! newL) = (newL, length $ splitContents !! newL)
205 | otherwise = (newL, newS)
206 in now & _2 .~ sum (map length $ take (fst newPos) splitContents) + snd newPos
207
208 appStartEvent :: InteractState c -> EventM InteractName (InteractState c)
209 appStartEvent = return
210
211 appAttrMap :: InteractState c -> AttrMap
212 appAttrMap = const $ attrMap defAttr []
213
214 appChooseCursor :: InteractState c -> [CursorLocation InteractName] -> Maybe (CursorLocation InteractName)
215 appChooseCursor = focusRingCursor istFocus
216
217prop :: forall st cfg m.
218 ( MonadState st m
219 , MonadReader cfg m
220 , HasComplement st (Complement cfg)
221 , HasEditLens cfg (StringEdits Natural Char) (StringEdits Natural Char)
222 )
223 => InteractDirection -> StringEdits Natural Char -> m (StringEdits Natural Char)
224prop dir edits = do
225 propD <- case dir of
226 PropagateRight -> asks propR
227 PropagateLeft -> asks propL
228 (c, res) <- propD . (, edits) <$> use complement
229 unsafePerformIO . fmap return . appendFile "interact.log" . (<> "\n\n") $ show (edits, dir, res)
230 res <$ assign complement c
231
232doEdit :: forall m c.
233 ( MonadState (InteractState c) m
234 , MonadReader (InteractConfig c) m
235 )
236 => StringEdits Integer Char -> m ()
237doEdit relativeEdit = void . runMaybeT $ do
238 currentFocus <- MaybeT $ uses focus focusGetCurrent
239 let direction
240 | RightEditor <- currentFocus = PropagateLeft
241 | otherwise = PropagateRight
242 aL :: Lens' (InteractState c) (Last Validity, Last (Seq Char, Int), StringEdits Natural Char)
243 aL | PropagateRight <- direction = left
244 | PropagateLeft <- direction = right
245 bL :: Lens' (InteractState c) (Last Validity, Last (Seq Char, Int), StringEdits Natural Char)
246 bL | PropagateRight <- direction = right
247 | PropagateLeft <- direction = left
248 (aN, bN) = bool swap id (direction == PropagateRight) (LeftEditor, RightEditor)
249 currentZipper <- use $ aL . _2 . _Wrapped
250 let currentPos = currentZipper ^. _2
251 absoluteEdit <- MaybeT . return $ do
252 let minOffset = minimumOf (stringEdits . sePos) relativeEdit
253 guard $ maybe True (\o -> 0 <= currentPos + fromIntegral o) minOffset
254 return $ relativeEdit & stringEdits . sePos %~ (\n -> fromIntegral $ currentPos + fromIntegral n)
255 newContent <- MaybeT . return $ view _1 currentZipper `apply` absoluteEdit
256 let currentPos'
257 | currentPos < 0 = 0
258 | currentPos > length newContent = length newContent
259 | otherwise = currentPos
260 aL . _2 %= (<> Last (newContent, currentPos'))
261 absoluteEdit' <- uses (aL . _3) (absoluteEdit `mappend`)
262 bEdits <- prop direction absoluteEdit'
263 bDom <- use $ bL . _2 . _Wrapped . _1
264 case bDom `apply` bEdits of
265 Nothing -> do
266 bL . _1 %= (<> Last Invalid)
267 aL . _3 .= absoluteEdit'
268 Just bDom' -> do
269 bL . _1 %= (<> Last Valid)
270 bL . _2 . _Wrapped . _1 .= bDom'
271 aL . _3 .= mempty