mirror of
https://github.com/CrystalSplitter/ghcitui.git
synced 2024-09-11 10:55:29 +03:00
Add Help and Exit dialogs
This commit is contained in:
parent
53c3be7c9a
commit
1ea4e95ba1
@ -47,6 +47,8 @@ data ActiveWindow
|
||||
= ActiveCodeViewport
|
||||
| ActiveLiveInterpreter
|
||||
| ActiveInfoWindow
|
||||
| ActiveDialogQuit
|
||||
| ActiveDialogHelp
|
||||
deriving (Show, Eq, Ord)
|
||||
|
||||
data MaxState = NoMaxState | Maximised | Minimised
|
||||
|
@ -7,6 +7,7 @@ data AppName
|
||||
| CodeViewportLine Int
|
||||
| LiveInterpreter
|
||||
| LiveInterpreterViewport
|
||||
| HelpViewport
|
||||
| BindingViewport
|
||||
| ModulesViewport
|
||||
| TraceViewport
|
||||
|
139
app/BrickUI.hs
139
app/BrickUI.hs
@ -10,6 +10,7 @@ import qualified Brick as B
|
||||
import qualified Brick.Widgets.Border as B
|
||||
import qualified Brick.Widgets.Center as B
|
||||
import Brick.Widgets.Core ((<+>), (<=>))
|
||||
import qualified Brick.Widgets.Dialog as B
|
||||
import qualified Brick.Widgets.Edit as BE
|
||||
import Control.Error (headMay)
|
||||
import Data.Bifunctor (second)
|
||||
@ -33,6 +34,7 @@ import qualified AppState
|
||||
import AppTopLevel (AppName (..))
|
||||
import qualified Events
|
||||
import qualified Ghcid.Daemon as Daemon
|
||||
import qualified HelpText
|
||||
import qualified Loc
|
||||
import qualified NameBinding
|
||||
import qualified Util
|
||||
@ -42,16 +44,60 @@ type AppS = AppState AppName
|
||||
|
||||
appDraw :: AppS -> [B.Widget AppName]
|
||||
appDraw s =
|
||||
[ (viewportBox <=> interpreterBox <=> debugBox)
|
||||
[ drawDialogLayer s
|
||||
, drawBaseLayer s
|
||||
]
|
||||
|
||||
dialogMaxWidth :: (Integral a) => a
|
||||
dialogMaxWidth = 90
|
||||
|
||||
{- | Draw the dialog layer.
|
||||
|
||||
If there's no dialog, returns an 'emptyWidget'.
|
||||
-}
|
||||
drawDialogLayer :: AppS -> B.Widget AppName
|
||||
-- Quit Dialog
|
||||
drawDialogLayer AppState{activeWindow = ActiveDialogQuit} =
|
||||
B.withAttr (B.attrName "dialog") $ B.renderDialog dialogObj body
|
||||
where
|
||||
dialogObj = B.dialog (Just titleW) Nothing dialogMaxWidth
|
||||
titleW = B.txt "Please don't go. The drones need you. They look up to you."
|
||||
body =
|
||||
B.hCenter
|
||||
(B.padAll 1 (B.txt "Do you want to halt the current program and quit?"))
|
||||
<=> B.hCenter (B.padAll 1 (B.txt "[Enter] -> QUIT" <=> B.txt "[Esc/q] -> Go back"))
|
||||
-- Help Dialog
|
||||
drawDialogLayer AppState{activeWindow = ActiveDialogHelp} =
|
||||
B.withAttr (B.attrName "dialog") $ B.renderDialog dialogObj body
|
||||
where
|
||||
dialogObj = B.dialog (Just titleW) Nothing dialogMaxWidth
|
||||
titleW = B.txt "Actually reading the manual, huh?"
|
||||
body =
|
||||
( B.hCenter
|
||||
. B.withVScrollBars B.OnRight
|
||||
. B.viewport HelpViewport B.Vertical
|
||||
$ B.padAll 1 (B.txt HelpText.helpText)
|
||||
)
|
||||
<=> ( B.hCenter
|
||||
. B.padAll 1
|
||||
$ B.txt "[Esc/Enter/q] -> Go back"
|
||||
)
|
||||
-- No Dialog
|
||||
drawDialogLayer _ = B.emptyWidget
|
||||
|
||||
drawBaseLayer :: AppS -> B.Widget AppName
|
||||
drawBaseLayer s =
|
||||
verticalBoxes
|
||||
-- TODO: Make this an expandable viewport, maybe?
|
||||
<+> infoBox s
|
||||
]
|
||||
where
|
||||
verticalBoxes = viewportBox <=> interpreterBox <=> debugBox
|
||||
sourceLabel =
|
||||
markLabel
|
||||
(s.activeWindow == ActiveCodeViewport)
|
||||
( "Source: " <> maybe "?" T.pack s.selectedFile
|
||||
)
|
||||
"[Esc]"
|
||||
interpreterLabel =
|
||||
markLabel
|
||||
(s.activeWindow == ActiveLiveInterpreter)
|
||||
@ -59,6 +105,7 @@ appDraw s =
|
||||
then "Interpreter"
|
||||
else "Interpreter (Scrolling)"
|
||||
)
|
||||
"[Ctrl+x]"
|
||||
|
||||
-- For seeing the source code.
|
||||
viewportBox :: B.Widget AppName
|
||||
@ -209,6 +256,10 @@ markLabel False labelTxt focus = B.txt . appendFocusButton $ labelTxt
|
||||
markLabel True labelTxt _ =
|
||||
B.withAttr (B.attrName "highlight") (B.txt ("#> " <> labelTxt <> " <#"))
|
||||
|
||||
-- -------------------------------------------------------------------------------------------------
|
||||
-- Code Viewport Drawing
|
||||
-- -------------------------------------------------------------------------------------------------
|
||||
|
||||
-- | Information used to compute the gutter status of each line.
|
||||
data GutterInfo = GutterInfo
|
||||
{ isStoppedHere :: !Bool
|
||||
@ -272,6 +323,46 @@ codeViewportDraw s =
|
||||
codeViewportDraw' :: AppS -> T.Text -> B.Widget AppName
|
||||
codeViewportDraw' s sourceData = composedTogether
|
||||
where
|
||||
composedTogether :: B.Widget AppName
|
||||
composedTogether = B.vBox (createWidget <$> windowedSplitSourceData)
|
||||
where
|
||||
wrapSelectedLine lineno w =
|
||||
if lineno == s.selectedLine
|
||||
then -- Add highlighting, then mark it as visible in the viewport.
|
||||
B.visible $ B.modifyDefAttr (`V.withStyle` V.bold) w
|
||||
else w
|
||||
createWidget (num, lineTxt) = wrapSelectedLine num (composedTogetherHelper (num, lineTxt))
|
||||
|
||||
-- Select which line widget we want to draw based on both the interpreter
|
||||
-- state and the app state.
|
||||
--
|
||||
-- It's important that the line information is cached, because
|
||||
-- each line is actually pretty expensive to render.
|
||||
composedTogetherHelper :: (Int, T.Text) -> B.Widget AppName
|
||||
composedTogetherHelper (lineno, lineTxt) = lineWidgetCached
|
||||
where
|
||||
sr = maybe Loc.unknownSourceRange Loc.sourceRange (Daemon.pauseLoc (interpState s))
|
||||
mLineno = Loc.singleify sr
|
||||
lineWidget = case mLineno of
|
||||
-- This only makes the stopped line widget appear for the start loc.
|
||||
Just (singleLine, _) | lineno == singleLine -> stoppedLineW lineTxt
|
||||
-- If it's a range, just try to show the range.
|
||||
_
|
||||
| Loc.isLineInside sr lineno -> stoppedRangeW
|
||||
-- If it's not something we stopped in, just show the selection normally.
|
||||
_
|
||||
| lineno == s.selectedLine -> selectedLineW lineTxt
|
||||
-- Default case.
|
||||
_ -> ((\w -> prefixLineDefault' (lineno, w)) . B.txt) lineTxt
|
||||
lineWidgetCached = B.cached (CodeViewportLine lineno) lineWidget
|
||||
|
||||
stoppedRangeW :: B.Widget AppName
|
||||
stoppedRangeW =
|
||||
prefixLineDefault'
|
||||
( lineno
|
||||
, B.forceAttrAllowStyle (B.attrName "stop-line") (B.txt lineTxt)
|
||||
)
|
||||
|
||||
-- Source data split on lines.
|
||||
splitSourceData = T.lines sourceData
|
||||
-- Source data split on lines, but only the bit we may want to render.
|
||||
@ -287,9 +378,6 @@ codeViewportDraw' s sourceData = composedTogether
|
||||
startLineno = 1
|
||||
withLineNums = zip [startLineno ..]
|
||||
|
||||
breakpoints :: [Int]
|
||||
breakpoints = maybe mempty (\f -> Daemon.getBpInFile f (interpState s)) (selectedFile s)
|
||||
|
||||
gutterInfoForLine :: Int -> GutterInfo
|
||||
gutterInfoForLine lineno =
|
||||
GutterInfo
|
||||
@ -303,6 +391,9 @@ codeViewportDraw' s sourceData = composedTogether
|
||||
, gutterDigitWidth = Util.getNumDigits $ length splitSourceData
|
||||
, isSelected = lineno == s.selectedLine
|
||||
}
|
||||
where
|
||||
breakpoints :: [Int]
|
||||
breakpoints = maybe mempty (\f -> Daemon.getBpInFile f (interpState s)) (selectedFile s)
|
||||
|
||||
prefixLineDefault' :: (Int, B.Widget n) -> B.Widget n
|
||||
prefixLineDefault' (lineno, w) =
|
||||
@ -323,49 +414,12 @@ codeViewportDraw' s sourceData = composedTogether
|
||||
lineWidget = makeStoppedLineWidget lineTxt (startCol, endCol)
|
||||
in prefixLineDefault' (originalLookupLineNo, lineWidget)
|
||||
|
||||
stoppedRangeW :: Int -> T.Text -> B.Widget AppName
|
||||
stoppedRangeW lineno lineTxt =
|
||||
prefixLineDefault' (lineno, B.forceAttrAllowStyle (B.attrName "stop-line") (B.txt lineTxt))
|
||||
|
||||
selectedLineW :: T.Text -> B.Widget AppName
|
||||
selectedLineW lineTxt =
|
||||
let transform = id -- Potentialy useful for highlighting spaces?
|
||||
lineWidget = B.txt $ transform lineTxt
|
||||
in prefixLineDefault' (s.selectedLine, lineWidget)
|
||||
|
||||
-- Select which line widget we want to draw based on both the interpreter
|
||||
-- state and the app state.
|
||||
--
|
||||
-- It's important that the line information is cached, because
|
||||
-- each line is actually pretty expensive to render.
|
||||
composedTogetherHelper :: (Int, T.Text) -> B.Widget AppName
|
||||
composedTogetherHelper (lineno, lineTxt) = lineWidgetCached
|
||||
where
|
||||
sr = maybe Loc.unknownSourceRange Loc.sourceRange (Daemon.pauseLoc (interpState s))
|
||||
mLineno = Loc.singleify sr
|
||||
lineWidget = case mLineno of
|
||||
-- This only makes the stopped line widget appear for the start loc.
|
||||
Just (singleLine, _) | lineno == singleLine -> stoppedLineW lineTxt
|
||||
-- If it's a range, just try to show the range.
|
||||
_
|
||||
| Loc.isLineInside sr lineno -> stoppedRangeW lineno lineTxt
|
||||
-- If it's not something we stopped in, just show the selection normally.
|
||||
_
|
||||
| lineno == s.selectedLine -> selectedLineW lineTxt
|
||||
-- Default case.
|
||||
_ -> ((\w -> prefixLineDefault' (lineno, w)) . B.txt) lineTxt
|
||||
lineWidgetCached = B.cached (CodeViewportLine lineno) lineWidget
|
||||
|
||||
composedTogether :: B.Widget AppName
|
||||
composedTogether = B.vBox (createWidget <$> windowedSplitSourceData)
|
||||
where
|
||||
wrapSelectedLine lineno w =
|
||||
if lineno == s.selectedLine
|
||||
then -- Add highlighting, then mark it as visible in the viewport.
|
||||
B.visible $ B.modifyDefAttr (`V.withStyle` V.bold) w
|
||||
else w
|
||||
createWidget (num, lineTxt) = wrapSelectedLine num (composedTogetherHelper (num, lineTxt))
|
||||
|
||||
-- | Make the Stopped Line widget (the line where we paused execution)
|
||||
makeStoppedLineWidget :: T.Text -> Loc.ColumnRange -> B.Widget AppName
|
||||
makeStoppedLineWidget lineData (Nothing, _) =
|
||||
@ -408,6 +462,7 @@ brickApp =
|
||||
, (B.attrName "underline", B.style V.underline)
|
||||
, (B.attrName "styled", B.fg V.magenta `V.withStyle` V.bold)
|
||||
, (B.attrName "highlight", B.style V.standout)
|
||||
, (B.attrName "dialog", B.style V.standout)
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,14 @@ handleEvent :: B.BrickEvent AppName e -> B.EventM AppName (AppState AppName) ()
|
||||
handleEvent (B.VtyEvent (V.EvResize _ _)) = B.invalidateCache
|
||||
handleEvent ev = do
|
||||
appState <- B.get
|
||||
case appState.activeWindow of
|
||||
ActiveCodeViewport -> handleViewportEvent ev
|
||||
ActiveLiveInterpreter -> handleInterpreterEvent ev
|
||||
let handler = case appState.activeWindow of
|
||||
ActiveCodeViewport -> handleViewportEvent
|
||||
ActiveLiveInterpreter -> handleInterpreterEvent
|
||||
ActiveInfoWindow -> handleInfoEvent
|
||||
ActiveDialogQuit -> handleDialogQuit
|
||||
ActiveDialogHelp -> handleDialogHelp
|
||||
handler ev
|
||||
|
||||
-- -------------------------------------------------------------------------------------------------
|
||||
-- Info Event Handling
|
||||
----------------------------------------------------------------------------------------------------
|
||||
@ -212,7 +217,7 @@ replaceCommandBuffer replacement s = Lens.set liveEditor' newEditor s
|
||||
newEditor = BE.applyEdit zipp (s ^. liveEditor')
|
||||
|
||||
-- -------------------------------------------------------------------------------------------------
|
||||
-- Viewport Event Handling
|
||||
-- Code Viewport Event Handling
|
||||
-- -------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Handle mouse events?
|
||||
@ -238,11 +243,13 @@ handleViewportEvent (B.VtyEvent (V.EvKey key ms))
|
||||
| key == V.KChar 'b' = do
|
||||
appState <- B.get
|
||||
insertViewportBreakpoint appState
|
||||
|
||||
-- j and k are the vim navigation keybindings.
|
||||
| key `elem` [V.KDown, V.KChar 'j'] = do
|
||||
moveSelectedLineBy 1
|
||||
| key `elem` [V.KUp, V.KChar 'k'] = do
|
||||
moveSelectedLineBy (-1)
|
||||
|
||||
-- '+' and '-' move the middle border.
|
||||
| key == V.KChar '+' && null ms = do
|
||||
appState <- B.get
|
||||
@ -288,6 +295,7 @@ handleViewportEvent (B.VtyEvent (V.EvKey key ms))
|
||||
appState <- B.get
|
||||
B.put appState{activeWindow = ActiveInfoWindow}
|
||||
B.invalidateCacheEntry ModulesViewport
|
||||
| key == V.KChar '?' = B.modify (\state -> state{activeWindow = ActiveDialogHelp})
|
||||
handleViewportEvent _ = pure ()
|
||||
|
||||
moveSelectedLineBy :: Int -> B.EventM AppName (AppState n) ()
|
||||
@ -306,10 +314,7 @@ moveSelectedLineBy movAmnt = do
|
||||
-- as we may need to store the dialogue structure in the app state.
|
||||
-- For now, just quit cleanly.
|
||||
confirmQuit :: B.EventM AppName (AppState AppName) ()
|
||||
confirmQuit = do
|
||||
appState <- B.get
|
||||
_ <- liftIO $ Daemon.quit appState.interpState
|
||||
B.halt
|
||||
confirmQuit = B.put . (\s -> s{activeWindow = ActiveDialogQuit}) =<< B.get
|
||||
|
||||
invalidateCachedLine :: Int -> B.EventM AppName s ()
|
||||
invalidateCachedLine lineno = B.invalidateCacheEntry (CodeViewportLine lineno)
|
||||
@ -378,6 +383,7 @@ runDaemon2 f appState =
|
||||
newState <- updateSourceMap appState{interpState = interp}
|
||||
pure (resetSelectedLine newState, x)
|
||||
|
||||
-- | Determine whether to show the cursor.
|
||||
handleCursorPosition
|
||||
:: AppState AppName
|
||||
-- ^ State of the app.
|
||||
@ -412,3 +418,32 @@ selectedModuleLoc s = eModuleLoc =<< fl
|
||||
<> showT moduleFileMap
|
||||
<> "'"
|
||||
in note errMsg res
|
||||
|
||||
-- -------------------------------------------------------------------------------------------------
|
||||
-- Dialog boxes
|
||||
-- -------------------------------------------------------------------------------------------------
|
||||
|
||||
handleDialogQuit :: B.BrickEvent AppName e -> B.EventM AppName (AppState AppName) ()
|
||||
handleDialogQuit ev = do
|
||||
appState <- B.get
|
||||
case ev of
|
||||
(B.VtyEvent (V.EvKey key _))
|
||||
| key == V.KChar 'q' || key == V.KEsc -> do
|
||||
B.put $ appState{activeWindow = ActiveCodeViewport}
|
||||
| key == V.KEnter -> do
|
||||
_ <- liftIO $ Daemon.quit appState.interpState
|
||||
B.halt
|
||||
_ -> pure ()
|
||||
pure ()
|
||||
|
||||
handleDialogHelp :: B.BrickEvent AppName e -> B.EventM AppName (AppState AppName) ()
|
||||
handleDialogHelp (B.VtyEvent (V.EvKey key _))
|
||||
| key == V.KChar 'q' || key == V.KEsc || key == V.KEnter = do
|
||||
appState <- B.get
|
||||
B.put $ appState{activeWindow = ActiveCodeViewport}
|
||||
| key == V.KPageDown = B.vScrollPage scroller B.Down
|
||||
| key == V.KPageUp = B.vScrollPage scroller B.Up
|
||||
| otherwise = pure ()
|
||||
where
|
||||
scroller = B.viewportScroll HelpViewport
|
||||
handleDialogHelp _ = pure ()
|
143
app/HelpText.hs
Normal file
143
app/HelpText.hs
Normal file
@ -0,0 +1,143 @@
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
|
||||
module HelpText where
|
||||
|
||||
import Data.String (IsString)
|
||||
|
||||
-- import Data.FileEmbed
|
||||
|
||||
helpText :: (IsString a) => a
|
||||
helpText = "\
|
||||
\GHCITUI MANUAL() GHCITUI MANUAL()\n\
|
||||
\\n\
|
||||
\NAME\n\
|
||||
\ GHCiTUI MANUAL -\n\
|
||||
\\n\
|
||||
\CLI SYNOPSIS\n\
|
||||
\ Usage: ghcitui [OPTIONS] [TARGET]\n\
|
||||
\\n\
|
||||
\ ghcitui: A TUI interface for GHCi\n\
|
||||
\\n\
|
||||
\ Available options:\n\
|
||||
\ -h,--help Show this help text\n\
|
||||
\ -d,--debug-console Display the debug console\n\
|
||||
\ -c,--cmd CMD Command to start the internal\n\
|
||||
\ interpreter\n\
|
||||
\ -C,--workdir DIR Set working dir\n\
|
||||
\\n\
|
||||
\STARTING AND STOPPING\n\
|
||||
\ Starting\n\
|
||||
\ GHCiTUI runs a REPL in the current directory by default. By default,\n\
|
||||
\ it launches cabal repl.\n\
|
||||
\\n\
|
||||
\ $ cd your/cabal/project/root/directory\n\
|
||||
\ $ ghcitui\n\
|
||||
\\n\
|
||||
\ You can specify another starting directory with the -C <DIR> flag.\n\
|
||||
\\n\
|
||||
\ $ ghcitui -C some/other/directory\n\
|
||||
\\n\
|
||||
\ Stopping\n\
|
||||
\ To quit, press <ESC> or q while in the code viewport panel to quit.\n\
|
||||
\ While not in the code viewport panel, you may press <ESC> to get to\n\
|
||||
\ the viewport panel.\n\
|
||||
\\n\
|
||||
\LAYOUT\n\
|
||||
\ GHCiTUI is an in-terminal viewer for GHCi. The TUI is broken up into\n\
|
||||
\ three primary panels, with some additional auxiliary panels for spe‐\n\
|
||||
\ cial use cases:\n\
|
||||
\\n\
|
||||
\ ┌──────────────────┬──────┐\n\
|
||||
\ │ │ Info │\n\
|
||||
\ │ │ │\n\
|
||||
\ │ Source Viewer │ │\n\
|
||||
\ │ │ │\n\
|
||||
\ │ │ │\n\
|
||||
\ ├──────────────────┤ │\n\
|
||||
\ │ │ │\n\
|
||||
\ │ Live Interpreter │ │\n\
|
||||
\ │ │ │\n\
|
||||
\ └──────────────────┴──────┘\n\
|
||||
\\n\
|
||||
\ Source Viewer: This panel shows source code. You can step, continue,\n\
|
||||
\ and toggle breakpoints among other operations in this panel.\n\
|
||||
\\n\
|
||||
\ Live Interpreter: This panel shows the GHCi/REPL passthrough. You can\n\
|
||||
\ enter expressions and GHCi commands here like you would normally,\n\
|
||||
\ with some additional keybindings.\n\
|
||||
\\n\
|
||||
\ Info: This panel displays miscellaneous info about whatever is cur‐\n\
|
||||
\ rently running. For example, it can display the current bindings,\n\
|
||||
\ loaded modules, and the current program trace.\n\
|
||||
\\n\
|
||||
\NAVIGATION\n\
|
||||
\ At any point in time, you can revert back to the Source Viewer panel\n\
|
||||
\ with the <Esc> key, and you can always quit by hitting <Esc> in the\n\
|
||||
\ Source Viewer panel.\n\
|
||||
\\n\
|
||||
\KEYBINDINGS\n\
|
||||
\ At this time, keybindings are hardcoded. This will hopefully change\n\
|
||||
\ in the future with a keybinding configuration file.\n\
|
||||
\\n\
|
||||
\ Source Viewer\n\
|
||||
\ • Ctrl+x: Toggle between the Source Viewer and the Live Interpreter\n\
|
||||
\ panels.\n\
|
||||
\\n\
|
||||
\ • <Esc>, q: Quit.\n\
|
||||
\\n\
|
||||
\ • <Up>, k: Move the cursor up. (j and k from Vim keybinds)\n\
|
||||
\\n\
|
||||
\ • <Down>, j: Move the cursor down. (j and k from Vim keybinds).\n\
|
||||
\\n\
|
||||
\ • <PgUp>: Move the source viewer one page up.\n\
|
||||
\\n\
|
||||
\ • <PgDown>: Move the source viewer one page down.\n\
|
||||
\\n\
|
||||
\ • +, -: Increase/decrease the Info panel size.\n\
|
||||
\\n\
|
||||
\ • b: Toggle breakpoint at current line. Not every line in a source\n\
|
||||
\ file can have a breakpoint placed on it.\n\
|
||||
\\n\
|
||||
\ • s: Advance execution by one step. Same as the :step in GHCi.\n\
|
||||
\\n\
|
||||
\ • c: Advance execution until next breakpoint. Same as :continue in\n\
|
||||
\ GHCi.\n\
|
||||
\\n\
|
||||
\ • t: Advance execution until next breakpoint under tracing. Same as\n\
|
||||
\ :trace in GHCi.\n\
|
||||
\\n\
|
||||
\ Live Interpreter (REPL)\n\
|
||||
\ • Ctrl+x: Toggle between the Source Viewer and the Live Interpreter\n\
|
||||
\ panels.\n\
|
||||
\\n\
|
||||
\ • <Esc>: Switch to Source Viewer.\n\
|
||||
\\n\
|
||||
\ • <Up>: Scroll back in time through the REPL command history.\n\
|
||||
\\n\
|
||||
\ • <Down>: Scroll forward in time through the REPL command history.\n\
|
||||
\\n\
|
||||
\ • <PgUp>: Scroll the Live Interpreter window one page up.\n\
|
||||
\\n\
|
||||
\ • <PgDown>: Scroll the Live Interpreter window one page down.\n\
|
||||
\\n\
|
||||
\ • Ctrl+n: Toggle scrolling mode.\n\
|
||||
\\n\
|
||||
\ • +, - while in scrolling mode: Increase/decrease the live panel\n\
|
||||
\ size.\n\
|
||||
\\n\
|
||||
\ • <Enter>: Enter a command to the REPL.\n\
|
||||
\\n\
|
||||
\ Modules\n\
|
||||
\ • Ctrl+x: Switch to the Live Interpreter.\n\
|
||||
\\n\
|
||||
\ • <Esc>: Switch to Source Viewer.\n\
|
||||
\\n\
|
||||
\ • <Up>, k: Move the module selection up.\n\
|
||||
\\n\
|
||||
\ • <Down>, j: Move the module selection down.\n\
|
||||
\\n\
|
||||
\ • +, -: Increase/decrease the info panel size.\n\
|
||||
\\n\
|
||||
\ • <Enter>, o: Open the selected module.\n\
|
||||
\\n\
|
||||
\ GHCITUI MANUAL()"
|
@ -49,6 +49,7 @@ executable ghcitui
|
||||
, AppInterpState
|
||||
, AppConfig
|
||||
, Events
|
||||
, HelpText
|
||||
ghc-options: -rtsopts
|
||||
-threaded
|
||||
-Wall
|
||||
|
Loading…
Reference in New Issue
Block a user