{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE TypeFamilies #-} #ifdef LEKSAH_WITH_CODE_MIRROR {-# LANGUAGE RecordWildCards #-} #endif {-# LANGUAGE RankNTypes #-} {-# LANGUAGE NoImplicitPrelude #-} ----------------------------------------------------------------------------- -- -- Module : IDE.TextEditor.CodeMirror -- Copyright : 2007-2013 Juergen Nicklisch-Franken, Hamish Mackenzie -- License : GPL Nothing -- -- Maintainer : maintainer@leksah.org -- Stability : provisional -- Portability : -- -- | -- ----------------------------------------------------------------------------- module IDE.TextEditor.CodeMirror ( CodeMirror(..) #ifdef LEKSAH_WITH_CODE_MIRROR , TextEditor(..) , EditorBuffer(..) , EditorView(..) , EditorIter(..) , EditorMark(..) , EditorTag(..) , EditorTagTable(..) , newCMBuffer #endif ) where import Data.Typeable (Typeable) import Data.Text (Text) import Text.Show (Show) #ifdef LEKSAH_WITH_CODE_MIRROR import Data.Tuple (snd, fst) import Data.Function (($), (.)) import Data.Maybe (Maybe, Maybe(..)) import GHC.Base (Functor(..), Monad(..)) import Data.Int (Int) import System.IO (FilePath) import Data.List ((++)) import Data.Bool (Bool(..), not) import GHC.Real (fromIntegral, RealFrac(..)) import GHC.Num (Num(..)) import Data.Eq (Eq(..)) import GHC.Float (Double) import qualified Data.Text as T (pack) import Control.Lens.Getter (to) import Control.Monad (liftM, (=<<)) import GI.WebKit.Objects.WebView (onWebViewPopulatePopup, webViewLoadString, webViewGetMainFrame, onWebViewLoadFinished, webViewNew, WebView(..)) import GI.Gtk.Functions (mainIteration) import GI.Gtk.Objects.ScrolledWindow (ScrolledWindow(..), scrolledWindowSetShadowType, scrolledWindowNew) import GI.Gtk.Objects.Adjustment (noAdjustment) import GI.Gtk.Enums (ShadowType(..)) import GI.Gtk.Objects.Container (containerAdd) import GI.WebKit.Objects.WebFrame (webFrameGetGlobalContext) import GI.Gtk.Objects.Widget (widgetAddEvents, onWidgetKeyReleaseEvent, onWidgetLeaveNotifyEvent, onWidgetMotionNotifyEvent, onWidgetKeyPressEvent, onWidgetButtonReleaseEvent, onWidgetButtonPressEvent, afterWidgetFocusInEvent, widgetGrabFocus, toWidget, widgetGetParent, widgetGetWindow) import Graphics.UI.Frame.Rectangle (newRectangle) import Data.GI.Base.ManagedPtr (withManagedPtr, unsafeCastTo) import GI.Gdk.Flags (ModifierType(..), EventMask(..)) import GI.Gdk.Structs.EventButton (getEventButtonState) import GI.JavaScriptCore.Structs.GlobalContext (GlobalContext(..)) import Foreign.Ptr (castPtr) import Data.GI.Base.BasicConversions (gflagsToWord) import Data.GI.Base.Attributes (AttrOp(..)) import Control.Monad (unless) import Data.Text (pack, unpack) import IDE.TextEditor.Class (TextEditor(..)) import Control.Monad.Reader (ReaderT(..)) import Language.Javascript.JSaddle (valToObject, (#), JSContextRef, Object, jsg, jsg2, (<#), obj, js2, jss, js, JSM, js1, valToText, valToStr, js3, js0, ToJSVal(..), ToJSString(..), JSString, JSVal, valToBool, strToText, valToNumber, MakeObject(..)) import Control.Applicative ((<$>)) import Control.Monad.Reader.Class (MonadReader(..)) import Control.Concurrent (putMVar, newEmptyMVar, takeMVar, MVar, tryTakeMVar) import IDE.Core.Types (IDEM) import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.Trans.Class (MonadTrans(..)) import Control.Lens ((^.), IndexPreservingGetter) import qualified GHCJS.CodeMirror as CM (getDataDir) import Text.Blaze.Html.Renderer.Text (renderHtml) import Text.Hamlet (shamlet) import Data.Maybe (fromJust) import IDE.Core.State (onIDE, reflectIDE, leksahOrPackageDir) import Graphics.UI.Editor.Basics (Connection(..)) import System.Log.Logger (debugM) #endif data CodeMirror = CodeMirror deriving( Typeable, Show ) #ifdef LEKSAH_WITH_CODE_MIRROR data CodeMirrorState = CodeMirrorState { cmContext :: GlobalContext , cmObject :: Object } type CM = ReaderT (WebView, CodeMirrorState) JSM webView :: CM WebView webView = fst <$> ask codeMirror :: CM Object codeMirror = cmObject . snd <$> ask runCM :: CodeMirrorRef -> CM a -> IDEM a runCM (v, mvar) f = liftIO $ do s <- guiTakeMVar mvar withManagedPtr (cmContext s) (runReaderT (runReaderT f (v, s)) . castPtr) where guiTakeMVar mvar = do maybeValue <- tryTakeMVar mvar case maybeValue of Just value -> do putMVar mvar value return value Nothing -> do debugM "leksah" "looping" s <- loop mvar debugM "leksah" "done looping" return s loop mvar = do maybeValue <- tryTakeMVar mvar case maybeValue of Just value -> do putMVar mvar value return value Nothing -> do mainIteration loop mvar type CodeMirrorRef = (WebView, MVar CodeMirrorState) gCodeMirror x y = jsg2 "CodeMirror" x y >>= valToObject body = js "body" setValue v = jss "value" v setSize = js2 "setSize" setMode m = jss "mode" m line = js "line" . to valToNumber setLine l = jss "line" l ch = js "ch" . to valToNumber setCh c = jss "ch" c setLeft l = jss "left" l setTop t = jss "top" t left = js "left" . to valToNumber top = js "top" . to valToNumber right = js "right" . to valToNumber bottom = js "bottom" . to valToNumber lastLine = js0 "lastLine" . to valToNumber getRange x y = js2 "getRange" x y . to valToStr callSetValue = js1 "setValue" setBookmark' = js2 "setBookmark" find = js0 "find" . to valToObject from = js "from" . to valToObject getCursor x = js1 "getCursor" x . to valToObject isClean = js0 "isClean" . to valToBool markText = js3 "markText" className = "className" clearHistory = js0 "clearHistory" callUndo = js0 "undo" undo' = js "undo" . to valToBool callRedo = js0 "redo" redo' = js "redo" . to valToBool historySize = js0 "historySize" replaceRange = js3 "replaceRange" insertAt = js2 "replaceRange" replaceSelection = js1 "replaceSelection" posFromIndex x = js1 "posFromIndex" x . to valToObject lineCount = js0 "lineCount" . to valToNumber somethingSelected = js0 "somethingSelected" . to valToBool setSelection = js2 "setSelection" placeCursorAt = js1 "setSelection" markClean = js0 "markClean" coordsChar pos n = js2 "coordsChar" pos n . to valToObject charCoords = js2 "charCoords" scrollIntoView = js2 "scrollIntoView" getAllMarks = js0 "getAllMarks" indexFromPos p = js1 "indexFromPos" p . to valToNumber getLineText l = js1 "getLine" l jsLength = js "length" . to valToNumber cmIter :: CodeMirrorRef -> Int -> Int -> CM (EditorIter CodeMirror) cmIter cm l c = lift $ do i <- obj i ^. setLine (fromIntegral l :: Double) i ^. setCh (fromIntegral c :: Double) return $ CMIter cm i newCMBuffer :: Maybe FilePath -> Text -> IDEM (EditorBuffer CodeMirror) newCMBuffer mbFilename contents = do ideR <- ask liftIO $ do debugM "leksah" "newCMBuffer" scrolledWindow <- scrolledWindowNew noAdjustment noAdjustment scrolledWindowSetShadowType scrolledWindow ShadowTypeIn cmWebView <- webViewNew containerAdd scrolledWindow cmWebView dataDir <- liftIO $ leksahOrPackageDir "ghcjs-codemirror" CM.getDataDir s <- newEmptyMVar onWebViewLoadFinished cmWebView $ \ _ -> do debugM "leksah" "newCMBuffer loadFinished" cmContext <- webViewGetMainFrame cmWebView >>= webFrameGetGlobalContext let runjs f = withManagedPtr cmContext (runReaderT f . castPtr) runjs $ do document <- jsg "document" code <- obj code ^. setValue contents code ^. setMode "haskell" cmObject <- gCodeMirror (document ^. body) code cmObject ^. setSize "100%" "100%" liftIO $ debugM "leksah" "newCMBuffer loaded" liftIO . putMVar s $ CodeMirrorState{..} webViewLoadString cmWebView (T.pack $ "" ++ "