@@ -20,20 +20,33 @@ module Swarm.TUI.Model (
20
20
Modal (.. ),
21
21
22
22
-- * UI state
23
+
24
+ -- ** REPL
23
25
REPLHistItem (.. ),
24
26
firstReplEntry ,
27
+ replItemText ,
28
+ isREPLEntry ,
29
+ getREPLEntry ,
30
+ REPLHistory ,
31
+ replSeq ,
32
+ replStart ,
33
+ replIndex ,
34
+ addREPLEntry ,
35
+ addREPLOutput ,
36
+ replLength ,
37
+
38
+ -- ** Inventory
25
39
InventoryListEntry (.. ),
26
40
_Separator ,
27
41
_InventoryEntry ,
28
42
_InstalledEntry ,
29
- UIState ,
30
43
31
- -- ** Fields
44
+ -- ** UI Model
45
+ UIState ,
32
46
uiFocusRing ,
33
47
uiReplForm ,
34
48
uiReplType ,
35
49
uiReplHistory ,
36
- uiReplHistIdx ,
37
50
uiReplLast ,
38
51
uiInventory ,
39
52
uiMoreInfoTop ,
@@ -79,7 +92,7 @@ import Control.Lens
79
92
import Control.Monad.Except
80
93
import Control.Monad.State
81
94
import Data.List (findIndex , sortOn )
82
- import Data.Maybe (fromMaybe )
95
+ import Data.Maybe (fromMaybe , isJust )
83
96
import Data.Text (Text )
84
97
import qualified Data.Text as T
85
98
import qualified Data.Vector as V
@@ -91,6 +104,8 @@ import Brick.Forms
91
104
import qualified Brick.Widgets.List as BL
92
105
93
106
import Data.Bits (FiniteBits (finiteBitSize ))
107
+ import Data.Sequence (Seq )
108
+ import qualified Data.Sequence as Seq
94
109
import Swarm.Game.Entity as E
95
110
import Swarm.Game.Robot
96
111
import Swarm.Game.State
@@ -148,29 +163,74 @@ data Modal
148
163
149
164
-- | An item in the REPL history.
150
165
data REPLHistItem
151
- = -- | Something entered by the user. The first
152
- -- @Bool@ indicates whether it is
153
- -- something entered this session (it
154
- -- will be @False@ for entries that were
155
- -- loaded from the history file). This is
156
- -- so we know which ones to append to the
157
- -- history file on shutdown.
158
- -- The second @Bool@ indicates whether it
159
- -- is a duplicate of the preceding item (it
160
- -- will be @True@ for duplicate entries).
161
- -- This is so we can ignore it when scrolling
162
- -- through the REPL history in the REPL window.
163
- REPLEntry Bool Bool Text
166
+ = -- | Something entered by the user.
167
+ REPLEntry Text
164
168
| -- | A response printed by the system.
165
169
REPLOutput Text
166
170
deriving (Eq , Ord , Show , Read )
167
171
172
+ getREPLEntry :: REPLHistItem -> Maybe Text
173
+ getREPLEntry = \ case
174
+ REPLEntry t -> Just t
175
+ _ -> Nothing
176
+
177
+ isREPLEntry :: REPLHistItem -> Bool
178
+ isREPLEntry = isJust . getREPLEntry
179
+
180
+ replItemText :: REPLHistItem -> Text
181
+ replItemText = \ case
182
+ REPLEntry t -> t
183
+ REPLOutput t -> t
184
+
185
+ -- | History of the REPL with indices (0 is first entry) to the current
186
+ -- line and to the first entry since loading saved history.
187
+ -- We also (ab)use the length of the REPL as the index of current
188
+ -- input line, since that number is one past the index of last entry.
189
+ data REPLHistory = REPLHistory
190
+ { _replSeq :: Seq REPLHistItem
191
+ , _replIndex :: Int
192
+ , _replStart :: Int
193
+ }
194
+
195
+ makeLensesWith (lensRules & generateSignatures .~ False ) ''REPLHistory
196
+
197
+ -- | Sequence of REPL inputs and outputs, oldest entry is leftmost.
198
+ --
199
+ -- TODO: The ideal would be not to expose inner storage, but currently
200
+ -- namely the index+predicate functions on Seq are hard to resist.
201
+ replSeq :: Lens' REPLHistory (Seq REPLHistItem )
202
+
203
+ -- | The current index in the REPL history (if the user is going back
204
+ -- through the history using up/down keys).
205
+ replIndex :: Lens' REPLHistory Int
206
+
207
+ -- | The index of the first entry since loading saved history.
208
+ replStart :: Lens' REPLHistory Int
209
+
210
+ -- | Current (vertical) length of the REPL - used as index of input buffer.
211
+ replLength :: REPLHistory -> Int
212
+ replLength = length . _replSeq
213
+
214
+ -- | Add new REPL input - the index must have been pointing one past
215
+ -- the last element already, so we increment it to keep it that way.
216
+ addREPLItem :: (Text -> REPLHistItem ) -> Text -> REPLHistory -> REPLHistory
217
+ addREPLItem rc t h = h
218
+ & replSeq %~ (|> rc t)
219
+ & replIndex .~ replLength h
220
+
221
+ -- | Add new REPL input and increment index to last.
222
+ addREPLEntry :: Text -> REPLHistory -> REPLHistory
223
+ addREPLEntry = addREPLItem REPLEntry
224
+
225
+
226
+ -- | Add new REPL output and increment index to last.
227
+ addREPLOutput :: Text -> REPLHistory -> REPLHistory
228
+ addREPLOutput = addREPLItem REPLOutput
229
+
168
230
-- | Given a REPL history return @Just@ the most recent @Text@
169
231
-- entered by the user or @Nothing@ if there is none.
170
- firstReplEntry :: [REPLHistItem ] -> Maybe Text
171
- firstReplEntry ((REPLEntry _ _ entry) : _) = Just entry
172
- firstReplEntry (_ : rest) = firstReplEntry rest
173
- firstReplEntry [] = Nothing
232
+ firstReplEntry :: REPLHistory -> Maybe Text
233
+ firstReplEntry = foldr (const . getREPLEntry) Nothing . _replSeq
174
234
175
235
-- | An entry in the inventory list displayed in the info panel. We
176
236
-- can either have an entity with a count in the robot's inventory,
@@ -192,8 +252,7 @@ data UIState = UIState
192
252
, _uiReplForm :: Form Text AppEvent Name
193
253
, _uiReplType :: Maybe Polytype
194
254
, _uiReplLast :: Text
195
- , _uiReplHistory :: [REPLHistItem ]
196
- , _uiReplHistIdx :: Int
255
+ , _uiReplHistory :: REPLHistory
197
256
, _uiInventory :: Maybe (Int , BL. List Name InventoryListEntry )
198
257
, _uiMoreInfoTop :: Bool
199
258
, _uiMoreInfoBot :: Bool
@@ -238,11 +297,7 @@ uiReplLast :: Lens' UIState Text
238
297
239
298
-- | History of things the user has typed at the REPL, interleaved
240
299
-- with outputs the system has generated.
241
- uiReplHistory :: Lens' UIState [REPLHistItem ]
242
-
243
- -- | The current index in the REPL history (if the user is going back
244
- -- through the history using up/down keys).
245
- uiReplHistIdx :: Lens' UIState Int
300
+ uiReplHistory :: Lens' UIState REPLHistory
246
301
247
302
-- | The hash value of the focused robot entity (so we can tell if its
248
303
-- inventory changed) along with a list of the items in the
@@ -335,27 +390,26 @@ initReplForm =
335
390
initLgTicksPerSecond :: Int
336
391
initLgTicksPerSecond = 3 -- 2^3 = 8 ticks / second
337
392
338
- createHistory :: [Text ] -> [REPLHistItem ]
339
- createHistory = map (uncurry $ REPLEntry False ) . zipLines . reverse
340
- where
341
- zipLines ls = zipWith samePair ls $ tail ls ++ [" " ]
342
- samePair n o = (n == o, n)
343
-
344
393
-- | Initialize the UI state. This needs to be in the IO monad since
345
394
-- it involves reading a REPL history file and getting the current
346
395
-- time.
347
396
initUIState :: ExceptT Text IO UIState
348
397
initUIState = liftIO $ do
349
398
historyT <- readFileMayT =<< getSwarmHistoryPath False
350
- let history = maybe [] (createHistory . T. lines ) historyT
399
+ let history = maybe Seq. empty (Seq. fromList . map REPLEntry . T. lines ) historyT
400
+ historyLen = length history
351
401
startTime <- getTime Monotonic
352
402
return $
353
403
UIState
354
404
{ _uiFocusRing = initFocusRing
355
405
, _uiReplForm = initReplForm
356
406
, _uiReplType = Nothing
357
- , _uiReplHistory = history
358
- , _uiReplHistIdx = - 1
407
+ , _uiReplHistory =
408
+ REPLHistory
409
+ { _replSeq = history
410
+ , _replIndex = historyLen -- one past rightmost=newest
411
+ , _replStart = historyLen
412
+ }
359
413
, _uiReplLast = " "
360
414
, _uiInventory = Nothing
361
415
, _uiMoreInfoTop = False
0 commit comments