-
-
Notifications
You must be signed in to change notification settings - Fork 47
/
Term.hs
356 lines (336 loc) · 11 KB
/
Term.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
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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
{-# LANGUAGE CPP #-}
module Termonad.Term where
import Termonad.Prelude
import Control.Lens ((^.), (&), (.~), set, to)
import Data.Colour.SRGB (Colour, RGB(RGB), toSRGB)
import GI.Gdk
( EventKey
, RGBA
, newZeroRGBA
, setRGBABlue
, setRGBAGreen
, setRGBARed
)
import GI.Gio
( noCancellable
)
import GI.GLib
( SpawnFlags(SpawnFlagsDefault)
)
import GI.Gtk
( Align(AlignFill)
, Box
, Button
, IconSize(IconSizeMenu)
, Label
, Notebook
, Orientation(OrientationHorizontal)
, PolicyType(PolicyTypeAlways, PolicyTypeAutomatic, PolicyTypeNever)
, ReliefStyle(ReliefStyleNone)
, ResponseType(ResponseTypeNo, ResponseTypeYes)
, ScrolledWindow
, applicationGetActiveWindow
, boxNew
, buttonNewFromIconName
, buttonSetRelief
, containerAdd
, dialogAddButton
, dialogGetContentArea
, dialogNew
, dialogRun
, labelNew
, labelSetEllipsize
, labelSetLabel
, labelSetMaxWidthChars
, noAdjustment
, notebookAppendPage
, notebookDetachTab
, notebookGetNPages
, notebookPageNum
, notebookSetCurrentPage
, notebookSetShowTabs
, notebookSetTabReorderable
, onButtonClicked
, onWidgetKeyPressEvent
, scrolledWindowNew
, scrolledWindowSetPolicy
, setWidgetMargin
, widgetDestroy
, widgetGrabFocus
, widgetSetCanFocus
, widgetSetFocusOnClick
, widgetSetHalign
, widgetSetHexpand
, widgetShow
, windowSetFocus
, windowSetTransientFor
)
import GI.Pango (EllipsizeMode(EllipsizeModeMiddle))
import GI.Vte
( PtyFlags(PtyFlagsDefault)
, Terminal
, onTerminalChildExited
, onTerminalWindowTitleChanged
, terminalGetWindowTitle
, terminalNew
, terminalSetCursorBlinkMode
, terminalSetFont
, terminalSetScrollbackLines
, terminalSpawnSync
, terminalSetWordCharExceptions
)
import System.FilePath ((</>))
import System.Directory (getSymbolicLinkTarget)
import System.Environment (lookupEnv)
import Termonad.FocusList (appendFL, deleteFL, getFLFocusItem)
import Termonad.Lenses
( lensConfirmExit
, lensOptions
, lensShowScrollbar
, lensShowTabBar
, lensTMNotebookTabLabel
, lensTMNotebookTabTerm
, lensTMNotebookTabTermContainer
, lensTMNotebookTabs
, lensTMStateApp
, lensTMStateConfig
, lensTMStateNotebook
, lensTerm
)
import Termonad.Types
( ConfigHooks(createTermHook)
, ConfigOptions(scrollbackLen, wordCharExceptions, cursorBlinkMode)
, ShowScrollbar(..)
, ShowTabBar(..)
, TMConfig(hooks, options)
, TMNotebookTab
, TMState
, TMState'(TMState, tmStateAppWin, tmStateConfig, tmStateFontDesc, tmStateNotebook)
, TMTerm
, assertInvariantTMState
, createTMNotebookTab
, newTMTerm
, pid
, tmNotebook
, tmNotebookTabTerm
, tmNotebookTabTermContainer
, tmNotebookTabs
)
focusTerm :: Int -> TMState -> IO ()
focusTerm i mvarTMState = do
note <- tmNotebook . tmStateNotebook <$> readMVar mvarTMState
notebookSetCurrentPage note (fromIntegral i)
altNumSwitchTerm :: Int -> TMState -> IO ()
altNumSwitchTerm = focusTerm
termExitFocused :: TMState -> IO ()
termExitFocused mvarTMState = do
tmState <- readMVar mvarTMState
let maybeTab =
tmState ^. lensTMStateNotebook . lensTMNotebookTabs . to getFLFocusItem
case maybeTab of
Nothing -> pure ()
Just tab -> termClose tab mvarTMState
termClose :: TMNotebookTab -> TMState -> IO ()
termClose tab mvarTMState = do
tmState <- readMVar mvarTMState
let confirm = tmState ^. lensTMStateConfig . lensOptions . lensConfirmExit
close = if confirm then termExitWithConfirmation else termExit
close tab mvarTMState
termExitWithConfirmation :: TMNotebookTab -> TMState -> IO ()
termExitWithConfirmation tab mvarTMState = do
tmState <- readMVar mvarTMState
let app = tmState ^. lensTMStateApp
win <- applicationGetActiveWindow app
dialog <- dialogNew
box <- dialogGetContentArea dialog
label <- labelNew (Just "Close tab?")
containerAdd box label
widgetShow label
setWidgetMargin label 10
void $
dialogAddButton
dialog
"No, do NOT close tab"
(fromIntegral (fromEnum ResponseTypeNo))
void $
dialogAddButton
dialog
"Yes, close tab"
(fromIntegral (fromEnum ResponseTypeYes))
windowSetTransientFor dialog win
res <- dialogRun dialog
widgetDestroy dialog
case toEnum (fromIntegral res) of
ResponseTypeYes -> termExit tab mvarTMState
_ -> pure ()
termExit :: TMNotebookTab -> TMState -> IO ()
termExit tab mvarTMState = do
detachTabAction <-
modifyMVar mvarTMState $ \tmState -> do
let notebook = tmStateNotebook tmState
detachTabAction =
notebookDetachTab
(tmNotebook notebook)
(tmNotebookTabTermContainer tab)
let newTabs = deleteFL tab (tmNotebookTabs notebook)
let newTMState =
set (lensTMStateNotebook . lensTMNotebookTabs) newTabs tmState
pure (newTMState, detachTabAction)
detachTabAction
relabelTabs mvarTMState
relabelTabs :: TMState -> IO ()
relabelTabs mvarTMState = do
TMState{tmStateNotebook} <- readMVar mvarTMState
let notebook = tmNotebook tmStateNotebook
tabFocusList = tmNotebookTabs tmStateNotebook
foldMap (go notebook) tabFocusList
where
go :: Notebook -> TMNotebookTab -> IO ()
go notebook tmNotebookTab = do
let label = tmNotebookTab ^. lensTMNotebookTabLabel
scrolledWin = tmNotebookTab ^. lensTMNotebookTabTermContainer
term' = tmNotebookTab ^. lensTMNotebookTabTerm . lensTerm
relabelTab notebook label scrolledWin term'
relabelTab :: Notebook -> Label -> ScrolledWindow -> Terminal -> IO ()
relabelTab notebook label scrolledWin term' = do
pageNum <- notebookPageNum notebook scrolledWin
maybeTitle <- terminalGetWindowTitle term'
let title = fromMaybe "shell" maybeTitle
labelSetLabel label $ tshow (pageNum + 1) <> ". " <> title
showScrollbarToPolicy :: ShowScrollbar -> PolicyType
showScrollbarToPolicy ShowScrollbarNever = PolicyTypeNever
showScrollbarToPolicy ShowScrollbarIfNeeded = PolicyTypeAutomatic
showScrollbarToPolicy ShowScrollbarAlways = PolicyTypeAlways
createScrolledWin :: TMState -> IO ScrolledWindow
createScrolledWin mvarTMState = do
tmState <- readMVar mvarTMState
let showScrollbarVal = tmState ^. lensTMStateConfig . lensOptions . lensShowScrollbar
vScrollbarPolicy = showScrollbarToPolicy showScrollbarVal
scrolledWin <- scrolledWindowNew noAdjustment noAdjustment
widgetShow scrolledWin
scrolledWindowSetPolicy scrolledWin PolicyTypeAutomatic vScrollbarPolicy
pure scrolledWin
createNotebookTabLabel :: IO (Box, Label, Button)
createNotebookTabLabel = do
box <- boxNew OrientationHorizontal 5
label <- labelNew (Just "")
labelSetEllipsize label EllipsizeModeMiddle
labelSetMaxWidthChars label 10
widgetSetHexpand label True
widgetSetHalign label AlignFill
button <-
buttonNewFromIconName
(Just "window-close")
(fromIntegral (fromEnum IconSizeMenu))
buttonSetRelief button ReliefStyleNone
containerAdd box label
containerAdd box button
widgetSetCanFocus button False
widgetSetFocusOnClick button False
widgetSetCanFocus label False
widgetSetFocusOnClick label False
widgetSetCanFocus box False
widgetSetFocusOnClick box False
widgetShow box
widgetShow label
widgetShow button
pure (box, label, button)
setShowTabs :: TMConfig -> Notebook -> IO ()
setShowTabs tmConfig note = do
npages <- notebookGetNPages note
let shouldShowTabs =
case tmConfig ^. lensOptions . lensShowTabBar of
ShowTabBarIfNeeded -> npages > 1
ShowTabBarAlways -> True
ShowTabBarNever -> False
notebookSetShowTabs note shouldShowTabs
toRGBA :: Colour Double -> IO RGBA
toRGBA colour = do
let RGB red green blue = toSRGB colour
rgba <- newZeroRGBA
setRGBARed rgba red
setRGBAGreen rgba green
setRGBABlue rgba blue
pure rgba
-- | TODO: This should probably be implemented in an external package,
-- since it is a generally useful utility.
--
-- It should also be implemented for windows and osx.
cwdOfPid :: Int -> IO (Maybe Text)
cwdOfPid pd = do
#ifdef mingw32_HOST_OS
pure Nothing
#else
#ifdef darwin_HOST_OS
pure Nothing
#else
let pidPath = "/proc" </> show pd </> "cwd"
eitherLinkTarget <- try $ getSymbolicLinkTarget pidPath
case eitherLinkTarget of
Left (_ :: IOException) -> pure Nothing
Right linkTarget -> pure $ Just $ pack linkTarget
#endif
#endif
createTerm :: (TMState -> EventKey -> IO Bool) -> TMState -> IO TMTerm
createTerm handleKeyPress mvarTMState = do
assertInvariantTMState mvarTMState
scrolledWin <- createScrolledWin mvarTMState
TMState{tmStateAppWin, tmStateFontDesc, tmStateConfig, tmStateNotebook=currNote} <-
readMVar mvarTMState
let maybeCurrFocusedTabPid = pid . tmNotebookTabTerm <$> getFLFocusItem (tmNotebookTabs currNote)
maybeCurrDir <- maybe (pure Nothing) cwdOfPid maybeCurrFocusedTabPid
vteTerm <- terminalNew
terminalSetFont vteTerm (Just tmStateFontDesc)
let curOpts = options tmStateConfig
terminalSetWordCharExceptions vteTerm $ wordCharExceptions curOpts
terminalSetScrollbackLines vteTerm (fromIntegral (scrollbackLen curOpts))
terminalSetCursorBlinkMode vteTerm (cursorBlinkMode curOpts)
widgetShow vteTerm
-- Should probably use GI.Vte.Functions.getUserShell, but contrary to its
-- documentation it raises an exception rather wrap in Maybe.
mShell <- lookupEnv "SHELL"
let argv = fromMaybe ["/usr/bin/env", "bash"] (pure <$> mShell)
terminalProcPid <-
terminalSpawnSync
vteTerm
[PtyFlagsDefault]
maybeCurrDir
argv
Nothing
([SpawnFlagsDefault] :: [SpawnFlags])
Nothing
noCancellable
tmTerm <- newTMTerm vteTerm (fromIntegral terminalProcPid)
containerAdd scrolledWin vteTerm
(tabLabelBox, tabLabel, tabCloseButton) <- createNotebookTabLabel
let notebookTab = createTMNotebookTab tabLabel scrolledWin tmTerm
void $
onButtonClicked tabCloseButton $
termClose notebookTab mvarTMState
mvarReturnAction <-
modifyMVar mvarTMState $ \tmState -> do
let notebook = tmStateNotebook tmState
note = tmNotebook notebook
tabs = tmNotebookTabs notebook
pageIndex <- notebookAppendPage note scrolledWin (Just tabLabelBox)
notebookSetTabReorderable note scrolledWin True
setShowTabs (tmState ^. lensTMStateConfig) note
let newTabs = appendFL tabs notebookTab
newTMState =
tmState & lensTMStateNotebook . lensTMNotebookTabs .~ newTabs
mvarReturnAction = notebookSetCurrentPage note pageIndex
pure (newTMState, mvarReturnAction)
mvarReturnAction
relabelTab (tmNotebook currNote) tabLabel scrolledWin vteTerm
void $ onTerminalWindowTitleChanged vteTerm $ do
TMState{tmStateNotebook} <- readMVar mvarTMState
let notebook = tmNotebook tmStateNotebook
relabelTab notebook tabLabel scrolledWin vteTerm
void $ onWidgetKeyPressEvent vteTerm $ handleKeyPress mvarTMState
void $ onWidgetKeyPressEvent scrolledWin $ handleKeyPress mvarTMState
void $ onTerminalChildExited vteTerm $ \_ -> termExit notebookTab mvarTMState
widgetGrabFocus vteTerm
windowSetFocus tmStateAppWin (Just vteTerm)
assertInvariantTMState mvarTMState
createTermHook (hooks tmStateConfig) mvarTMState vteTerm
pure tmTerm