Skip to content

Commit

Permalink
add core plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
soulomoon committed Mar 23, 2024
1 parent b2b41df commit 94d373a
Show file tree
Hide file tree
Showing 61 changed files with 839 additions and 8 deletions.
6 changes: 1 addition & 5 deletions ghcide/src/Development/IDE/LSP/HoverDefinition.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module Development.IDE.LSP.HoverDefinition
, gotoTypeDefinition
, documentHighlight
, references
, wsSymbols
-- , wsSymbols
) where

import Control.Monad.Except (ExceptT)
Expand Down Expand Up @@ -47,10 +47,6 @@ references ide _ (ReferenceParams (TextDocumentIdentifier uri) pos _ _ _) = do
" in file: " <> T.pack (show nfp)
InL <$> (liftIO $ runAction "references" ide $ refsAtPoint nfp pos)

wsSymbols :: PluginMethodHandler IdeState Method_WorkspaceSymbol
wsSymbols ide _ (WorkspaceSymbolParams _ _ query) = liftIO $ do
logDebug (ideLogger ide) $ "Workspace symbols request: " <> query
runIdeAction "WorkspaceSymbols" (shakeExtras ide) $ InL . fromMaybe [] <$> workspaceSymbols query

foundHover :: (Maybe Range, [T.Text]) -> Hover |? Null
foundHover (mbRange, contents) =
Expand Down
3 changes: 1 addition & 2 deletions ghcide/src/Development/IDE/Plugin/HLS/GhcIde.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ descriptor plId = (defaultPluginDescriptor plId desc)
gotoTypeDefinition ide TextDocumentPositionParams{..})
<> mkPluginHandler SMethod_TextDocumentDocumentHighlight (\ide _ DocumentHighlightParams{..} ->
documentHighlight ide TextDocumentPositionParams{..})
<> mkPluginHandler SMethod_TextDocumentReferences references
<> mkPluginHandler SMethod_WorkspaceSymbol wsSymbols,
<> mkPluginHandler SMethod_TextDocumentReferences references,

pluginConfigDescriptor = defaultConfigDescriptor
}
Expand Down
2 changes: 1 addition & 1 deletion ghcide/test/exe/InitializeResponseTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ tests = withResource acquire release tests where
, chk " find references" _referencesProvider (Just $ InR (ReferenceOptions (Just False)))
, chk " doc highlight" _documentHighlightProvider (Just $ InR (DocumentHighlightOptions (Just False)))
, chk " doc symbol" _documentSymbolProvider (Just $ InR (DocumentSymbolOptions (Just False) Nothing))
, chk " workspace symbol" _workspaceSymbolProvider (Just $ InR (WorkspaceSymbolOptions (Just False) (Just False)))
-- , chk " workspace symbol" _workspaceSymbolProvider (Just $ InR (WorkspaceSymbolOptions (Just False) (Just False)))
, chk "NO code action" _codeActionProvider Nothing
, chk " code lens" _codeLensProvider (Just $ CodeLensOptions (Just False) (Just True))
, chk "NO doc formatting" _documentFormattingProvider Nothing
Expand Down
79 changes: 79 additions & 0 deletions haskell-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -1602,6 +1602,84 @@ test-suite hls-refactor-plugin-tests
, tasty-expected-failure
, tasty

-----------------------------
-- core plugin
-----------------------------

-- flag semanticTokens
-- description: Enable semantic tokens plugin
-- default: True
-- manual: True

common core
build-depends: haskell-language-server:hls-core-plugin

library hls-core-plugin
import: defaults, pedantic, warnings
buildable: True
exposed-modules:
Ide.Plugin.Core

hs-source-dirs: plugins/hls-core-plugin/src
build-depends:
, base >=4.12 && <5
, containers
, extra
, text-rope
, mtl >= 2.2
, ghcide == 2.7.0.0
, hls-plugin-api == 2.7.0.0
, lens
, lsp >=2.4
, text
, transformers
, bytestring
, syb
, array
, deepseq
, dlist
, hls-graph == 2.7.0.0
, template-haskell
, data-default
, stm
, stm-containers

default-extensions: DataKinds

test-suite hls-core-plugin-tests
import: defaults, pedantic, test-defaults, warnings
type: exitcode-stdio-1.0
hs-source-dirs: plugins/hls-core-plugin/test
, plugins/hls-core-plugin/test/exe
main-is: CoreTest.hs
other-modules:
InitializeResponseTests

build-depends:
, aeson
, base
, containers
, filepath
, haskell-language-server:hls-core-plugin
, hls-test-utils == 2.7.0.0
, ghcide:ghcide-test-utils
, hls-plugin-api
, lens
, lsp
, text-rope
, lsp-test
, text
, tasty
, tasty-hunit
, data-default
, ghcide == 2.7.0.0
, hls-plugin-api == 2.7.0.0
, data-default
, row-types
, extra
, hls-test-utils


-----------------------------
-- semantic tokens plugin
-----------------------------
Expand Down Expand Up @@ -1777,6 +1855,7 @@ library
, overloadedRecordDot
, semanticTokens
, notes
, core

exposed-modules:
Ide.Arguments
Expand Down
66 changes: 66 additions & 0 deletions plugins/hls-core-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Semantic tokens (LSP) plugin for Haskell language server

## Purpose

The purpose of this plugin is to provide semantic tokens for the Haskell language server,
according to the [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens)
It can be used to provide semantic highlighting for Haskell code in editors by given semantic type and modifiers for some tokens.
A lot of editors support semantic highlighting through LSP, for example vscode, vim, emacs, etc.

## Features

### Semantic types and modifiers

The handles request for semantic tokens for the whole file.
It supports semantic types and but not yet modifiers from the LSP specification.

Default semantic types defined in lsp diverge greatly from the ones used in ghc.
But default semantic types allows user with less configuration to get semantic highlighting.
That is why we use default semantic types for now. By mapping ghc semantic types to lsp semantic types.
The mapping is defined in `Mapping.hs` file.

### delta semantic tokens, range semantic tokens and refresh

It is not yet support capabilities for delta semantic tokens, which might be
crucial for performance.
It should be implemented in the future.

## checkList

* Supported PluginMethodHandler
* [x] [textDocument/semanticTokens/full](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokens_fullRequest).
* [ ] [textDocument/semanticTokens/full/delta](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokens_deltaRequest)
* [ ] [workspace/semanticTokens/refresh](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#semanticTokens_refreshRequest)

* Supported semantic tokens type:
* [x] class and class method
* [x] type family name (data family)
* [x] data constructor name (not distinguishing record and normal data, and GADT)
* [x] type constructor name (GADT)
* [x] record field name
* [x] type synonym
* [x] pattern synonym
* [x] ~~pattern bindings~~ In favor of differing functions and none-functions from its type
* [x] ~~value bindings~~ In favor of differing functions and none-functions from its type
* [x] functions
* [x] none-function variables
* [x] imported name

* Supported modifiers(planning):
* [future] declaration (as in class declearations, type definition and type family)
* [future] definition (as in class instance declaration, left hand side value binding, and type family instance)
* [future] modification (as in rec field update)

## Implementation details

* [x] Compute visible names from renamedsource
* [x] Compute `NameSemanticMap` for imported and top level name tokens using `HscEnv`(with deps) and type checked result
* [x] Compute current module `NameSemanticMap` using `RefMap a` from the result of `GetHieAst`
* [x] Compute all visible `(Name, Span)` in current module, in turn compute their semantic token using the combination map of the above two `NameSemanticMap`
* [x] use default legends, Candidates map of token type with default token type: [Maps to default token types](https://github.com/soulomoon/haskell-language-server/blob/master/plugins/hls-semantic-tokens-plugin/src/Ide/Plugin/SemanticTokens/Mappings.hs)
* [x] add args support to turn the plugin on and off
* [x] enhence test <https://github.com/haskell/haskell-language-server/pull/3892#discussion_r1427844520>
* [x] enhence error reporting. <https://github.com/haskell/haskell-language-server/pull/3892#discussion_r1427955335>
* [x] computation of semantic tokens is pushed into rule `getSemanticTokensRule`
* [future] make use of modifiers
* [future] hadling customize legends using server capabilities (how?)
40 changes: 40 additions & 0 deletions plugins/hls-core-plugin/src/Ide/Plugin/Core.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{-# LANGUAGE OverloadedStrings #-}


module Ide.Plugin.Core(descriptor, CoreLog) where

import Control.Monad.IO.Class (liftIO)
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Development.IDE
import Development.IDE.Core.Actions (workspaceSymbols)
import qualified Development.IDE.Core.Shake as Shake
import Ide.Types
import Language.LSP.Protocol.Message
import Language.LSP.Protocol.Types (WorkspaceSymbolParams (..),
type (|?) (InL))

data CoreLog
= LogShake Shake.Log
| CoreLogMsg Text

instance Pretty CoreLog where
pretty theLog = case theLog of
LogShake shakeLog -> pretty shakeLog
CoreLogMsg msg -> "Core Message: " <> pretty msg



descriptor :: Recorder (WithPriority CoreLog) -> PluginId -> PluginDescriptor IdeState
descriptor recorder plId =
(defaultPluginDescriptor plId "Provides core IDE features for Haskell")
{
Ide.Types.pluginHandlers = mkPluginHandler SMethod_WorkspaceSymbol (wsSymbols recorder)
}



wsSymbols :: Recorder (WithPriority CoreLog) -> PluginMethodHandler IdeState Method_WorkspaceSymbol
wsSymbols logger ide _ (WorkspaceSymbolParams _ _ query) = liftIO $ do
logWith logger Debug $ CoreLogMsg $ "Workspace symbols request: " <> query
runIdeAction "WorkspaceSymbols" (shakeExtras ide) $ InL . fromMaybe [] <$> workspaceSymbols query
14 changes: 14 additions & 0 deletions plugins/hls-core-plugin/test/CoreTest.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}

import qualified InitializeResponseTests
import Test.Hls (defaultTestRunner, testGroup)


main :: IO ()
main =
defaultTestRunner $
testGroup
"core"
[ InitializeResponseTests.tests ]
144 changes: 144 additions & 0 deletions plugins/hls-core-plugin/test/exe/InitializeResponseTests.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}

module InitializeResponseTests (tests) where

import Control.Monad
import Data.List.Extra
import Data.Row
import qualified Data.Text as T
import Development.IDE.Plugin.TypeLenses (typeLensCommandId)
import qualified Language.LSP.Protocol.Lens as L
import Language.LSP.Protocol.Message
-- import qualified Language.LSP.Protocol.Types hiding
-- (SemanticTokenAbsolute (..),
-- SemanticTokenRelative (..),
-- SemanticTokensEdit (..),
-- mkRange)
import Language.LSP.Test

import Control.Lens ((^.))
import Data.Default (def)
import Data.Text (Text)
import qualified Data.Text as Text
import Development.IDE.Plugin.Test (blockCommandId)
import qualified Ide.Plugin.Core as Core
import Language.LSP.Protocol.Types (CodeLensOptions (..),
CompletionOptions (..),
DefinitionOptions (DefinitionOptions),
DocumentHighlightOptions (..),
DocumentSymbolOptions (..),
ExecuteCommandOptions (..),
HoverOptions (..),
InitializeResult (..),
ReferenceOptions (..),
SaveOptions (..),
ServerCapabilities (..),
TextDocumentSyncKind (..),
TextDocumentSyncOptions (..),
TypeDefinitionOptions (..),
WorkspaceFoldersServerCapabilities (..),
WorkspaceSymbolOptions (..),
type (|?) (..))
import System.FilePath ((</>))
import Test.Hls (PluginTestDescriptor,
mkPluginTestDescriptor,
runSessionWithServerInTmpDir)
import qualified Test.Hls.FileSystem as FS
import Test.Hls.FileSystem (file, text)
import Test.Tasty
import Test.Tasty.HUnit

corePlugin :: PluginTestDescriptor Core.CoreLog
corePlugin = mkPluginTestDescriptor Core.descriptor "core"

tests :: TestTree
tests = withResource acquire release tests where

-- these tests document and monitor the evolution of the
-- capabilities announced by the server in the initialize
-- response. Currently the server advertises almost no capabilities
-- at all, in some cases failing to announce capabilities that it
-- actually does provide! Hopefully this will change ...
tests :: IO (TResponseMessage Method_Initialize) -> TestTree
tests getInitializeResponse =
testGroup "initialize response capabilities"
[
chk " text doc sync" _textDocumentSync tds
, chk " hover" _hoverProvider (Just $ InR (HoverOptions (Just False)))
, chk " completion" _completionProvider (Just $ CompletionOptions (Just False) (Just ["."]) Nothing (Just True) Nothing)
, chk "NO signature help" _signatureHelpProvider Nothing
, chk " goto definition" _definitionProvider (Just $ InR (DefinitionOptions (Just False)))
, chk " goto type definition" _typeDefinitionProvider (Just $ InR (InL (TypeDefinitionOptions (Just False))))
-- BUG in lsp-test, this test fails, just change the accepted response
-- for now
, chk "NO goto implementation" _implementationProvider Nothing
, chk " find references" _referencesProvider (Just $ InR (ReferenceOptions (Just False)))
, chk " doc highlight" _documentHighlightProvider (Just $ InR (DocumentHighlightOptions (Just False)))
, chk " doc symbol" _documentSymbolProvider (Just $ InR (DocumentSymbolOptions (Just False) Nothing))
, chk " workspace symbol" _workspaceSymbolProvider (Just $ InR (WorkspaceSymbolOptions (Just False) (Just False)))
, chk "NO code action" _codeActionProvider Nothing
, chk " code lens" _codeLensProvider (Just $ CodeLensOptions (Just False) (Just True))
, chk "NO doc formatting" _documentFormattingProvider Nothing
, chk "NO doc range formatting"
_documentRangeFormattingProvider Nothing
, chk "NO doc formatting on typing"
_documentOnTypeFormattingProvider Nothing
, chk "NO renaming" _renameProvider Nothing
, chk "NO doc link" _documentLinkProvider Nothing
, chk "NO color" (^. L.colorProvider) Nothing
, chk "NO folding range" _foldingRangeProvider Nothing
, che " execute command" _executeCommandProvider [typeLensCommandId, blockCommandId]
, chk " workspace" (^. L.workspace) (Just $ #workspaceFolders .== Just WorkspaceFoldersServerCapabilities{_supported = Just True, _changeNotifications = Just ( InR True )}
.+ #fileOperations .== Nothing)
, chk "NO experimental" (^. L.experimental) Nothing
] where

tds = Just (InL (TextDocumentSyncOptions
{ _openClose = Just True
, _change = Just TextDocumentSyncKind_Incremental
, _willSave = Nothing
, _willSaveWaitUntil = Nothing
, _save = Just (InR $ SaveOptions {_includeText = Nothing})}))

chk :: (Eq a, Show a) => TestName -> (ServerCapabilities -> a) -> a -> TestTree
chk title getActual expected =
testCase title $ getInitializeResponse >>= \ir -> expected @=? (getActual . innerCaps) ir

che :: TestName -> (ServerCapabilities -> Maybe ExecuteCommandOptions) -> [T.Text] -> TestTree
che title getActual expected = testCase title $ do
ir <- getInitializeResponse
ExecuteCommandOptions {_commands = commands} <- case getActual $ innerCaps ir of
Just eco -> pure eco
Nothing -> assertFailure "Was expecting Just ExecuteCommandOptions, got Nothing"
let commandNames = (!! 2) . T.splitOn ":" <$> commands
zipWithM_ (\e o -> T.isSuffixOf e o @? show (e,o)) (sort expected) (sort commandNames)

innerCaps :: TResponseMessage Method_Initialize -> ServerCapabilities
innerCaps (TResponseMessage _ _ (Right (InitializeResult c _))) = c
innerCaps (TResponseMessage _ _ (Left _)) = error "Initialization error"

acquire :: IO (TResponseMessage Method_Initialize)
acquire = do
let content = Text.unlines ["module Hello where", "go _ = 1"]
let fs = mkFs $ directFile "Hello.hs" content
runSessionWithServerInTmpDir def corePlugin fs initializeResponse


release :: TResponseMessage Method_Initialize -> IO ()
release = mempty

directFile :: FilePath -> Text -> [FS.FileTree]
directFile fp content =
[ FS.directCradle [Text.pack fp]
, file fp (text content)
]

mkFs :: [FS.FileTree] -> FS.VirtualFileTree
mkFs = FS.mkVirtualFileTree testDataDir

testDataDir :: FilePath
testDataDir = "plugins" </> "core-plugin" </> "test" </> "testdata"

Loading

0 comments on commit 94d373a

Please sign in to comment.