Commit f3bbe00b authored by David Davó's avatar David Davó

Added buffer paths to widgets

parent 5a03d3dd
...@@ -38,6 +38,20 @@ You can see more info on the model state of widgets [here](https://github.com/ju ...@@ -38,6 +38,20 @@ You can see more info on the model state of widgets [here](https://github.com/ju
This state is also used with fragments of the overall state to sync changes between the frontend and This state is also used with fragments of the overall state to sync changes between the frontend and
the kernel. the kernel.
### Buffer paths
To display some widgets, we need to use the `buffer_paths`. It's only an array with arrays of keys on how to get to the fields that are to considered a
byte stream. For example, in an image widget, `buffer_paths` would be the array `[ ["value"] ]`, which means that `state.value` is a buffer path. The buffers are specified in the header, so the n-th buffer corresponds to the n-th buffer path.
```json
"data": {
"state": {
"value": ...,
...
},
"buffer_paths": ["value"]
}
```
## Displaying widgets ## Displaying widgets
The creation of a widget does not display it. To display a widget, the kernel sends a display The creation of a widget does not display it. To display a widget, the kernel sends a display
......
...@@ -19,7 +19,12 @@ import Control.Concurrent.STM.TChan ...@@ -19,7 +19,12 @@ import Control.Concurrent.STM.TChan
import Control.Monad (foldM) import Control.Monad (foldM)
import Data.Aeson import Data.Aeson
import Data.Aeson.Types (emptyArray) import Data.Aeson.Types (emptyArray)
import Data.ByteString.Lazy (toStrict)
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Text.Encoding (encodeUtf8)
import Data.HashMap.Strict as HM (lookup,insert,delete)
import Data.Functor ((<&>))
import Data.Foldable (foldl)
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
import IHaskell.Display import IHaskell.Display
...@@ -100,10 +105,12 @@ handleMessage send replyHeader state msg = do ...@@ -100,10 +105,12 @@ handleMessage send replyHeader state msg = do
newComms = Map.insert uuid widget oldComms newComms = Map.insert uuid widget oldComms
newState = state { openComms = newComms } newState = state { openComms = newComms }
content = object [ "state" .= value, "buffer_paths" .= emptyArray ] (newvalue,buffers,bp) = processBPs value $ getBufferPaths widget
applyBuffers x = x {mhBuffers = buffers}
content = object [ "state" .= newvalue, "buffer_paths" .= bp ]
communicate val = do communicate val = do
head <- dupHeader replyHeader CommDataMessage head <- dupHeader replyHeader CommDataMessage <&> applyBuffers
send $ CommData head uuid val send $ CommData head uuid val
-- If the widget is present, don't open it again. -- If the widget is present, don't open it again.
...@@ -111,7 +118,7 @@ handleMessage send replyHeader state msg = do ...@@ -111,7 +118,7 @@ handleMessage send replyHeader state msg = do
then return state then return state
else do else do
-- Send the comm open, with the initial state -- Send the comm open, with the initial state
hdr <- dupHeader replyHeader CommOpenMessage hdr <- dupHeader replyHeader CommOpenMessage <&> applyBuffers
let hdrV = setVersion hdr "2.0.0" -- Widget Messaging Protocol Version let hdrV = setVersion hdr "2.0.0" -- Widget Messaging Protocol Version
send $ CommOpen hdrV target_name target_module uuid content send $ CommOpen hdrV target_name target_module uuid content
...@@ -138,7 +145,9 @@ handleMessage send replyHeader state msg = do ...@@ -138,7 +145,9 @@ handleMessage send replyHeader state msg = do
View widget -> sendMessage widget (toJSON DisplayWidget) View widget -> sendMessage widget (toJSON DisplayWidget)
Update widget value -> sendMessage widget (toJSON $ UpdateState value) Update widget value -> do
let (newvalue,buffers,bp) = processBPs value $ getBufferPaths widget
sendMessageHdr widget (toJSON $ UpdateState newvalue bp) (\h->h {mhBuffers=buffers})
Custom widget value -> sendMessage widget (toJSON $ CustomContent value) Custom widget value -> sendMessage widget (toJSON $ CustomContent value)
...@@ -156,13 +165,14 @@ handleMessage send replyHeader state msg = do ...@@ -156,13 +165,14 @@ handleMessage send replyHeader state msg = do
where where
oldComms = openComms state oldComms = openComms state
sendMessage widget value = do sendMessage widget value = sendMessageHdr widget value id
sendMessageHdr widget value hdrf = do
let uuid = getCommUUID widget let uuid = getCommUUID widget
present = isJust $ Map.lookup uuid oldComms present = isJust $ Map.lookup uuid oldComms
-- If the widget is present, we send an update message on its comm. -- If the widget is present, we send an update message on its comm.
when present $ do when present $ do
hdr <- dupHeader replyHeader CommDataMessage hdr <- dupHeader replyHeader CommDataMessage <&> hdrf
send $ CommData hdr uuid value send $ CommData hdr uuid value
return state return state
...@@ -170,6 +180,33 @@ handleMessage send replyHeader state msg = do ...@@ -170,6 +180,33 @@ handleMessage send replyHeader state msg = do
unwrap (ManyDisplay ds) = concatMap unwrap ds unwrap (ManyDisplay ds) = concatMap unwrap ds
unwrap (Display ddatas) = ddatas unwrap (Display ddatas) = ddatas
-- Removes the values that are buffers and puts them in the third value of the tuple
-- The returned bufferpaths are the bufferpaths used
processBPs :: Value -> [BufferPath] -> (Value, [ByteString], [BufferPath])
-- Searching if the BufferPath key is in the Object is O(log n) or O(1) depending on implementation
-- For this reason we fold on the bufferpaths
processBPs val = foldl f (val,[],[])
where
nestedLookupRemove :: BufferPath -> Value -> (Value, Maybe Value)
nestedLookupRemove [] v = (v,Just v)
nestedLookupRemove [b] v =
case v of
Object o -> (Object $ HM.delete b o, HM.lookup b o)
_ -> (v, Nothing)
nestedLookupRemove (b:bp) v =
case v of
Object o -> maybe (v,Nothing) (upd . nestedLookupRemove bp) (HM.lookup b o)
_ -> (v,Nothing)
where upd :: (Value, Maybe Value) -> (Value, Maybe Value)
upd (Object v', Just (Object u)) = (Object $ HM.insert b (Object u) v', Just $ Object u)
upd r = r
f :: (Value, [ByteString], [BufferPath]) -> BufferPath -> (Value, [ByteString], [BufferPath])
f r@(v,bs,bps) bp =
case nestedLookupRemove bp v of
(newv, Just (String b)) -> (newv, encodeUtf8 b : bs, bp:bps)
_ -> r
-- Override toJSON for PublishDisplayData for sending Display messages through [method .= custom] -- Override toJSON for PublishDisplayData for sending Display messages through [method .= custom]
data WidgetDisplay = WidgetDisplay MessageHeader [DisplayData] data WidgetDisplay = WidgetDisplay MessageHeader [DisplayData]
......
...@@ -14,6 +14,7 @@ module IHaskell.Types ( ...@@ -14,6 +14,7 @@ module IHaskell.Types (
setVersion, setVersion,
Username, Username,
Metadata, Metadata,
BufferPath,
replyType, replyType,
ExecutionState(..), ExecutionState(..),
StreamType(..), StreamType(..),
...@@ -61,6 +62,11 @@ import IHaskell.IPython.Kernel ...@@ -61,6 +62,11 @@ import IHaskell.IPython.Kernel
class IHaskellDisplay a where class IHaskellDisplay a where
display :: a -> IO Display display :: a -> IO Display
type BufferPath = [Text]
emptyBPs :: [BufferPath]
emptyBPs = []
-- | Display as an interactive widget. -- | Display as an interactive widget.
class IHaskellDisplay a => IHaskellWidget a where class IHaskellDisplay a => IHaskellWidget a where
-- | Target name for this widget. The actual input parameter should be ignored. By default evaluate -- | Target name for this widget. The actual input parameter should be ignored. By default evaluate
...@@ -72,6 +78,10 @@ class IHaskellDisplay a => IHaskellWidget a where ...@@ -72,6 +78,10 @@ class IHaskellDisplay a => IHaskellWidget a where
targetModule :: a -> String targetModule :: a -> String
targetModule _ = "" targetModule _ = ""
-- | Buffer paths for this widget. Evaluates to an empty array by default.
getBufferPaths :: a -> [BufferPath]
getBufferPaths _ = emptyBPs
-- | Get the uuid for comm associated with this widget. The widget is responsible for storing the -- | Get the uuid for comm associated with this widget. The widget is responsible for storing the
-- UUID during initialization. -- UUID during initialization.
getCommUUID :: a -> UUID getCommUUID :: a -> UUID
...@@ -127,6 +137,7 @@ instance IHaskellDisplay Widget where ...@@ -127,6 +137,7 @@ instance IHaskellDisplay Widget where
instance IHaskellWidget Widget where instance IHaskellWidget Widget where
targetName (Widget widget) = targetName widget targetName (Widget widget) = targetName widget
targetModule (Widget widget) = targetModule widget targetModule (Widget widget) = targetModule widget
getBufferPaths (Widget widget) = getBufferPaths widget
getCommUUID (Widget widget) = getCommUUID widget getCommUUID (Widget widget) = getCommUUID widget
open (Widget widget) = open widget open (Widget widget) = open widget
comm (Widget widget) = comm widget comm (Widget widget) = comm widget
...@@ -243,13 +254,13 @@ data WidgetMsg = Open Widget Value ...@@ -243,13 +254,13 @@ data WidgetMsg = Open Widget Value
-- ^ A 'clear_output' message, sent as a [method .= custom] comm_msg -- ^ A 'clear_output' message, sent as a [method .= custom] comm_msg
deriving (Show, Typeable) deriving (Show, Typeable)
data WidgetMethod = UpdateState Value data WidgetMethod = UpdateState Value [BufferPath]
| CustomContent Value | CustomContent Value
| DisplayWidget | DisplayWidget
instance ToJSON WidgetMethod where instance ToJSON WidgetMethod where
toJSON DisplayWidget = object ["method" .= ("display" :: Text)] toJSON DisplayWidget = object ["method" .= ("display" :: Text)]
toJSON (UpdateState v) = object ["method" .= ("update" :: Text), "state" .= v] toJSON (UpdateState v bp) = object ["method" .= ("update" :: Text), "state" .= v, "buffer_paths" .= bp]
toJSON (CustomContent v) = object ["method" .= ("custom" :: Text), "content" .= v] toJSON (CustomContent v) = object ["method" .= ("custom" :: Text), "content" .= v]
-- | Output of evaluation. -- | Output of evaluation.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment