packages feed

leksah-0.16.2.2: src/IDE/Pane/Variables.hs

{-# LANGUAGE FlexibleInstances, TypeSynonymInstances,
   MultiParamTypeClasses, DeriveDataTypeable, OverloadedStrings #-}
-----------------------------------------------------------------------------
--
-- Module      :  IDE.Pane.Variables
-- Copyright   :  2007-2011 Juergen Nicklisch-Franken, Hamish Mackenzie
-- License     :  GPL
--
-- Maintainer  :  maintainer@leksah.org
-- Stability   :  provisional
-- Portability :
--
-- |
--
-----------------------------------------------------------------------------

module IDE.Pane.Variables (
    IDEVariables
,   VariablesState
,   showVariables
,   fillVariablesList
,   fillVariablesListQuiet
) where

import Data.Typeable (Typeable(..))
import IDE.Core.State
import IDE.Package (tryDebug, tryDebugQuiet)
import IDE.Debug (debugCommand')
import IDE.Utils.Tool (ToolOutput(..))
import Text.ParserCombinators.Parsec
    (anyChar,
     lookAhead,
     eof,
     (<|>),
     try,
     (<?>),
     char,
     noneOf,
     many,
     CharParser(..),
     parse)
import qualified Text.ParserCombinators.Parsec.Token as  P
    (whiteSpace, symbol, makeTokenParser)
import Text.ParserCombinators.Parsec.Language (emptyDef)
import IDE.Workspaces (packageTry, packageTryQuiet)
import qualified Data.Conduit.List as CL (consume)
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.IO.Class (MonadIO(..))
import IDE.Utils.GUIUtils (treeViewContextMenu, __)
import Data.Text (Text)
import Data.Monoid ((<>))
import qualified Data.Text as T (pack, unpack)
import Control.Applicative ((<$>))
import GI.Gtk.Objects.ScrolledWindow
       (scrolledWindowSetPolicy, scrolledWindowSetShadowType,
        scrolledWindowNew, ScrolledWindow(..))
import GI.Gtk.Objects.TreeView
       (onTreeViewRowActivated, treeViewGetSelection,
        treeViewSetHeadersVisible, treeViewAppendColumn, treeViewSetModel,
        treeViewNew, TreeView(..))
import Data.GI.Gtk.ModelView.ForestStore
       (forestStoreGetValue, ForestStore(..), forestStoreSetValue,
        forestStoreInsert, forestStoreClear, forestStoreNew)
import GI.Gtk.Objects.Widget (afterWidgetFocusInEvent, toWidget)
import GI.Gtk.Objects.Notebook (Notebook(..))
import GI.Gtk.Objects.Window (Window(..))
import GI.Gtk.Objects.CellRendererText
       (setCellRendererTextText, cellRendererTextNew)
import GI.Gtk.Objects.TreeViewColumn
       (TreeViewColumn(..), treeViewColumnSetReorderable,
        treeViewColumnSetResizable, treeViewColumnSetSizing,
        treeViewColumnSetTitle, treeViewColumnNew)
import GI.Gtk.Enums
       (PolicyType(..), ShadowType(..), SelectionMode(..),
        TreeViewColumnSizing(..))
import GI.Gtk.Interfaces.CellLayout (cellLayoutPackStart)
import Data.GI.Gtk.ModelView.CellLayout
       (cellLayoutSetDataFunction)
import GI.Gtk.Objects.TreeSelection
       (treeSelectionSetMode)
import GI.Gtk.Objects.Adjustment (noAdjustment)
import GI.Gtk.Objects.Container (containerAdd)
import Control.Monad.Reader (MonadReader(..))
import GI.Gtk.Structs.TreePath
       (TreePath(..))
import GI.Gtk.Objects.Menu (Menu(..))
import GI.Gtk.Objects.MenuItem
       (toMenuItem, onMenuItemActivate, menuItemNewWithLabel)
import GI.Gtk.Objects.SeparatorMenuItem (separatorMenuItemNew)
import GI.Gtk.Objects.MenuShell (menuShellAppend)
import Data.GI.Gtk.ModelView.Types
       (treeSelectionGetSelectedRows', treePathNewFromIndices')

-- | Represents the Variables pane
data IDEVariables    =   IDEVariables {
    scrolledView    ::   ScrolledWindow
,   treeView        ::   TreeView
,   variables       ::   ForestStore VarDescription
} deriving Typeable


-- | The data for a single entry in the pane
data VarDescription = VarDescription {
    varName         ::  Text
,   varType         ::  Text
,   varValue        ::  Text}


-- | The additional state used when recovering the pane
data VariablesState  =   VariablesState
    deriving(Eq,Ord,Read,Show,Typeable)


instance Pane IDEVariables IDEM
    where
    primPaneName _  =   __ "Variables"
    getAddedIndex _ =   0
    getTopWidget    =   liftIO . toWidget . scrolledView
    paneId b        =   "*Variables"


instance RecoverablePane IDEVariables VariablesState IDEM where
    saveState p     =   return (Just VariablesState)
    recoverState pp VariablesState =   do
        nb      <-  getNotebook pp
        buildPane pp nb builder
    builder = builder'


-- | Get the Variables pane
getVariables :: IDEM IDEVariables
getVariables = forceGetPane (Right "*Variables")


-- | Display the Variables pane
showVariables :: IDEAction
showVariables = do
    pane <- getVariables
    displayPane pane False


-- | Builds the Variables pane, together with a list
--   of connections of events
builder' :: PanePath ->
    Notebook ->
    Window ->
    IDEM (Maybe IDEVariables, Connections)
builder' pp nb windows = do
    ideR <- ask
    variables   <-  forestStoreNew []
    treeView    <-  treeViewNew
    treeViewSetModel treeView (Just variables)

    renderer1    <- cellRendererTextNew
    col1         <- treeViewColumnNew
    treeViewColumnSetTitle col1 (__ "Name")
    treeViewColumnSetSizing col1 TreeViewColumnSizingAutosize
    treeViewColumnSetResizable col1 True
    treeViewColumnSetReorderable col1 True
    treeViewAppendColumn treeView col1
    cellLayoutPackStart col1 renderer1 False
    cellLayoutSetDataFunction col1 renderer1 variables
        $ setCellRendererTextText renderer1 . varName

    renderer2    <- cellRendererTextNew
    col2         <- treeViewColumnNew
    treeViewColumnSetTitle col2 (__ "Type")
    treeViewColumnSetSizing col2 TreeViewColumnSizingAutosize
    treeViewColumnSetResizable col2 True
    treeViewColumnSetReorderable col2 True
    treeViewAppendColumn treeView col2
    cellLayoutPackStart col2 renderer2 False
    cellLayoutSetDataFunction col2 renderer2 variables
        $ setCellRendererTextText renderer2 . varType

    renderer3    <- cellRendererTextNew
    col3         <- treeViewColumnNew
    treeViewColumnSetTitle col3 (__ "Value")
    treeViewColumnSetSizing col3 TreeViewColumnSizingAutosize
    treeViewColumnSetResizable col3 True
    treeViewColumnSetReorderable col3 True
    treeViewAppendColumn treeView col3
    cellLayoutPackStart col3 renderer3 False
    cellLayoutSetDataFunction col3 renderer3 variables
        $ setCellRendererTextText renderer3 . varValue

    treeViewSetHeadersVisible treeView True
    sel <- treeViewGetSelection treeView
    treeSelectionSetMode sel SelectionModeSingle

    scrolledView <- scrolledWindowNew noAdjustment noAdjustment
    scrolledWindowSetShadowType scrolledView ShadowTypeIn
    containerAdd scrolledView treeView
    scrolledWindowSetPolicy scrolledView PolicyTypeAutomatic PolicyTypeAutomatic

    let pane = IDEVariables scrolledView treeView variables
    cid1 <- onIDE afterWidgetFocusInEvent treeView (do
        liftIDE $ makeActive pane
        return True)
    cids2 <- treeViewContextMenu treeView $ variablesContextMenu ideR variables treeView
    cid4 <- ConnectC treeView <$> onTreeViewRowActivated treeView (variablesSelect ideR variables)
    return (Just pane, [cid1, cid4] ++ cids2)


-- | Quietly list the variable bindings of the current debug session
fillVariablesListQuiet :: IDEAction
fillVariablesListQuiet = packageTryQuiet $ do
    mbVariables <- liftIDE getPane
    case mbVariables of
        Nothing -> return ()
        Just var -> tryDebugQuiet $ debugCommand' ":show bindings" $ do
            to <- CL.consume
            lift $ postAsyncIDE $
                case parse variablesParser "" . T.unpack $ selectString to of
                    Left e -> liftIO $ sysMessage Normal (T.pack $ show e)
                    Right triples -> do
                        forestStoreClear (variables var)
                        mapM_ (insertAtRoot (variables var))
                            (zip triples [0..length triples])

insertAtRoot :: MonadIO m => ForestStore a -> (a, Int) -> m ()
insertAtRoot forestStore (v,index)  = do
    emptyPath <- treePathNewFromIndices' []
    forestStoreInsert forestStore emptyPath index v


-- | List the variable bindings of the current debug session
fillVariablesList :: IDEAction
fillVariablesList = packageTry $ do
    mbVariables <- liftIDE getPane
    case mbVariables of
        Nothing -> return ()
        Just var -> tryDebug $ debugCommand' ":show bindings" $ do
            to <- CL.consume
            lift $ postAsyncIDE $
                case parse variablesParser "" . T.unpack $ selectString to of
                    Left e -> liftIO $ sysMessage Normal (T.pack $ show e)
                    Right triples -> do
                        forestStoreClear (variables var)
                        mapM_ (insertAtRoot (variables var))
                            (zip triples [0..length triples])


-- | Concatenates the tool output lines with newlines
selectString :: [ToolOutput] -> Text
selectString (ToolOutput str:r)  = "\n" <> str <> selectString r
selectString (_:r)               = selectString r
selectString []                  = ""


-- | Tries to get the selected variable in the Pane,
--  together with the 'TreePath'
getSelectedVariable ::  TreeView
    -> ForestStore VarDescription
    -> IO (Maybe (VarDescription,TreePath))
getSelectedVariable treeView forestStore = do
    treeSelection   <-  treeViewGetSelection treeView
    paths           <-  treeSelectionGetSelectedRows' treeSelection
    case paths of
        a:r ->  do
            val     <-  forestStoreGetValue forestStore a
            return (Just (val,a))
        _  ->  return Nothing


-- | Parses multiple 'VarDescription's
variablesParser :: CharParser () [VarDescription]
variablesParser = do
    whiteSpace
    r <- many variableParser
    eof
    return r


-- | Parses a 'VarDescription' from a line of ghci output
variableParser :: CharParser () VarDescription
variableParser = do
    whiteSpace
    varName <- T.pack <$> many (noneOf ":")
    symbol "::"
    typeStr  <- T.pack <$> many (noneOf "=")
    char '='
    value <- T.pack <$> many (
        noneOf "\n"
        <|> try (do
                r <- char '\n'
                lookAhead (char ' ')
                return r))

    return (VarDescription varName typeStr value)
    <?> "variableParser"


-- | Parses only the value of a variable binding from a line of
-- ghci output
valueParser :: CharParser () Text
valueParser = do
    whiteSpace
    many (noneOf "=")
    char '='
    T.pack <$> many anyChar
    <?> "valueParser"

-- | Parses only the type of a variable binding from a line of
-- ghci output
typeParser :: CharParser () Text
typeParser = do
    whiteSpace
    many (noneOf ":")
    symbol "::"
    T.pack <$> many anyChar
    <?> "typeParser"


lexer = P.makeTokenParser emptyDef
symbol = P.symbol lexer
whiteSpace = P.whiteSpace lexer


-- | Constructs the context menu of the Variables pane
variablesContextMenu :: IDERef
                  -> ForestStore VarDescription
                  -> TreeView
                  -> Menu
                  -> IO ()
variablesContextMenu ideR store treeView theMenu = do
    item1           <-  menuItemNewWithLabel (__ "Force")
    onMenuItemActivate item1 $ do
        mbSel  <-  getSelectedVariable treeView store
        case mbSel of
            Just (varDescr,path) -> reflectIDE (forceVariable varDescr path store) ideR
            otherwise     -> return ()
    sep1 <- separatorMenuItemNew >>= liftIO . toMenuItem
    item2           <-  menuItemNewWithLabel (__ "Print")
    onMenuItemActivate item2 $ do
        mbSel  <-  getSelectedVariable treeView store
        case mbSel of
            Just (varDescr,path) -> reflectIDE (printVariable varDescr path store) ideR
            otherwise     -> return ()
    item3           <-  menuItemNewWithLabel (__ "Update")
    onMenuItemActivate item3 $ reflectIDE (postAsyncIDE fillVariablesList) ideR
    mapM_ (menuShellAppend theMenu) [item1, item2, sep1, item3]


-- | Called when a variable is selected in the Variables pane.
-- Forces the variable and updates the entry in the pane,
variablesSelect :: IDERef
                -> ForestStore VarDescription
                -> TreePath
                -> TreeViewColumn
                -> IO ()
variablesSelect ideR store path _ = do
    varDescr <- forestStoreGetValue store path
    reflectIDE (forceVariable varDescr path store) ideR


-- | Force the value of the given 'VarDescription', retrieve its type
-- and update the appropriate entry in the Variables pane
forceVariable :: VarDescription -> TreePath -> ForestStore VarDescription -> IDEAction
forceVariable varDescr path forestStore = packageTry $ tryDebug $ do
    debugCommand' (":force " <> varName varDescr) $ do
        to <- CL.consume
        lift $ postAsyncIDE $
            case parse valueParser "" . T.unpack $ selectString to of
                Left e -> liftIO $ sysMessage Normal (T.pack $ show e)
                Right value -> do
                    var <- forestStoreGetValue forestStore path
                    forestStoreSetValue forestStore path var{varValue = value}
    debugCommand' (":type " <> varName varDescr) $ do
        to <- CL.consume
        lift $ postAsyncIDE $
            case parse typeParser "" . T.unpack $ selectString to of
                Left e -> liftIO $ sysMessage Normal (T.pack $ show e)
                Right typ -> do
                    var <- forestStoreGetValue forestStore path
                    forestStoreSetValue forestStore path var{varType = typ}


-- | Print the value of the given 'VarDescription', retrieve its type
-- and update the appropriate entry in the Variables pane
printVariable :: VarDescription -> TreePath -> ForestStore VarDescription -> IDEAction
printVariable varDescr path forestStore = packageTry $ tryDebug $ do
    debugCommand' (":print " <> varName varDescr) $ do
        to <- CL.consume
        lift $ postAsyncIDE $
            case parse valueParser "" . T.unpack $ selectString to of
                Left e -> liftIO $ sysMessage Normal (T.pack $ show e)
                Right value -> do
                    var <- forestStoreGetValue forestStore path
                    forestStoreSetValue forestStore path var{varValue = value}
    debugCommand' (":type " <> varName varDescr) $ do
        to <- CL.consume
        lift $ postAsyncIDE $
            case parse typeParser "" . T.unpack $ selectString to of
                Left e -> liftIO $ sysMessage Normal (T.pack $ show e)
                Right typ -> do
                    var <- forestStoreGetValue forestStore path
                    forestStoreSetValue forestStore path var{varType = typ}