module IDE.IDE where import Common import Data.Text.Encoding (decodeUtf8) import Compiler.Lexer import Compiler.Parser import Control.Concurrent import Control.Concurrent.STM import Control.Exception import Control.Monad import Data.IORef import qualified Data.List as DL import Data.Map as Map import Data.Maybe import qualified Data.Set as S import Data.Text as T import Data.Text.IO as T import System.Process (createPipe) import qualified System.IO as SIO import Compiler.AST.Program import IDE.Common import IDE.Help import Interpreter import Interpreter.Common import Interpreter.Lib.Misc import Interpreter.Initialize import Parser.Parser import UI.Widgets import UI.Widgets.AutoComplete import UI.Widgets.Layout import UI.Widgets.TextContainer import UI.Widgets.LogWidget import UI.Widgets.WatchWidget import UI.Widgets.TitledContainer data IDEDebugEnv = IDEDebugEnv { ideDebugIn :: TBQueue DebugIn , ideDebugOut :: TBQueue DebugOut , ideDebugThreadInputR :: SIO.Handle , ideDebugThreadInputW :: SIO.Handle } data IDEState = IDEState { idsDebugEnv :: Maybe IDEDebugEnv , idsCodeAutocompletions :: [(Text, Text)] , idsCodeParseError :: Maybe (Location, Text) , idsKeyInputReciever :: Maybe SomeKeyInputWidget } ideStartState :: IDEState ideStartState = IDEState Nothing [] Nothing Nothing resetIDEState :: IDEState -> IDEState resetIDEState ids = ids { idsDebugEnv = Nothing, idsCodeParseError = Nothing, idsKeyInputReciever = Nothing } type Clipboard = (Text -> IO (), IO (Maybe Text)) type IDEM a = WidgetM IO a editorId :: Text editorId = "editor" watchWidgetId :: Text watchWidgetId = "watch" putStrLnFlush :: Text -> IO () putStrLnFlush s = do T.putStrLn s SIO.hFlush stdout extractBuiltIns :: IO ([(Text, Text)], [(Text, Text)]) extractBuiltIns = do sdlWindowsRef <- newIORef [] scope <- isGlobalScope . snd <$> runStateT loadBuiltIns (emptyIs sdlWindowsRef) let keywords = (\x -> let s = toSource $ toEnum @Keyword x in (s, s)) <$> [0 .. fromEnum $ maxBound @Keyword] let builtIns = DL.foldl' extractOneDoc [] (Map.assocs scope) pure (keywords, builtIns) where extractOneDoc :: [(Text, Text)] -> (ScopeKey, Value) -> [(Text, Text)] extractOneDoc in_ (SkIdentifier (unIdentifer ->ide), BuiltIn (BuiltinVal (ObjectValue m))) = in_ <> ((\k -> let a = ide <> "." <> k in (a, a)) <$> Map.keys m) extractOneDoc in_ (SkIdentifier ide, BuiltIn (BuiltinCallWithDoc s)) = (unIdentifer ide <> "()", T.concat [ unIdentifer ide, "(", T.intercalate ", " ((\(l, r) -> T.concat [l, ": ", r]) <$> extractDoc s), ")" ] ) : in_ extractOneDoc in_ (SkIdentifier ide, BuiltIn (BuiltinCall _)) = (unIdentifer ide, unIdentifer ide) : in_ extractOneDoc in_ (SkOperator op, _) = (toSource op, toSource op) : in_ extractOneDoc in_ _ = in_ getAutocompleteSuggestions :: MonadIO m => TVar IDEState -> [(Text, Text)] -> Text -> m [(Text, Text)] getAutocompleteSuggestions ideStateRef dict key = do acs <- liftIO $ idsCodeAutocompletions <$> readTVarIO ideStateRef pure $ Prelude.filter fn (acs <> dict) where fn :: (Text, Text) -> Bool fn (i, _) = T.isPrefixOf key i getTerminalSize' :: IO Dimensions getTerminalSize' = do s <- getTerminalSizeIO case s of Just (xs, ys) -> pure $ Dimensions xs ys _ -> error "Cannot read terminal size" -- This thread reads the editor contents and try to parse into -- tokens and ast. And then fills some autocomplete suggestions and -- makes any parse error location. codeProcessingThread :: IORef [Token] -> TChan IDEEvent -> MVar Text -> IO () codeProcessingThread tokensRef ideEventRef editorContentRef = forever $ flip catch handler $ do liftIO $ atomically $ writeTChan ideEventRef IDEReqEditorContent code <- takeMVar editorContentRef tokens <- tokenize code liftIO $ writeIORef tokensRef tokens mError <- parseEither @Program tokens >>= \case Left (ParseErrorWithParsed _ loc (FatalError a)) -> pure $ Just (loc, T.pack $ show a) Left (ParseErrorWithParsed _ _ (FatalErrorWithLocation loc fpe)) -> pure $ Just (loc, T.pack $ show fpe) _ -> pure Nothing liftIO $ atomically $ writeTChan ideEventRef $ IDEParseErrorUpdate mError liftIO $ atomically $ writeTChan ideEventRef $ IDEACUpdateIdentifiers $ S.toList $ S.fromList $ catMaybes $ filterIdentifiers <$> tokens wait 1 where handler :: SomeException -> IO () handler e = liftIO $ atomically $ writeTChan ideEventRef $ IDEAppendLog (T.pack $ show e) filterIdentifiers :: Token -> Maybe Text filterIdentifiers Token { tkRaw = (TkIdentifier (unIdentifer -> idf))} = Just idf filterIdentifiers _ = Nothing -- | A type to differentiat debuging mode in IDE when input events -- need to be fed to the interpreted program, and not to the IDE. data InputRouting = RouteIDE | RouteProgram SIO.Handle instance Show InputRouting where show RouteIDE = "Route To IDE" show (RouteProgram _) = "Route To Program" runIDE :: FilePath -> TChan TerminalEvent -> TMVar ThreadId -> IO () runIDE filePath inputChan interpreterThreadRef = do content <- try (T.readFile filePath) >>= \case Right c -> pure c Left (_ :: SomeException) -> pure "" (keywords, builtIns) <- extractBuiltIns ideStateRef <- newTVarIO ideStartState ideEventsRef <- newTChanIO atomically $ writeTChan ideEventsRef (IDEAppendLog ("Loaded file: " <> (T.pack filePath))) clipboardRef <- newIORef @(Maybe Text) Nothing let clipboard = (\t -> modifyIORef clipboardRef (\_ -> Just t), readIORef clipboardRef) editorContentMVar <- newEmptyMVar @Text tokensRef <- newIORef @[Token] [] void $ forkIO $ codeProcessingThread tokensRef ideEventsRef editorContentMVar inputRouteToRef <- newTVarIO RouteIDE void $ liftIO $ forkIO $ forever $ do kv <- atomically $ readTChan inputChan (atomically $ readTVar inputRouteToRef) >>= \case RouteIDE -> atomically $ writeTChan ideEventsRef (IDETerminalEvent kv) RouteProgram handle' -> pushTerminalEventToHandle handle' kv void $ runWidgetM $ do -- The editor editorRef <- editor (getAutocompleteSuggestions ideStateRef (keywords <> builtIns)) (Just $ SomeTokenStream (readIORef tokensRef)) modifyWRef editorRef (\ew' -> ew' { ewParams = (ewParams ew') { epLinenumberRightPad = 1, epBorder = False }}) setContent editorRef content liftIO $ atomically $ modifyTVar ideStateRef (\ids -> ids { idsKeyInputReciever = Just $ SomeKeyInputWidget editorRef }) -- Watch widget watchWidgetRef <- watchWidget titledWatchRef <- titledContainer (ScreenPos 0 0) (Dimensions 0 0) (SomeWidgetRef watchWidgetRef) " Watch " let dimensionDistributionFn = \case 1 -> [1] 2 -> [0.85, 0.15] 3 -> [0.70, 0.15, 0.15] 4 -> [0.55, 0.15, 0.15, 0.15] a -> error $ "Unsupported widget count:" <> show a logRef <- logWidget titledLogRef <- titledContainer (ScreenPos 0 0) (Dimensions 0 0) (SomeWidgetRef logRef) " Log " helpWidgetRef <- helpWidget ((Prelude.head . T.split (== '.'). fst) <$> builtIns) ideEventsRef -- The autocomplete widget ac <- autoComplete modifyWRef editorRef (\ed -> ed { ewAutocompleteWidget = Just $ SomeWidgetRef ac }) -- The aboutbox widget aboutText <- textContainer (ScreenPos 0 0) (Dimensions 58 12) let aboutContent = (T.replicate 24 " ") <> "S.P.A.D.E" <> "\n" <> (T.replicate 24 " ") <> "=========" <> "\n\n" <> " A Simple Programming And Debugging Environment" <> "\n" <> " ______________________________________________" <> "\n\n\n" <> " Copyright (C) 2022 Sandeep.C.R" <> "\n\n\n" <> " Press Esc to close" setContent aboutText aboutContent aboutBox <- borderBox (ScreenPos 10 16) (Dimensions 60 14) (SomeWidgetRef aboutText) setVisibility aboutBox False -- The Outermost layout layoutRef <- layoutWidget Vertical dimensionDistributionFn (Just $ SomeKeyInputWidget editorRef) modifyWRef layoutRef (\lw -> lw { lowFloatingContent = [SomeWidgetRef ac, SomeWidgetRef aboutBox] }) addWidget layoutRef editorId editorRef addWidget layoutRef "helpwidget" helpWidgetRef addWidget layoutRef "watch" titledWatchRef addWidget layoutRef "log" titledLogRef setVisibility helpWidgetRef False setVisibility titledWatchRef False setTextFocus layoutRef editorId -- The menu let menu = Menu [ ("File", SubMenu ["Save (F6)", "Beautify", "Exit"]) , ("Edit", SubMenu ["Cut", "Copy", "Paste", "Select All"]) , ("Run", SubMenu ["Run (F5)", "Step (F8)"]) , ("Windows", SubMenu ["Clear log"]) , ("Help", SubMenu ["About", "Contents"]) ] Nothing (Just $ T.pack filePath) menuContainerRef <- menuContainer (SomeWidgetRef layoutRef) menu (selectionHandler ideEventsRef) modify (\ws -> ws { wsCursorWidget = Just $ SomeKeyInputWidget editorRef }) -- A snippet that refresh the screen. let ideRedraw :: WidgetC m => m () ideRedraw = do csClear draw menuContainerRef csDraw let getActiveEditor :: WidgetC m => m (WRef EditorWidget) getActiveEditor = do (getVisibility editorRef) >>= \case True -> pure editorRef _ -> getVisibility helpWidgetRef >>= \case True -> hwContentWidget <$> (readWRef helpWidgetRef) _ -> error "No visible editor" let launchProgram :: forall m. WidgetC m => m (Either Text IDEDebugEnv) launchProgram = do source <- getContent editorRef liftIO $ do compileEither source >>= \case Right p -> do debugIn <- atomically (newTBQueue 10) debugOut <- atomically (newTBQueue 10) let handler :: SomeException -> IO () handler e = do atomically (writeTBQueue debugOut (Errored (T.pack $ show e))) (programInputHandleR, programInputHandleW) <- liftIO createPipe let ideDebugEnv = IDEDebugEnv debugIn debugOut programInputHandleR programInputHandleW osThreadId <- forkOS $ do startMode <- atomically $ do -- Initialize IDE debuggins state dIn <- readTBQueue debugIn check (dIn == Start || dIn == StartStep) -- Wait till the Start command modifyTVar ideStateRef (\idestate -> idestate { idsDebugEnv = Just ideDebugEnv }) pure dIn let stateFn = case startMode of StartStep -> (\is -> is { isInputHandle = programInputHandleR , isRunMode = DebugMode $ DebugEnv SingleStep debugIn debugOut }) Start -> (\is -> is { isRunMode = NormalMode (Just $ DebugEnv SingleStep debugIn debugOut) , isInputHandle = programInputHandleR }) _ -> error "Impossible!" flip catch handler (void $ do void $ interpret stateFn p atomically (writeTBQueue debugOut (Finished False)) ) -- Reset IDE debuggins state void $ atomically $ do readTBQueue debugIn >>= (check . (== Stop)) -- Wait till the stop command void $ takeTMVar interpreterThreadRef modifyTVar ideStateRef (\idestate -> idestate { idsDebugEnv = Nothing }) atomically $ putTMVar interpreterThreadRef osThreadId pure $ Right ideDebugEnv Left err -> do pure $ Left (hReadable err) uiLoop ideEventsRef (\case IDEShowAbout -> do mc <- readWRef menuContainerRef setVisibility aboutBox True adim <- getDim aboutBox move aboutBox (ScreenPos ((div (diW $ mcwDim mc) 2) - (div (diW adim) 2)) 20) ideRedraw pure True IDEHideAbout -> do setVisibility aboutBox False ideRedraw pure True IDEToggleHelp -> do getVisibility helpWidgetRef >>= \case True -> do setVisibility helpWidgetRef False setVisibility editorRef True setVisibility watchWidgetRef True modify (\ws -> ws { wsCursorWidget = Just $ SomeKeyInputWidget editorRef }) liftIO $ atomically (modifyTVar ideStateRef (\is -> is { idsKeyInputReciever = Just $ SomeKeyInputWidget editorRef })) False -> do setVisibility helpWidgetRef True setVisibility editorRef False setVisibility watchWidgetRef False modify (\ws -> ws { wsCursorWidget = Just $ SomeKeyInputWidget helpWidgetRef }) liftIO $ atomically (modifyTVar ideStateRef (\is -> is { idsKeyInputReciever = Just $ SomeKeyInputWidget helpWidgetRef })) d <- getScreenBounds csInitialize d draw menuContainerRef csDraw pure True IDESave -> do c <- getContent editorRef liftIO $ T.writeFile filePath c insertLog logRef ("Saved to " <> (T.pack filePath) <>".") pure True IDEEdit Copy -> do ew <- getActiveEditor >>= readWRef case ewSelection ew of Just (r1, r2) -> let contents = ewContent ew removeLength = (r2 - r1 + 1) copyContent = T.take (removeLength + 1) (T.drop r1 contents) in liftIO $ (fst clipboard) copyContent Nothing -> pass pure True IDEEdit Cut -> do modifyWRefM editorRef (\ew -> case ewSelection ew of Just (r1, r2) -> let contents = ewContent ew (p1, p2) = T.splitAt r1 contents removeLength = (r2 - r1 + 1) cutContent = T.take removeLength p2 in do liftIO $ (fst clipboard) cutContent let newCursor = max 0 (r1 - 1) pure $ putCursor Nothing newCursor $ ew { ewSelection = Nothing, ewContent = p1 <> (T.drop removeLength p2) } Nothing -> pure ew ) ideRedraw pure True IDEEdit Paste -> do modifyWRefM editorRef (\ew -> do let contents = ewContent ew (p1, p2) = T.splitAt (ewCursor ew) contents (liftIO (snd clipboard)) >>= \case Nothing -> pure ew Just insertContent -> pure $ putCursor Nothing (ewCursor ew + T.length insertContent) $ ew { ewContent = p1 <> insertContent <> p2 } ) ideRedraw pure True IDEEdit SelectAll -> do modifyWRef editorRef (\ew -> ew { ewSelection = Just (0, (T.length $ ewContent ew) - 1) }) ideRedraw pure True IDEClearLog -> do modifyWRef logRef (\lw -> lw { lwContent = [] }) pure True IDEAppendLog msg -> do insertLog logRef msg pure True IDEBeautify -> do insertLog logRef "Formatting..." code <- getContent editorRef (liftIO $ compileEither @Program code) >>= \case Right a -> do setContent editorRef (T.strip $ toSource a) liftIO $ atomically $ writeTChan ideEventsRef IDEDraw Left (ParseErrorWithParsed _ _ e) -> insertLog logRef ("Parse error:" <> T.pack (show e)) pure True IDEDraw -> do d <- getScreenBounds csInitialize d draw menuContainerRef csDraw pure True IDEACUpdateIdentifiers acs -> do liftIO $ atomically (modifyTVar ideStateRef (\is -> is { idsCodeAutocompletions = (\x -> (x, x)) <$> acs })) pure True IDEParseErrorUpdate mError -> do liftIO $ atomically (modifyTVar ideStateRef (\is -> is { idsCodeParseError = mError })) idestate <- liftIO $ readTVarIO ideStateRef case idsCodeParseError idestate of Just (loc, _) -> modifyWRef editorRef (\ew -> ew { ewParseErrorLocation = Just loc }) Nothing -> modifyWRef editorRef (\ew -> ew { ewParseErrorLocation = Nothing }) ideRedraw pure True IDEReqEditorContent -> do c <- getContent editorRef liftIO $ void $ putMVar editorContentMVar c pure True IDEExit -> do pure False IDERun -> do idsDebugEnv <$> liftIO (readTVarIO ideStateRef) >>= \case -- If we are in a debug session, just run to completion. (Just ideDebugEnv) -> do insertLog logRef "Continuing debug session to end..." liftIO $ atomically $ do writeTBQueue (ideDebugIn ideDebugEnv) Run writeTVar inputRouteToRef (RouteProgram $ ideDebugThreadInputW ideDebugEnv) waitTill (\_ -> True) (ideDebugOut ideDebugEnv) >>= processDebugOut ideEventsRef ideStateRef logRef editorRef (ideDebugIn ideDebugEnv) liftIO $ atomically $ do writeTChan ideEventsRef IDEDraw writeTVar inputRouteToRef RouteIDE _ -> do -- Or else launch a new run of the program. clearscreen setCursorPosition 0 0 launchProgram >>= \case Right ideDebugEnv -> do insertLog logRef "Starting..." liftIO $ atomically $ do writeTBQueue (ideDebugIn ideDebugEnv) Start writeTVar inputRouteToRef (RouteProgram $ ideDebugThreadInputW ideDebugEnv) waitTill (\_ -> True) (ideDebugOut ideDebugEnv) >>= processDebugOut ideEventsRef ideStateRef logRef editorRef (ideDebugIn ideDebugEnv) liftIO $ atomically $ do writeTChan ideEventsRef IDEDraw writeTVar inputRouteToRef RouteIDE Left err -> do insertLog logRef $ "Compile Error:" <> err liftIO $ atomically $ writeTChan ideEventsRef IDEDraw pure True IDEStep -> do csSetCursorPosition 0 0 idsDebugEnv <$> liftIO (readTVarIO ideStateRef) >>= \case -- A debugging session exists already. -- Send the step command, wait for updated state to arrive. (Just ideDebugEnv) -> do -- Get current interpreter statement location (liftIO $ atomically $ tryReadTBQueue (ideDebugOut ideDebugEnv)) >>= \case Just debugOut -> do processDebugOut ideEventsRef ideStateRef logRef editorRef (ideDebugIn ideDebugEnv) debugOut Nothing -> pass liftIO $ atomically $ do writeTBQueue (ideDebugIn ideDebugEnv) StepIn writeTVar inputRouteToRef (RouteProgram $ ideDebugThreadInputW ideDebugEnv) -- Wait till the interpreter moves to the next location (liftIO $ atomically $ readTBQueue (ideDebugOut ideDebugEnv)) >>= processDebugOut ideEventsRef ideStateRef logRef editorRef (ideDebugIn ideDebugEnv) liftIO $ atomically $ do writeTVar inputRouteToRef RouteIDE _ -> do -- No debugging session exist. Launch one. launchProgram >>= \case Right ideDebugEnv -> do liftIO $ atomically $ do writeTBQueue (ideDebugIn ideDebugEnv) StartStep writeTBQueue (ideDebugIn ideDebugEnv) StepIn writeTVar inputRouteToRef (RouteProgram $ ideDebugThreadInputW ideDebugEnv) -- Wait till the interpreter moves to the first location (liftIO $ atomically $ readTBQueue (ideDebugOut ideDebugEnv)) >>= processDebugOut ideEventsRef ideStateRef logRef editorRef (ideDebugIn ideDebugEnv) liftIO $ atomically $ do writeTVar inputRouteToRef RouteIDE Left err -> insertLog logRef $ "Compile Error:" <> err pure True IDEDebugUpdate mds -> do case mds of Just ds -> do items <- liftIO $ scopeToWatchItems $ dsScope ds modifyWRef editorRef (\ew -> putCursor Nothing (lcOffset (dsLocation ds)) $ ew { ewReadOnly = True, ewDebugLocation = Just $ dsLocation ds }) modifyWRef titledWatchRef (\twr -> setTitle twr $ wrapInSpace ("Watching Thread: " <> dsThreadName ds)) setVisibility titledWatchRef True modifyWRef watchWidgetRef (\ww -> ww { wwVisible = True, wwContent = items }) case dsCurrenEvaluation ds of Just cv -> insertLog logRef ("Evaluating: " <> cv) Nothing -> pass Nothing -> do modifyWRef titledWatchRef (\twr -> setTitle twr "Watch") modifyWRef editorRef (\ew -> ew { ewReadOnly = False, ewDebugLocation = Nothing }) setVisibility titledWatchRef False liftIO $ atomically $ writeTChan ideEventsRef IDEDraw pure True IDETerminalEvent TerminalInterrupt -> pure False IDETerminalEvent (TerminalResize w h) -> do let dim' = Dimensions w h csInitialize dim' modifyWRef menuContainerRef (\mc -> mc { mcwDim = dim' }) draw menuContainerRef adjustScrollOffset editorRef draw menuContainerRef csDraw pure True IDETerminalEvent (TerminalKey ev) -> case ev of KeyChar True _ _ 'c' -> pure False KeyChar True _ _ 'C' -> pure False k -> do updateMenuOnKey ideEventsRef menuContainerRef k >>= \case True -> pure () False -> shortcutsHandler ideEventsRef k >>= \case True -> pure () False -> idsKeyInputReciever <$> (liftIO $ readTVarIO ideStateRef) >>= \case Just (SomeKeyInputWidget w) -> handleInput w k Nothing -> pure () idestate <- liftIO $ readTVarIO ideStateRef case idsCodeParseError idestate of Just (loc, _) -> modifyWRef editorRef (\ew -> ew { ewParseErrorLocation = Just loc }) Nothing -> modifyWRef editorRef (\ew -> ew { ewParseErrorLocation = Nothing }) ideRedraw pure True ) clearscreen setCursorPosition 0 0 where waitTill :: MonadIO m => (DebugOut -> Bool) -> TBQueue DebugOut -> m DebugOut waitTill pred_ debugOut = do (liftIO $ atomically $ readTBQueue debugOut) >>= \case a@(pred_ -> True) -> pure a _ -> waitTill pred_ debugOut processDebugOut :: forall m. (HasCallStack, WidgetC m) => TChan IDEEvent -> TVar IDEState -> WRef LogWidget -> WRef EditorWidget -> TBQueue DebugIn -> DebugOut -> m () processDebugOut ideEventRef ideStateRef logRef editorRef debugIn dout = do case dout of -- Update the IDE debug state with the received debug state. DebugData ds -> do liftIO $ atomically $ writeTChan ideEventRef (IDEDebugUpdate $ Just ds) Errored err -> do liftIO $ atomically $ modifyTVar ideStateRef (\i -> (resetIDEState i) { idsKeyInputReciever = idsKeyInputReciever i }) modifyWRef editorRef (\ew -> ew { ewReadOnly = False }) insertLog logRef err liftIO $ atomically $ do writeTBQueue debugIn Stop writeTChan ideEventRef (IDEDebugUpdate Nothing) Finished isUserInterrupt -> do modifyWRef editorRef (\ew -> ew { ewReadOnly = False }) liftIO $ atomically $ modifyTVar ideStateRef (\i -> (resetIDEState i) { idsKeyInputReciever = idsKeyInputReciever i }) insertLog logRef (if isUserInterrupt then "User Interrupt" else "Finished program") liftIO $ atomically $ do writeTBQueue debugIn Stop writeTChan ideEventRef (IDEDebugUpdate Nothing) updateMenuOnKey :: WidgetC m => TChan IDEEvent -> WRef MenuContainerWidget -> KeyEvent -> m Bool updateMenuOnKey ideEventsRef ref kv = do w <- readWRef ref case (kv, menuActive $ mcwMenu w) of (KeyCtrl _ _ _ Esc, Just _) -> do modifyWRef ref (\w' -> w' { mcwMenu = (mcwMenu w) { menuActive = Nothing }}) setCursorVisibility True pure True (KeyCtrl _ _ _ Esc, _) -> do liftIO $ atomically $ writeTChan ideEventsRef IDEHideAbout pure False -- Let Esc key bubble (KeyChar _ _ True c, _) -> do foldM_ (\x (n, _) -> case T.uncons (T.toLower n) of Nothing -> pure $ x + 1 Just (c1, _) -> if c == c1 then do modifyWRef ref (\w' -> w' { mcwMenu = (mcwMenu w) { menuActive = Just (x, 0) }}) setCursorVisibility False pure $ x + 1 else pure $ x + 1) 0 (menuItems $ mcwMenu w) pure True (KeyCtrl _ _ _ ArrowLeft, Just (x, _)) | x > 0 -> do modifyWRef ref (\w' -> w' { mcwMenu = (mcwMenu w) { menuActive = Just (x - 1, 0) }}) pure True | otherwise -> do modifyWRef ref (\w' -> w' { mcwMenu = (mcwMenu w) { menuActive = Just (Prelude.length (menuItems $ mcwMenu w) - 1, 0) }}) pure True (KeyCtrl _ _ _ ArrowRight, Just (x, _)) -> do let menuItemsLength = Prelude.length $ menuItems $ mcwMenu w let newX = x + 1 if newX < menuItemsLength then do modifyWRef ref (\w' -> w' { mcwMenu = (mcwMenu w) { menuActive = Just (newX, 0) }}) pure True else do modifyWRef ref (\w' -> w' { mcwMenu = (mcwMenu w) { menuActive = Just (0, 0) }}) pure True (KeyCtrl _ _ _ ArrowUp, Just (x, y)) | y > 0 -> do modifyWRef ref (\w' -> w' { mcwMenu = (mcwMenu w) { menuActive = Just (x, y - 1) }}) pure True | otherwise -> do case getSubmenuAt (mcwMenu w) x of Nothing -> pure True Just (SubMenu smi) -> do modifyWRef ref (\w' -> w' { mcwMenu = (mcwMenu w) { menuActive = Just (x, Prelude.length smi - 1) }}) pure True (KeyCtrl _ _ _ ArrowDown, Just (x, y)) -> do case getSubmenuAt (mcwMenu w) x of Just (SubMenu smi) -> do let newY = y + 1 if newY < (Prelude.length smi) then do modifyWRef ref (\w' -> w' { mcwMenu = (mcwMenu w) { menuActive = Just (x, newY) }}) pure True else do modifyWRef ref (\w' -> w' { mcwMenu = (mcwMenu w) { menuActive = Just (x, 0) }}) pure True Nothing -> pure True (KeyCtrl _ _ _ Return, Just (x, y)) -> do (mcwSelectionHandler w) ref (x, y) setCursorVisibility True pure True _ -> pure False scopeToWatchItems :: Scope -> IO [(Text, Text)] scopeToWatchItems s = foldM (\o (k, v) -> do let vKey = case k of SkIdentifier i -> unIdentifer i SkOperator op -> T.pack $ show op vText <- case v of ErrorValue t -> pure $ Just $ "Error: " <> t v' -> catch (fst <$> (runInterpretM dummyIS ((Just . decodeUtf8) <$> serializeJSON v'))) (\case UnserializeableValue -> pure Nothing e -> pure $ Just $ "Error: " <> (T.pack $ show e)) case vText of Just t -> pure $ (vKey, t) : o Nothing -> pure o ) [] (Map.assocs s) selectionHandler :: forall m. WidgetC m => TChan IDEEvent -> WRef MenuContainerWidget -> (Int, Int) -> m () selectionHandler ideEventsRef ref selection = do case selection of (0, 0) -> liftIO $ atomically $ writeTChan ideEventsRef IDESave (0, 1) -> liftIO $ atomically $ writeTChan ideEventsRef IDEBeautify (0, 2) -> liftIO $ atomically $ writeTChan ideEventsRef IDEExit (1, 0) -> liftIO $ atomically $ writeTChan ideEventsRef (IDEEdit Cut) (1, 1) -> liftIO $ atomically $ writeTChan ideEventsRef (IDEEdit Copy) (1, 2) -> liftIO $ atomically $ writeTChan ideEventsRef (IDEEdit Paste) (1, 3) -> liftIO $ atomically $ writeTChan ideEventsRef (IDEEdit SelectAll) (2, 0) -> liftIO $ atomically $ writeTChan ideEventsRef IDERun (2, 1) -> liftIO $ atomically $ writeTChan ideEventsRef IDEStep (3, 0) -> liftIO $ atomically $ writeTChan ideEventsRef IDEClearLog (4, 0) -> liftIO $ atomically $ writeTChan ideEventsRef IDEShowAbout (4, 1) -> liftIO $ atomically $ writeTChan ideEventsRef IDEToggleHelp _ -> pass modifyWRef ref (\mcw -> mcw { mcwMenu = (mcwMenu mcw) { menuActive = Nothing } }) liftIO $ atomically $ writeTChan ideEventsRef IDEDraw shortcutsHandler :: forall m. WidgetC m => TChan IDEEvent -> KeyEvent -> m Bool shortcutsHandler ideEventRef kv = do case kv of KeyCtrl _ _ _ (Fun 6) -> do liftIO $ atomically $ writeTChan ideEventRef IDESave pure True KeyCtrl _ _ _ (Fun 1) -> do liftIO $ atomically $ writeTChan ideEventRef IDEToggleHelp pure True KeyCtrl _ _ _ (Fun 8) -> do liftIO $ atomically $ writeTChan ideEventRef IDEStep pure True KeyCtrl _ _ _ (Fun 5) -> do liftIO $ atomically $ writeTChan ideEventRef IDERun pure True _ -> pure False