@@ -20,20 +20,36 @@ module Swarm.TUI.Model (
20
20
Modal (.. ),
21
21
22
22
-- * UI state
23
+
24
+ -- ** REPL
23
25
REPLHistItem (.. ),
24
- firstReplEntry ,
26
+ replItemText ,
27
+ isREPLEntry ,
28
+ getREPLEntry ,
29
+ REPLHistory ,
30
+ replIndex ,
31
+ replLength ,
32
+ newREPLHistory ,
33
+ addREPLItem ,
34
+ restartREPLHistory ,
35
+ getLatestREPLHistoryItems ,
36
+ moveReplHistIndex ,
37
+ getCurrentItemText ,
38
+ replIndexIsAtInput ,
39
+ TimeDir (.. ),
40
+
41
+ -- ** Inventory
25
42
InventoryListEntry (.. ),
26
43
_Separator ,
27
44
_InventoryEntry ,
28
45
_InstalledEntry ,
29
- UIState ,
30
46
31
- -- ** Fields
47
+ -- ** UI Model
48
+ UIState ,
32
49
uiFocusRing ,
33
50
uiReplForm ,
34
51
uiReplType ,
35
52
uiReplHistory ,
36
- uiReplHistIdx ,
37
53
uiReplLast ,
38
54
uiInventory ,
39
55
uiMoreInfoTop ,
@@ -78,19 +94,22 @@ module Swarm.TUI.Model (
78
94
import Control.Lens
79
95
import Control.Monad.Except
80
96
import Control.Monad.State
97
+ import Data.Bits (FiniteBits (finiteBitSize ))
98
+ import Data.Foldable (toList )
81
99
import Data.List (findIndex , sortOn )
82
- import Data.Maybe (fromMaybe )
100
+ import Data.Maybe (fromMaybe , isJust )
101
+ import Data.Sequence (Seq )
102
+ import qualified Data.Sequence as Seq
83
103
import Data.Text (Text )
104
+ import qualified Data.Text as T
84
105
import qualified Data.Vector as V
85
106
import System.Clock
86
- import Text.Read (readMaybe )
87
107
88
108
import Brick
89
109
import Brick.Focus
90
110
import Brick.Forms
91
111
import qualified Brick.Widgets.List as BL
92
112
93
- import Data.Bits (FiniteBits (finiteBitSize ))
94
113
import Swarm.Game.Entity as E
95
114
import Swarm.Game.Robot
96
115
import Swarm.Game.State
@@ -138,39 +157,126 @@ data Name
138
157
infoScroll :: ViewportScroll Name
139
158
infoScroll = viewportScroll InfoViewport
140
159
141
- data Modal
142
- = HelpModal
143
- deriving (Eq , Show )
144
-
145
160
------------------------------------------------------------
146
- -- UI state
161
+ -- REPL History
147
162
------------------------------------------------------------
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
168
- -- | Given a REPL history return @Just@ the most recent @Text@
169
- -- 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
172
+ -- | Useful helper function to only get user input text.
173
+ getREPLEntry :: REPLHistItem -> Maybe Text
174
+ getREPLEntry = \ case
175
+ REPLEntry t -> Just t
176
+ _ -> Nothing
177
+
178
+ -- | Useful helper function to filter out REPL output.
179
+ isREPLEntry :: REPLHistItem -> Bool
180
+ isREPLEntry = isJust . getREPLEntry
181
+
182
+ -- | Get the text of REPL input/output.
183
+ replItemText :: REPLHistItem -> Text
184
+ replItemText = \ case
185
+ REPLEntry t -> t
186
+ REPLOutput t -> t
187
+
188
+ -- | History of the REPL with indices (0 is first entry) to the current
189
+ -- line and to the first entry since loading saved history.
190
+ -- We also (ab)use the length of the REPL as the index of current
191
+ -- input line, since that number is one past the index of last entry.
192
+ data REPLHistory = REPLHistory
193
+ { _replSeq :: Seq REPLHistItem
194
+ , _replIndex :: Int
195
+ , _replStart :: Int
196
+ }
197
+
198
+ makeLensesWith (lensRules & generateSignatures .~ False ) ''REPLHistory
199
+
200
+ -- | Sequence of REPL inputs and outputs, oldest entry is leftmost.
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
+ --
209
+ -- It will be set on load and reset on save (happens during exit).
210
+ replStart :: Lens' REPLHistory Int
211
+
212
+ -- | Create new REPL history (i.e. from loaded history file lines).
213
+ newREPLHistory :: [REPLHistItem ] -> REPLHistory
214
+ newREPLHistory xs =
215
+ let s = Seq. fromList xs
216
+ in REPLHistory
217
+ { _replSeq = s
218
+ , _replStart = length s
219
+ , _replIndex = length s
220
+ }
221
+
222
+ -- | Point the start of REPL history after current last line. See 'replStart'.
223
+ restartREPLHistory :: REPLHistory -> REPLHistory
224
+ restartREPLHistory h = h & replStart .~ replLength h
225
+
226
+ -- | Current number lines of the REPL history - (ab)used as index of input buffer.
227
+ replLength :: REPLHistory -> Int
228
+ replLength = length . _replSeq
229
+
230
+ -- | Add new REPL input - the index must have been pointing one past
231
+ -- the last element already, so we increment it to keep it that way.
232
+ addREPLItem :: REPLHistItem -> REPLHistory -> REPLHistory
233
+ addREPLItem t h =
234
+ h
235
+ & replSeq %~ (|> t)
236
+ & replIndex .~ 1 + replLength h
237
+
238
+ -- | Get the latest N items in history, starting with the oldest one.
239
+ --
240
+ -- This is used to show previous REPL lines in UI, so we need the items
241
+ -- sorted in the order they were entered and will be drawn top to bottom.
242
+ getLatestREPLHistoryItems :: Int -> REPLHistory -> [REPLHistItem ]
243
+ getLatestREPLHistoryItems n h = toList latestN
244
+ where
245
+ latestN = Seq. drop oldestIndex $ h ^. replSeq
246
+ oldestIndex = max (h ^. replStart) $ length (h ^. replSeq) - n
247
+
248
+ data TimeDir = Newer | Older deriving (Eq , Ord , Show )
249
+
250
+ moveReplHistIndex :: TimeDir -> Text -> REPLHistory -> REPLHistory
251
+ moveReplHistIndex d lastEntered history = history & replIndex .~ newIndex
252
+ where
253
+ historyLen = replLength history
254
+ curText = fromMaybe lastEntered $ getCurrentItemText history
255
+ curIndex = history ^. replIndex
256
+ entries = history ^. replSeq
257
+ -- split repl at index
258
+ (olderP, newer) = Seq. splitAt curIndex entries
259
+ -- find first different entry in direction
260
+ notSameEntry = \ case
261
+ REPLEntry t -> t /= curText
262
+ _ -> False
263
+ newIndex = case d of
264
+ Newer -> maybe historyLen (curIndex + ) $ Seq. findIndexL notSameEntry newer
265
+ Older -> fromMaybe curIndex $ Seq. findIndexR notSameEntry olderP
266
+
267
+ getCurrentItemText :: REPLHistory -> Maybe Text
268
+ getCurrentItemText history = replItemText <$> Seq. lookup (history ^. replIndex) (history ^. replSeq)
269
+
270
+ replIndexIsAtInput :: REPLHistory -> Bool
271
+ replIndexIsAtInput repl = repl ^. replIndex == replLength repl
272
+
273
+ ------------------------------------------------------------
274
+ -- UI state
275
+ ------------------------------------------------------------
276
+
277
+ data Modal
278
+ = HelpModal
279
+ deriving (Eq , Show )
174
280
175
281
-- | An entry in the inventory list displayed in the info panel. We
176
282
-- can either have an entity with a count in the robot's inventory,
@@ -192,8 +298,7 @@ data UIState = UIState
192
298
, _uiReplForm :: Form Text AppEvent Name
193
299
, _uiReplType :: Maybe Polytype
194
300
, _uiReplLast :: Text
195
- , _uiReplHistory :: [REPLHistItem ]
196
- , _uiReplHistIdx :: Int
301
+ , _uiReplHistory :: REPLHistory
197
302
, _uiInventory :: Maybe (Int , BL. List Name InventoryListEntry )
198
303
, _uiMoreInfoTop :: Bool
199
304
, _uiMoreInfoBot :: Bool
@@ -238,11 +343,7 @@ uiReplLast :: Lens' UIState Text
238
343
239
344
-- | History of things the user has typed at the REPL, interleaved
240
345
-- 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
346
+ uiReplHistory :: Lens' UIState REPLHistory
246
347
247
348
-- | The hash value of the focused robot entity (so we can tell if its
248
349
-- inventory changed) along with a list of the items in the
@@ -340,15 +441,15 @@ initLgTicksPerSecond = 3 -- 2^3 = 8 ticks / second
340
441
-- time.
341
442
initUIState :: ExceptT Text IO UIState
342
443
initUIState = liftIO $ do
343
- mhist <- (>>= readMaybe @ [REPLHistItem ]) <$> readFileMay " .swarm_history"
444
+ historyT <- readFileMayT =<< getSwarmHistoryPath False
445
+ let history = maybe [] (map REPLEntry . T. lines ) historyT
344
446
startTime <- getTime Monotonic
345
447
return $
346
448
UIState
347
449
{ _uiFocusRing = initFocusRing
348
450
, _uiReplForm = initReplForm
349
451
, _uiReplType = Nothing
350
- , _uiReplHistory = mhist ? []
351
- , _uiReplHistIdx = - 1
452
+ , _uiReplHistory = newREPLHistory history
352
453
, _uiReplLast = " "
353
454
, _uiInventory = Nothing
354
455
, _uiMoreInfoTop = False
0 commit comments