{-# OPTIONS_GHC -XScopedTypeVariables -XDeriveDataTypeable -XMultiParamTypeClasses -XTypeSynonymInstances -XParallelListComp #-} -- -- Module : IDE.Pane.Log -- Copyright : (c) Juergen Nicklisch-Franken (aka Jutaro) -- License : GNU-GPL -- -- Maintainer : Juergen Nicklisch-Franken -- Stability : experimental -- Portability : portable -- -- | Log pane -- ------------------------------------------------------------------------------- module IDE.Pane.Log ( IDELog(..) , LogView(..) , LogAction(..) , LogState , LogTag(..) , readOut , readErr , runExternal ) where import Data.Typeable (Typeable(..)) import Text.ParserCombinators.Parsec import IDE.Core.State import Graphics.UI.Gtk import Graphics.UI.Gtk.Gdk.Events import Graphics.UI.Editor.MakeEditor (buildEditor) import Control.Monad.Trans (liftIO) import Graphics.UI.Editor.Basics (eventPaneName) import Graphics.UI.Editor.Basics (GUIEventSelector(..)) import Control.Event (registerEvent) import Graphics.UI.Editor.Simple (okCancelFields) import Graphics.UI.Editor.Parameters import Text.ParserCombinators.Parsec.Language (haskellDef) import Data.Char (isUpper) import qualified Text.ParserCombinators.Parsec.Token as P import Text.ParserCombinators.Parsec.Language (haskellStyle) import Data.List (foldl',sort,nub) import IDE.SourceCandy (getCandylessText) import IDE.Pane.SourceBuffer (inActiveBufContext') import IDE.Metainfo.Provider (getIdentifierDescr) import Debug.Trace (trace) import Data.List (nubBy) import IDE.Pane.SourceBuffer (markErrorInSourceBuf) import IDE.Pane.SourceBuffer (selectSourceBuf) import Graphics.UI.Editor.MakeEditor (FieldDescription(..),mkField) import Graphics.UI.Editor.Simple (staticListEditor) import Data.Maybe (isNothing,isJust) import Distribution.ModuleName(ModuleName) import Distribution.Text(disp) import Text.PrettyPrint (render) import System.Process import System.IO import Prelude hiding (catch) import Control.Exception hiding (try) ------------------------------------------------------------------------------- -- -- * Interface -- -- -- | The Log pane -- data IDELog = IDELog { textView :: TextView , scrolledWindowL :: ScrolledWindow } deriving Typeable data LogState = LogState deriving(Eq,Ord,Read,Show,Typeable) class LogAction alpha where clearLog :: alpha addAllImports :: alpha addOneImport :: alpha instance LogAction IDEAction where clearLog = clearLog' addAllImports = addAllImports' addOneImport = addOneImport' class Pane alpha beta => LogView alpha beta where getLog :: beta alpha appendLog :: alpha -> String -> LogTag -> IO Int markErrorInLog :: alpha -> (Int, Int) -> IO () instance IDEObject IDELog instance LogView IDELog IDEM where getLog = getLog' appendLog = appendLog' markErrorInLog = markErrorInLog' instance Pane IDELog IDEM where primPaneName _ = "Log" getAddedIndex _ = 0 getTopWidget = castToWidget . scrolledWindowL paneId b = "*Log" makeActive log = activatePane log [] close = closePane instance RecoverablePane IDELog LogState IDEM where saveState p = return (Just LogState) recoverState pp LogState = do nb <- getNotebook pp initLog pp nb ------------------------------------------------------------------------------- -- -- * Implementation -- initLog :: PanePath -> Notebook -> IDEAction initLog panePath nb = do panes <- readIDE panes paneMap <- readIDE paneMap prefs <- readIDE prefs (buf,cids) <- reifyIDE $ \ideR -> do tv <- textViewNew buf <- textViewGetBuffer tv iter <- textBufferGetEndIter buf textBufferCreateMark buf (Just "end") iter True tags <- textBufferGetTagTable buf errtag <- textTagNew (Just "err") set errtag[textTagForeground := "red"] textTagTableAdd tags errtag frametag <- textTagNew (Just "frame") set frametag[textTagForeground := "green"] textTagTableAdd tags frametag activeErrtag <- textTagNew (Just "activeErr") set activeErrtag[textTagBackground := "yellow"] textTagTableAdd tags activeErrtag textViewSetEditable tv True fd <- case logviewFont prefs of Just str -> do fontDescriptionFromString str Nothing -> do f <- fontDescriptionNew fontDescriptionSetFamily f "Sans" return f widgetModifyFont tv (Just fd) sw <- scrolledWindowNew Nothing Nothing containerAdd sw tv scrolledWindowSetPolicy sw PolicyAutomatic PolicyAutomatic scrolledWindowSetShadowType sw ShadowIn let buf = IDELog tv sw notebookInsertOrdered nb sw (paneName buf) Nothing widgetShowAll (scrolledWindowL buf) cid1 <- tv `afterFocusIn` (\_ -> do reflectIDE (makeActive buf) ideR ; return True) cid2 <- tv `onButtonPress` (\ b -> do reflectIDE (clicked b buf) ideR ; return True) return (buf,[ConnectC cid1, ConnectC cid2]) addPaneAdmin buf cids panePath liftIO $widgetGrabFocus (textView buf) clicked :: Event -> IDELog -> IDEAction clicked (Button _ SingleClick _ _ _ _ LeftButton x y) ideLog = do errors' <- readIDE errors line' <- liftIO $ do (x,y) <- widgetGetPointer (textView ideLog) (_,y') <- textViewWindowToBufferCoords (textView ideLog) TextWindowWidget (x,y) (iter,_) <- textViewGetLineAtY (textView ideLog) y' textIterGetLine iter case filter (\(es,_) -> fst (logLines es) <= (line'+1) && snd (logLines es) >= (line'+1)) (zip errors' [0..(length errors')]) of [(thisErr,n)] -> do succ <- selectSourceBuf (filePath thisErr) if isJust succ then markErrorInSourceBuf (line thisErr) (column thisErr) (errDescription thisErr) else return () log :: IDELog <- getLog liftIO $ markErrorInLog log (logLines thisErr) modifyIDE_ (\ide -> return (ide{currentErr = Just n})) otherwise -> return () clicked (Button _ SingleClick _ _ _ _ RightButton x y) ideLog = do errors' <- readIDE errors line' <- reifyIDE $ \ideR -> do (x,y) <- widgetGetPointer (textView ideLog) (_,y') <- textViewWindowToBufferCoords (textView ideLog) TextWindowWidget (x,y) (iter,_) <- textViewGetLineAtY (textView ideLog) y' textIterGetLine iter case filter (\(es,_) -> fst (logLines es) <= (line'+1) && snd (logLines es) >= (line'+1)) (zip errors' [0..(length errors')]) of [(thisErr,n)] -> reifyIDE $ \ideR -> do theMenu <- menuNew item0 <- menuItemNewWithLabel "Add all imports" item0 `onActivateLeaf` do reflectIDE addAllImports' ideR menuShellAppend theMenu item0 case parseNotInScope (errDescription thisErr) of Nothing -> do return () Just _ -> do item1 <- menuItemNewWithLabel "Add import" item1 `onActivateLeaf` do reflectIDE (addImport thisErr >> return()) ideR menuShellAppend theMenu item1 menuPopup theMenu Nothing widgetShowAll theMenu return () otherwise -> return () clicked _ _ = return () getLog' :: IDEM IDELog getLog' = do mbPane <- getPane case mbPane of Nothing -> do prefs <- readIDE prefs layout <- readIDE layout let pp = getStandardPanePath (logPanePath prefs) layout nb <- getNotebook pp initLog pp nb mbPane <- getPane case mbPane of Nothing -> throwIDE "Can't init log" Just l -> return l Just p -> return p simpleLog :: String -> IDEAction simpleLog str = do log :: IDELog <- getLog liftIO $ appendLog log str LogTag return () appendLog' :: IDELog -> String -> LogTag -> IO Int appendLog' l@(IDELog tv _) string tag = do buf <- textViewGetBuffer tv iter <- textBufferGetEndIter buf textBufferSelectRange buf iter iter textBufferInsert buf iter string iter2 <- textBufferGetEndIter buf case tag of LogTag -> return () ErrorTag -> do len <- textBufferGetCharCount buf strti <- textBufferGetIterAtOffset buf (len - length string) textBufferApplyTagByName buf "err" iter2 strti FrameTag -> do len <- textBufferGetCharCount buf strti <- textBufferGetIterAtOffset buf (len - length string) textBufferApplyTagByName buf "frame" iter2 strti textBufferMoveMarkByName buf "end" iter2 mbMark <- textBufferGetMark buf "end" line <- textIterGetLine iter2 case mbMark of Nothing -> return () Just mark -> textViewScrollMarkOnscreen tv mark bringPaneToFront l return line markErrorInLog' :: IDELog -> (Int,Int) -> IO () markErrorInLog' (IDELog tv _) (l1,l2) = do idleAdd (do buf <- textViewGetBuffer tv iter <- textBufferGetIterAtLineOffset buf (l1-1) 0 iter2 <- textBufferGetIterAtLineOffset buf l2 0 textBufferSelectRange buf iter iter2 textBufferMoveMarkByName buf "end" iter mbMark <- textBufferGetMark buf "end" case mbMark of Nothing -> return () Just mark -> do textViewScrollToMark tv mark 0.0 (Just (0.3,0.3)) return () return False) priorityDefaultIdle return () clearLog' :: IDEAction clearLog' = do log <- getLog buf <- liftIO$ textViewGetBuffer $textView log liftIO $textBufferSetText buf "" modifyIDE_ (\ide -> return (ide{errors = [], currentErr = Nothing})) ---------------------------------------------------------------------------- -- |* Import Wizard -- | Add all imports which gave error messages ... addAllImports' :: IDEAction addAllImports' = do errors <- readIDE errors mapM_ addImport [ y | (x,y) <- nubBy (\ (p1,_) (p2,_) -> p1 == p2) $ [(x,y) | (x,y) <- [((parseNotInScope . errDescription) e, e) | e <- errors]], isJust x] -- | Add import for current error ... addOneImport' :: IDEAction addOneImport' = do errors' <- readIDE errors currentErr' <- readIDE currentErr case currentErr' of Nothing -> do ideMessage Normal $ "No error selected" return () Just i -> do if 0 <= i && i < length errors' then let error = errors' !! i in addImport error >> return () else error "Log>>addOneImport: Error out of range" -- | Add one missing import addImport :: ErrorSpec -> IDEM (Bool,Maybe ModuleName) addImport error = case parseNotInScope (errDescription error) of Nothing -> trace "Dont parse as not in scope error" return (True,Nothing) Just nis -> do currentInfo' <- readIDE currentInfo case currentInfo' of Nothing -> trace "No current info" return (False,Nothing) Just ((_,symbolTable1),(_,symbolTable2)) -> case (getIdentifierDescr (id' nis) symbolTable1 symbolTable2) of [] -> do ideMessage Normal $ "Identifier " ++ (id' nis) ++ " not found in imported packages" return (True,Nothing) descr : [] -> addImport' nis (filePath error) descr list -> do mbDescr <- liftIO $ selectModuleDialog list (id' nis) case mbDescr of Nothing -> return (False,Nothing) Just descr -> addImport' nis (filePath error) descr addImport' :: NotInScopeParseResult -> FilePath -> Descr -> IDEM (Bool,Maybe ModuleName) addImport' nis filePath descr = let mbQ = mbQual' nis id = id' nis mod = case descr of Descr _ _ m _ _ _ -> modu m Reexported m _ -> modu m importId = if isSub' nis then ImportCoC (getRealId descr id) Nothing else if isOp' nis then ImportOp (id' nis) else ImportId (id' nis) importSpec = ImportSpec (render $disp mod) mbQ (Just ([importId],True)) 0 in do candy' <- readIDE candy mbBuf <- selectSourceBuf filePath case mbBuf of Nothing -> return (True,Nothing) Just buf -> do inActiveBufContext' () $ \ _ gtkbuf _ _ -> do ideMessage Normal $ "addImport " ++ show id ++ " from " ++ (render $ disp $ mod) liftIO $ do i1 <- textBufferGetStartIter gtkbuf i2 <- textBufferGetEndIter gtkbuf text <- getCandylessText candy' gtkbuf let (importSpecs,lastImport) = getImportSpecs text linesToModify = filter (\ is -> modid is == (render $ disp $ mod) && mbQual is == mbQ && not(isHiding (mbImpspec is))) importSpecs case linesToModify of [] -> let newSpec = importSpec newLine = show newSpec ++ "\n" lineSel = let bv = foldl' max 0 (map lineNr importSpecs) in if bv == 0 then (lastImport `max` 0) else bv in do textIterSetLine i1 lineSel textBufferInsert gtkbuf i1 newLine (spec:t) -> let newSpec = spec{mbImpspec = case mbImpspec spec of Nothing -> Just ([importId],True) Just (ids,b) -> Just (nub(importId:ids),b)} newLine = show newSpec ++ "\n" in do textIterSetLine i1 (lineNr spec - 1) textIterSetLine i2 (lineNr spec) textBufferDelete gtkbuf i1 i2 textBufferInsert gtkbuf i1 newLine return (True,Just mod) where isHiding (Just (_,False)) = True isHiding _ = False getRealId descr id = if isReexported descr then getRealId (impDescr descr) id else getReal (details' descr) where getReal (FieldDescr d) = descrName' d getReal (ConstructorDescr d) = descrName' d getReal (MethodDescr d) = descrName' d getReal _ = id -- |* The import data data NotInScopeParseResult = NotInScopeParseResult { mbQual' :: Maybe String , id' :: String , isSub' :: Bool , isOp' :: Bool} deriving Eq data ImportSpec = ImportSpec { modid :: String , mbQual :: Maybe String , mbImpspec :: Maybe ([ImportItem],Bool) , lineNr :: Int} data ImportItem = ImportId String | ImportOp String | ImportCoC String (Maybe [ImportSubItem]) deriving Eq instance Show ImportItem where show (ImportId str) = str show (ImportOp str) = "(" ++ str ++ ")" show (ImportCoC str Nothing) = str ++ "(..)" show (ImportCoC str (Just [])) = str show (ImportCoC str (Just l)) = str ++ "(" ++ showCoC l where showCoC [a] = show a ++ ")" showCoC (a:b) = show a ++ "," ++ showCoC b showCoC _ = error "Log>>instance ImportItem Show: Empty list" data ImportSubItem = ImportSubId String | ImportSubOp String deriving Eq instance Show ImportSubItem where show (ImportSubId str) = str show (ImportSubOp str) = "(" ++ str ++ ")" instance Show ImportSpec where show (ImportSpec modid mbQual mbImpspec _) = "import " ++ case mbQual of Nothing -> "" Just _ -> "qualified " ++ modid ++ case mbQual of Nothing -> "" Just str -> " as " ++ str ++ " " ++ case mbImpspec of Nothing -> "" Just ([],True) -> "" Just (l,True) -> " (" ++ showImportSpecs l Just (l, False) -> " hiding (" ++ showImportSpecs l where showImportSpecs [a] = show a ++ ")" showImportSpecs (a:b) = show a ++ "," ++ showImportSpecs b showImportSpecs _ = error "Log>>showImportSpecs: Empty list" -- |* The error line parser lexer = P.makeTokenParser haskellStyle lexeme = P.lexeme lexer whiteSpace = P.whiteSpace lexer symbol = P.symbol lexer identifier = P.identifier lexer comma = P.comma lexer dot = P.dot lexer integer = P.integer lexer parens = P.parens lexer operator = P.operator lexer parseNotInScope :: String -> (Maybe NotInScopeParseResult) parseNotInScope str = case parse scopeParser "" str of Left e -> Nothing Right r -> Just r scopeParser :: CharParser () NotInScopeParseResult scopeParser = do whiteSpace symbol "Not in scope:" isSub <- optionMaybe (try (choice [symbol "type constructor or class" , symbol "data constructor"])) symbol "`" mbQual <- optionMaybe (try (do q <- lexeme conid dot return q)) id <- optionMaybe (try identifier) case id of Just id -> return (NotInScopeParseResult mbQual (take (length id - 1) id) (isJust isSub) False) Nothing -> do op <- operator symbol "'" return (NotInScopeParseResult mbQual op (isJust isSub) True) "scopeParser" -- |* The import parser getImportSpecs :: String -> ([ImportSpec],Int) getImportSpecs modSource = case parse importSpecParser "" modSource of Left e -> trace ("parse failed with " ++ show e) ([],0) Right specs -> trace (show specs) specs importSpecParser :: CharParser () ([ImportSpec],Int) importSpecParser = do whiteSpace skipHeader lines <- sepBy (try parseImportLine) (many newline) pos <- getPosition skipMany anyChar eof return (lines, sourceLine pos - 2) "importSpecParser" skipHeader :: CharParser () () skipHeader =do try (do symbol "module" manyTill anyChar (try (symbol "where")) trace "with module header " (return ())) <|> trace "no module header " (return ()) "skipHeader" parseImportLine :: CharParser () ImportSpec parseImportLine = do symbol "import" pos <- getPosition isQualified <- optionMaybe (try (symbol "qualified")) modid <- lexeme mident whiteSpace mbQName <- if isJust isQualified then do symbol "as" qid <- identifier return (Just qid) else return Nothing mbImpSpec <- optionMaybe (try (do mbHiding <- optionMaybe (try (symbol "hiding")) idList <- parens (sepBy parseImportItem comma) return (idList, isNothing mbHiding))) whiteSpace return (ImportSpec modid mbQName mbImpSpec (sourceLine pos)) "parseImportLine" parseImportItem :: CharParser () ImportItem parseImportItem = do id <- optionMaybe (try identifier) case id of Just id -> if isUpper (head id) then do ccList <- optionMaybe (try parseSubList) return (ImportCoC id Nothing) else return (ImportId id) Nothing -> do op <- optionMaybe (try (parens operator)) case op of Just op -> return (ImportOp op) Nothing -> do pos <- getPosition error $ "Can't parse import item " ++ show pos "parseImportItem" parseSubList :: CharParser () (Maybe [ImportSubItem]) parseSubList = do all <- optionMaybe (try (symbol "(..)")) case all of Just _ -> return Nothing Nothing -> do list <- optionMaybe (try (parens (sepBy parseSubItem comma))) case list of Nothing -> return (Just []) Just l -> return list "parseSubList" parseSubItem :: CharParser () ImportSubItem parseSubItem = do id <- optionMaybe (try identifier) case id of Just id -> return (ImportSubId id) Nothing -> do op <- optionMaybe (try operator) case op of Just op -> return (ImportSubOp op) Nothing -> do pos <- getPosition error $ "Can't parse sub import item " ++ show pos "parseSubItem" mident = do c <- P.identStart haskellDef cs <- many (alphaNum <|> oneOf "_'.") return (c:cs) "midentifier" conid = do c <- upper cs <- many (alphaNum <|> oneOf "_'") return (c:cs) "conid" -- |* The stupid little dialog moduleFields :: [String] -> String -> FieldDescription String moduleFields list id = mkField (paraName <<<- ParaName ("From which module is " ++ id) $ paraMultiSel <<<- ParaMultiSel False $ emptyParams) (\ a -> [a]) (\ [a] b -> a) (staticListEditor ((nub . sort) list)) selectModuleDialog :: [Descr] -> String -> IO (Maybe Descr) selectModuleDialog list id = do dia <- dialogNew upper <- dialogGetUpper dia lower <- dialogGetActionArea dia (widget,_,ext,_) <- buildEditor (moduleFields (map (render . disp . modu . descrModu') list) id) ((render . disp . modu . descrModu') (head list)) (widget2,_,_,notifier) <- buildEditor okCancelFields () registerEvent notifier Clicked (Left (\e -> do case eventPaneName e of "Ok" -> dialogResponse dia ResponseOk _ -> dialogResponse dia ResponseCancel return e)) boxPackStart upper widget PackGrow 7 boxPackStart lower widget2 PackNatural 7 widgetShowAll dia resp <- dialogRun dia value <- ext ([]) widgetDestroy dia --find case (resp,value) of (ResponseOk,Just v) -> return (Just (head (filter (\e -> (render . disp . modu . descrModu') e == v) list))) _ -> trace ("cancel") return Nothing -- --------------------------------------------------------------------- -- ** Spawning external processes -- readOut :: IDELog -> Handle -> IO () readOut log hndl = catch (readAndShow) (\(e :: SomeException) -> do --appendLog log ("----------------------------------------\n") FrameTag hClose hndl return ()) where readAndShow = do line <- hGetLine hndl appendLog log (line ++ "\n") LogTag readAndShow readErr :: IDELog -> Handle -> IO () readErr log hndl = catch (readAndShow) (\(e :: SomeException) -> do hClose hndl return ()) where readAndShow = do line <- hGetLine hndl appendLog log (line ++ "\n") ErrorTag readAndShow runExternal :: FilePath -> [String] -> IO (Handle, Handle, Handle, ProcessHandle) runExternal path args = do putStrLn $ "Run external called with args " ++ show args hndls@(inp, out, err, _) <- runInteractiveProcess path args Nothing Nothing sysMessage Normal $ "Starting external tool: " ++ path ++ " with args " ++ (show args) hSetBuffering out NoBuffering hSetBuffering err NoBuffering hSetBuffering inp NoBuffering hSetBinaryMode out True hSetBinaryMode err True return hndls