leksah-0.13.4.3: src/IDE/Pane/HLint.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances, DeriveDataTypeable, MultiParamTypeClasses,
TypeSynonymInstances, RecordWildCards #-}
-----------------------------------------------------------------------------
--
-- Module : IDE.Pane.HLint
-- Copyright : (c) Juergen Nicklisch-Franken, Hamish Mackenzie
-- License : GNU-GPL
--
-- Maintainer : <maintainer at leksah.org>
-- Stability : provisional
-- Portability : portable
--
-- | The pane of ide where HLint results are displayed
--
-----------------------------------------------------------------------------
module IDE.Pane.HLint (
IDEHLint(..)
, refreshHLint
, HLintState(..)
, getHLint
) where
import Graphics.UI.Gtk hiding (get)
import qualified Graphics.UI.Gtk.Gdk.Events as Gdk
import Text.ParserCombinators.Parsec.Language
import Text.ParserCombinators.Parsec hiding(Parser)
import qualified Text.ParserCombinators.Parsec.Token as P
import Data.Maybe
import Data.Typeable
import IDE.Core.State hiding (SrcSpan(..))
import IDE.BufferMode
import IDE.LogRef (logOutput, defaultLineLogger)
import IDE.Pane.SourceBuffer
(goToSourceDefinition, maybeActiveBuf, IDEBuffer(..), replaceHLintSource)
import IDE.TextEditor (grabFocus)
import Control.Applicative ((<$>))
import System.FilePath ((</>), dropFileName)
import System.Exit (ExitCode(..))
import IDE.Pane.Log (getLog)
import Control.Monad (void, forM_, foldM, when)
import Control.Monad.Trans.Reader (ask)
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.IO.Class (MonadIO(..))
import Language.Haskell.HLint (Suggestion(..), suggestionLocation)
import Language.Haskell.Exts (SrcLoc(..))
import Language.Haskell.Exts.SrcLoc (SrcSpan(..))
import qualified Language.Haskell.HLint2 as H
import IDE.Utils.GUIUtils (__, treeViewContextMenu)
import Data.List (isPrefixOf, findIndex)
import Debug.Trace (trace)
import Control.Exception (SomeException, catch)
import Distribution.ModuleName (ModuleName)
import IDE.Metainfo.Provider (getWorkspaceInfo)
import qualified Data.Map as Map (keys, lookup)
import Distribution.Package (PackageIdentifier(..))
data HLintRecord = HLintRecord {
condPackage :: Maybe IDEPackage
, context :: String
, condIdea :: Maybe H.Idea
, parDir :: Maybe FilePath
} deriving (Eq)
isDir HLintRecord{parDir = Nothing} = True
isDir otherwies = False
-- | A HLint pane description
--
data IDEHLint = IDEHLint {
scrolledView :: ScrolledWindow
, treeView :: TreeView
, hlintStore :: TreeStore HLintRecord
} deriving Typeable
data HLintState = HLintState
deriving(Eq,Ord,Read,Show,Typeable)
instance Pane IDEHLint IDEM
where
primPaneName _ = "HLint"
getAddedIndex _ = 0
getTopWidget = castToWidget . scrolledView
paneId b = "*HLint"
instance RecoverablePane IDEHLint HLintState IDEM where
saveState p = return (Just HLintState)
recoverState pp HLintState = do
nb <- getNotebook pp
buildPane pp nb builder
builder pp nb windows = reifyIDE $ \ ideR -> do
hlintStore <- treeStoreNew []
treeView <- treeViewNew
treeViewSetModel treeView hlintStore
renderer1 <- cellRendererTextNew
renderer10 <- cellRendererPixbufNew
col1 <- treeViewColumnNew
treeViewColumnSetTitle col1 (__ "Context")
treeViewColumnSetSizing col1 TreeViewColumnAutosize
treeViewColumnSetResizable col1 True
treeViewColumnSetReorderable col1 True
treeViewAppendColumn treeView col1
cellLayoutPackStart col1 renderer10 False
cellLayoutPackStart col1 renderer1 True
cellLayoutSetAttributes col1 renderer1 hlintStore
$ \row -> [ cellText := context row ]
treeViewSetHeadersVisible treeView False
sel <- treeViewGetSelection treeView
treeSelectionSetMode sel SelectionSingle
scrolledView <- scrolledWindowNew Nothing Nothing
scrolledWindowSetShadowType scrolledView ShadowIn
containerAdd scrolledView treeView
scrolledWindowSetPolicy scrolledView PolicyAutomatic PolicyAutomatic
let hlint = IDEHLint {..}
cid1 <- after treeView focusInEvent $ do
liftIO $ reflectIDE (makeActive hlint) ideR
return True
cid2 <- on treeView rowExpanded $ \ iter path -> do
record <- treeStoreGetValue hlintStore path
case record of
HLintRecord { condPackage = Just p, parDir = Nothing } ->
reflectIDE (refreshDir hlintStore iter p) ideR
_ -> reflectIDE (ideMessage Normal (__ "Unexpected Expansion in HLint Pane")) ideR
cid3 <- on treeView rowActivated $ \ path col -> do
record <- treeStoreGetValue hlintStore path
mbIter <- treeModelGetIter hlintStore path
case (mbIter, record) of
(Just iter, HLintRecord { condPackage = Just p, parDir = Nothing }) ->
reflectIDE (refreshDir hlintStore iter p) ideR
_ -> return ()
cid4 <- on treeView keyPressEvent $ do
name <- eventKeyName
liftIO $ case name of
"Return" -> reflectIDE (gotoSource True treeView hlintStore) ideR
"Escape" -> do
reflectIDE (do
lastActiveBufferPane ?>>= \paneName -> do
(PaneC pane) <- paneFromName paneName
makeActive pane
return ()
triggerEventIDE StartFindInitial) ideR
return True
-- gotoSource True
_ -> return False
treeViewContextMenu treeView
$ hlintContextMenu ideR hlintStore treeView
on sel treeSelectionSelectionChanged (reflectIDE (void $ gotoSource False treeView hlintStore) ideR)
return (Just hlint,map ConnectC [cid1])
getHLint :: Maybe PanePath -> IDEM IDEHLint
getHLint Nothing = forceGetPane (Right "*HLint")
getHLint (Just pp) = forceGetPane (Left pp)
data FindResult = WhereExpected TreeIter | Found TreeIter | NotFound
find :: Eq a => a -> TreeStore a -> Maybe TreeIter -> IO FindResult
find _ _ Nothing = return NotFound
find a store (Just iter) = do
row <- treeModelGetRow store iter
if row == a
then return $ WhereExpected iter
else treeModelIterNext store iter >>= find'
where
find' :: Maybe TreeIter -> IO FindResult
find' Nothing = return NotFound
find' (Just iter) = do
row <- treeModelGetRow store iter
if row == a
then return $ Found iter
else treeModelIterNext store iter >>= find'
removeUntil :: Eq a => a -> TreeStore a -> TreePath -> IO ()
removeUntil a store path = do
row <- treeStoreGetValue store path
when (row /= a) $ do
found <- treeStoreRemove store path
when found $ removeUntil a store path
removeRemaining :: TreeStore a -> TreePath -> IO ()
removeRemaining store path = do
found <- treeStoreRemove store path
when found $ removeRemaining store path
getSelectionHLintRecord :: TreeView
-> TreeStore HLintRecord
-> IO (Maybe HLintRecord)
getSelectionHLintRecord treeView hlintStore = do
treeSelection <- treeViewGetSelection treeView
paths <- treeSelectionGetSelectedRows treeSelection
case paths of
p:_ -> Just <$> treeStoreGetValue hlintStore p
_ -> return Nothing
refreshHLint :: WorkspaceAction
refreshHLint = do
ws <- ask
maybeActive <- lift $ readIDE activePack
let packages = case maybeActive of
Just active -> active : filter (/= active) (wsAllPackages ws)
Nothing -> wsAllPackages ws
lift $ hlintDirectories2 packages
--gotoSource :: Bool -> IDEM Bool
gotoSource focus treeView hlintStore = do
sel <- liftIO $ getSelectionHLintRecord treeView hlintStore
case sel of
Just record ->
case record of
HLintRecord {condIdea = Just idea} ->
goToSourceDefinition (srcSpanFilename (H.ideaSpan idea))
(Just $ Location (srcSpanStartLine (H.ideaSpan idea))
(srcSpanStartColumn (H.ideaSpan idea))
(srcSpanEndLine (H.ideaSpan idea))
(srcSpanEndColumn (H.ideaSpan idea)))
?>>= (\(IDEBuffer {sourceView = sv}) -> when focus $ grabFocus sv)
_ -> return ()
Nothing -> return ()
return True
hlintDirectories2 :: [IDEPackage] -> IDEAction
hlintDirectories2 packages = do
hlint <- getHLint Nothing
let store = hlintStore hlint
liftIO $ do
treeStoreClear store
forM_ packages $ \ p -> do
nDir <- treeModelIterNChildren store Nothing
treeStoreInsert store [] nDir $ HLintRecord (Just p) (packageIdentifierToString (ipdPackageId p)) Nothing Nothing
treeStoreInsert store [nDir] 0 $ HLintRecord (Just p) (packageIdentifierToString (ipdPackageId p)) Nothing Nothing
refreshDir :: TreeStore HLintRecord -> TreeIter -> IDEPackage -> IDEM ()
refreshDir store iter package = do
mbHlintDir <- liftIO $ leksahSubDir "hlint"
let datadirOpt = case mbHlintDir of
Just d -> "--datadir":[d]
Nothing -> []
(flags, classify, hint) <- liftIO H.autoSettings
let modules = Map.keys (ipdModules package)
pathes <- getSourcePathes (ipdPackageId package) modules
resL <- liftIO $ mapM (\dir -> H.parseModuleEx flags dir Nothing) pathes
let resOk = mapMaybe (\ pr -> case pr of
Left e -> trace ("can't parse: " ++ H.parseErrorContents e ++
" location " ++ show H.parseErrorLocation) Nothing
Right r -> Just r) resL
let ideas = H.applyHints classify hint resOk
liftIO $ setHLint2Results store iter (packageIdentifierToString (ipdPackageId package)) ideas
return ()
getSourcePathes :: PackageIdentifier -> [ModuleName] -> IDEM [FilePath]
getSourcePathes packId names = do
mbWorkspaceInfo <- getWorkspaceInfo
case mbWorkspaceInfo of
Nothing -> return []
Just (sc, _) -> return (mapMaybe (sourcePathFromScope sc) names)
where
sourcePathFromScope :: GenScope -> ModuleName -> Maybe FilePath
sourcePathFromScope (GenScopeC (PackScope l _)) mn =
case packId `Map.lookup` l of
Just pack ->
case filter (\md -> modu (mdModuleId md) == mn)
(pdModules pack) of
(mod : tl) -> mdMbSourcePath mod
[] -> Nothing
Nothing -> Nothing
hlint2Record dir idea = HLintRecord {
condPackage = Nothing,
context = show idea,
condIdea = Just idea,
parDir = Just dir}
setHLint2Results :: TreeStore HLintRecord -> TreeIter -> FilePath -> [H.Idea] -> IO Int
setHLint2Results store parent dir ideas = do
parentPath <- treeModelGetPath store parent
forM_ (zip [0..] records) $ \(n, record) -> do
mbChild <- treeModelIterNthChild store (Just parent) n
findResult <- find record store mbChild
case (mbChild, findResult) of
(_, WhereExpected _) -> return ()
(Just iter, Found _) -> do
path <- treeModelGetPath store iter
removeUntil record store path
_ -> treeStoreInsert store parentPath n record
removeRemaining store (parentPath++[nRecords])
return nRecords
where
records = map (hlint2Record dir) ideas
nRecords = length records
hlintContextMenu :: IDERef
-> TreeStore HLintRecord
-> TreeView
-> Menu
-> IO ()
hlintContextMenu ideR store treeView theMenu = do
mbSel <- getSelectionHLintRecord treeView store
item0 <- menuItemNewWithLabel (__ "Replace")
item0 `on` menuItemActivate $ reflectIDE (replaceHlint store treeView mbSel) ideR
menuShellAppend theMenu item0
where
replaceableSelection Nothing = False
replaceableSelection (Just s) | isNothing (parDir s) = True
| otherwise = False
replaceHlint store treeView (Just sel) =
case condIdea sel of
Just idea | isJust (H.ideaTo idea) ->
let lined = lines (fromJust (H.ideaTo idea))
startColumn = srcSpanStartColumn (H.ideaSpan idea)
source = init $ unlines (head lined :
map (\ s -> replicate startColumn ' ' ++ s) (tail lined))
in
replaceHLintSource (srcSpanFilename (H.ideaSpan idea))
(srcSpanStartLine (H.ideaSpan idea))
startColumn
(srcSpanEndLine (H.ideaSpan idea))
(srcSpanEndColumn (H.ideaSpan idea))
source
otherwise -> return ()
replaceHlint _ _ Nothing = return ()