Skip to content

Commit

Permalink
Avoid reordering plugins
Browse files Browse the repository at this point in the history
Order of execution matters for notification plugins, so lets avoid unnecessary
reorderings
  • Loading branch information
pepeiborra committed Mar 28, 2021
1 parent c5f5d20 commit cb57563
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 22 deletions.
1 change: 1 addition & 0 deletions ghcide/ghcide.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ test-suite ghcide-tests
implicit-hie:gen-hie
build-depends:
aeson,
async,
base,
binary,
bytestring,
Expand Down
2 changes: 1 addition & 1 deletion ghcide/src/Development/IDE/LSP/LanguageServer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import Data.Aeson (Value)
import Data.Maybe
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Development.IDE.GHC.Util as Ghcide

import Development.IDE.LSP.Server
import Development.IDE.Session (runWithDb)
import Ide.Types (traceWithSpan)
Expand Down
1 change: 0 additions & 1 deletion ghcide/src/Development/IDE/LSP/Notifications.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import qualified Language.LSP.Types.Capabilities as LSP
import Development.IDE.Core.IdeConfiguration
import Development.IDE.Core.Service
import Development.IDE.Core.Shake
import Development.IDE.LSP.Server
import Development.IDE.Types.Location
import Development.IDE.Types.Logger
import Development.IDE.Types.Options
Expand Down
3 changes: 1 addition & 2 deletions ghcide/src/Development/IDE/Plugin/HLS.hs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,12 @@ import UnliftIO.Exception (catchAny)

-- | Map a set of plugins to the underlying ghcide engine.
asGhcIdePlugin :: Config -> IdePlugins IdeState -> Plugin Config
asGhcIdePlugin defaultConfig mp =
asGhcIdePlugin defaultConfig (IdePlugins ls) =
mkPlugin rulesPlugins HLS.pluginRules <>
mkPlugin executeCommandPlugins HLS.pluginCommands <>
mkPlugin (extensiblePlugins defaultConfig) HLS.pluginHandlers <>
mkPlugin (extensibleNotificationPlugins defaultConfig) HLS.pluginNotificationHandlers
where
ls = Map.toList (ipMap mp)

mkPlugin :: ([(PluginId, b)] -> Plugin Config) -> (PluginDescriptor IdeState -> b) -> Plugin Config
mkPlugin maker selector =
Expand Down
61 changes: 53 additions & 8 deletions ghcide/test/exe/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import Development.IDE.Core.PositionMapping (PositionResult (..),
positionResultToMaybe,
toCurrent)
import Development.IDE.Core.Shake (Q (..))
import Development.IDE.Main as IDE
import Development.IDE.GHC.Util
import Development.IDE.Plugin.Completions.Types (extendImportCommandId)
import Development.IDE.Plugin.TypeLenses (typeLensCommandId)
Expand Down Expand Up @@ -75,7 +76,7 @@ import qualified System.IO.Extra
import System.Info.Extra (isWindows)
import System.Process.Extra (CreateProcess (cwd),
proc,
readCreateProcessWithExitCode)
readCreateProcessWithExitCode, createPipe)
import Test.QuickCheck
-- import Test.QuickCheck.Instances ()
import Control.Lens ((^.))
Expand All @@ -92,6 +93,14 @@ import Test.Tasty.ExpectedFailure
import Test.Tasty.HUnit
import Test.Tasty.Ingredients.Rerun
import Test.Tasty.QuickCheck
import Data.IORef
import Ide.PluginUtils (pluginDescToIdePlugins)
import Control.Concurrent.Async
import Ide.Types
import Data.String (IsString(fromString))
import qualified Language.LSP.Types as LSP
import Data.IORef.Extra (atomicModifyIORef_)
import qualified Development.IDE.Plugin.HLS.GhcIde as Ghcide

waitForProgressBegin :: Session ()
waitForProgressBegin = skipManyTill anyMessage $ satisfyMaybe $ \case
Expand Down Expand Up @@ -5143,21 +5152,26 @@ runInDir' dir startExeIn startSessionIn extraOptions s = do
-- HIE calls getXgdDirectory which assumes that HOME is set.
-- Only sets HOME if it wasn't already set.
setEnv "HOME" "/homeless-shelter" False
let lspTestCaps = fullCaps { _window = Just $ WindowClientCapabilities $ Just True }
conf <- getConfigFromEnv
runSessionWithConfig conf cmd lspTestCaps projDir s

getConfigFromEnv :: IO SessionConfig
getConfigFromEnv = do
logColor <- fromMaybe True <$> checkEnv "LSP_TEST_LOG_COLOR"
timeoutOverride <- fmap read <$> getEnv "LSP_TIMEOUT"
let conf = defaultConfig{messageTimeout = fromMaybe (messageTimeout defaultConfig) timeoutOverride}
-- uncomment this or set LSP_TEST_LOG_STDERR=1 to see all logging
-- { logStdErr = True }
-- uncomment this or set LSP_TEST_LOG_MESSAGES=1 to see all messages
-- { logMessages = True }
runSessionWithConfig conf{logColor} cmd lspTestCaps projDir s
return defaultConfig
{ messageTimeout = fromMaybe (messageTimeout defaultConfig) timeoutOverride
, logColor
}
where
checkEnv :: String -> IO (Maybe Bool)
checkEnv s = fmap convertVal <$> getEnv s
convertVal "0" = False
convertVal _ = True

lspTestCaps :: ClientCapabilities
lspTestCaps = fullCaps { _window = Just $ WindowClientCapabilities $ Just True }

openTestDataDoc :: FilePath -> Session TextDocumentIdentifier
openTestDataDoc path = do
source <- liftIO $ readFileUtf8 $ "test/data" </> path
Expand Down Expand Up @@ -5225,8 +5239,39 @@ unitTests = do
let expected = "1:2-3:4"
assertBool (unwords ["expected to find range", expected, "in diagnostic", shown]) $
expected `isInfixOf` shown
, testCase "notification handlers run sequentially" $ do
orderRef <- newIORef []
let plugins = pluginDescToIdePlugins $
[ (defaultPluginDescriptor $ fromString $ show i)
{ pluginNotificationHandlers = mconcat
[ mkPluginNotificationHandler LSP.STextDocumentDidOpen $ \_ _ _ ->
liftIO $ atomicModifyIORef_ orderRef (i:)
]
}
| i <- [(1::Int)..20]
] ++ Ghcide.descriptors

testIde def{argsHlsPlugins = plugins} $ do
_ <- createDoc "haskell" "A.hs" "module A where"
waitForProgressDone
actualOrder <- liftIO $ readIORef orderRef

liftIO $ actualOrder @?= reverse [(1::Int)..20]
]

testIde :: Arguments -> Session () -> IO ()
testIde arguments session = do
config <- getConfigFromEnv
(hInRead, hInWrite) <- createPipe
(hOutRead, hOutWrite) <- createPipe
let server = IDE.defaultMain arguments
{ argsHandleIn = pure hInRead
, argsHandleOut = pure hOutWrite
}

withAsync server $ \_ ->
runSessionWithHandles hInWrite hOutRead config lspTestCaps "." session

positionMappingTests :: TestTree
positionMappingTests =
testGroup "position mapping"
Expand Down
5 changes: 2 additions & 3 deletions hls-plugin-api/src/Ide/Plugin/ConfigUtils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import Data.Default (def)
import qualified Data.Dependent.Map as DMap
import qualified Data.Dependent.Sum as DSum
import qualified Data.HashMap.Lazy as HMap
import qualified Data.Map as Map
import Ide.Plugin.Config
import Ide.Plugin.Properties (toDefaultJSON, toVSCodeExtensionSchema)
import Ide.Types
Expand All @@ -36,7 +35,7 @@ pluginsToDefaultConfig IdePlugins {..} =
defaultConfig@Config {} = def
unsafeValueToObject (A.Object o) = o
unsafeValueToObject _ = error "impossible"
elems = A.object $ mconcat $ singlePlugin <$> Map.elems ipMap
elems = A.object $ mconcat $ singlePlugin <$> map snd ipMap
-- Splice genericDefaultConfig and dedicatedDefaultConfig
-- Example:
--
Expand Down Expand Up @@ -100,7 +99,7 @@ pluginsToDefaultConfig IdePlugins {..} =
-- | Generates json schema used in haskell vscode extension
-- Similar to 'pluginsToDefaultConfig' but simpler, since schema has a flatten structure
pluginsToVSCodeExtensionSchema :: IdePlugins a -> A.Value
pluginsToVSCodeExtensionSchema IdePlugins {..} = A.object $ mconcat $ singlePlugin <$> Map.elems ipMap
pluginsToVSCodeExtensionSchema IdePlugins {..} = A.object $ mconcat $ singlePlugin <$> map snd ipMap
where
singlePlugin PluginDescriptor {..} = genericSchema <> dedicatedSchema
where
Expand Down
6 changes: 2 additions & 4 deletions hls-plugin-api/src/Ide/PluginUtils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import Language.LSP.Types
import qualified Language.LSP.Types as J
import Language.LSP.Types.Capabilities

import qualified Data.Map.Strict as Map
import Ide.Plugin.Config
import Ide.Plugin.Properties
import Language.LSP.Server
Expand Down Expand Up @@ -144,7 +143,7 @@ clientSupportsDocumentChanges caps =
-- ---------------------------------------------------------------------

pluginDescToIdePlugins :: [PluginDescriptor ideState] -> IdePlugins ideState
pluginDescToIdePlugins plugins = IdePlugins $ Map.fromList $ map (\p -> (pluginId p, p)) plugins
pluginDescToIdePlugins plugins = IdePlugins $ map (\p -> (pluginId p, p)) plugins


-- ---------------------------------------------------------------------
Expand Down Expand Up @@ -214,12 +213,11 @@ positionInRange (Position pl po) (Range (Position sl so) (Position el eo)) =
-- ---------------------------------------------------------------------

allLspCmdIds' :: T.Text -> IdePlugins ideState -> [T.Text]
allLspCmdIds' pid mp = mkPlugin (allLspCmdIds pid) (Just . pluginCommands)
allLspCmdIds' pid (IdePlugins ls) = mkPlugin (allLspCmdIds pid) (Just . pluginCommands)
where
justs (p, Just x) = [(p, x)]
justs (_, Nothing) = []

ls = Map.toList (ipMap mp)

mkPlugin maker selector
= maker $ concatMap (\(pid, p) -> justs (pid, selector p)) ls
Expand Down
2 changes: 1 addition & 1 deletion hls-plugin-api/src/Ide/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import Text.Regex.TDFA.Text ()
-- ---------------------------------------------------------------------

newtype IdePlugins ideState = IdePlugins
{ ipMap :: Map.Map PluginId (PluginDescriptor ideState)}
{ ipMap :: [(PluginId, PluginDescriptor ideState)]}

-- ---------------------------------------------------------------------

Expand Down
3 changes: 1 addition & 2 deletions src/Ide/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import Control.Monad.Extra
import qualified Data.Aeson.Encode.Pretty as A
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.Default
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import Development.IDE.Core.Rules
import qualified Development.IDE.Main as Main
Expand Down Expand Up @@ -97,7 +96,7 @@ runLspMode lspArgs@LspArguments{..} idePlugins = do
when argLSP $ do
hPutStrLn stderr "Starting (haskell-language-server)LSP server..."
hPutStrLn stderr $ " with arguments: " <> show lspArgs
hPutStrLn stderr $ " with plugins: " <> show (Map.keys $ ipMap idePlugins)
hPutStrLn stderr $ " with plugins: " <> show (map fst $ ipMap idePlugins)
hPutStrLn stderr $ " in directory: " <> dir
hPutStrLn stderr "If you are seeing this in a terminal, you probably should have run ghcide WITHOUT the --lsp option!"

Expand Down

0 comments on commit cb57563

Please sign in to comment.