Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Many of these are standard LSP features, but a lot of special features are provi
| [Selection range](#selection-range) | `textDocument/selectionRange` |
| [Rename](#rename) | `textDocument/rename` |
| [Semantic tokens](#semantic-tokens) | `textDocument/semanticTokens/full` |
| [Document links](#document-links) | `textDocument/documentLink` |

The individual sections below also identify which [HLS plugin](./what-is-hls.md#hls-plugins) is responsible for providing the given functionality, which is useful if you want to raise an issue report or contribute!
Additionally, not all plugins are supported on all versions of GHC, see the [plugin support page](./support/plugin-support.md) for details.
Expand Down Expand Up @@ -434,6 +435,13 @@ Provided by: `hls-semantic-tokens-plugin`

Provides semantic tokens for each token in the source code to support semantic highlighting.

## Document links

Provided by: `hls-document-link-plugin`

Extracts clickable links (e.g., URLs in Haddock comments) and makes them available as LSP document links.
This allows editors to open the linked resource directly from the source code.

## Rewrite to overloaded record syntax

Provided by: `hls-overloaded-record-dot-plugin`
Expand All @@ -454,7 +462,6 @@ Contributions welcome!
| Jump to declaration | Unclear if useful | `textDocument/declaration` |
| Jump to implementation | Unclear if useful | `textDocument/implementation` |
| Linked editing | Unimplemented | `textDocument/linkedEditingRange` |
| Document links | Unimplemented | `textDocument/documentLink` |
| Document color | Unclear if useful | `textDocument/documentColor` |
| Color presentation | Unclear if useful | `textDocument/colorPresentation` |
| Monikers | Unclear if useful | `textDocument/moniker` |
53 changes: 53 additions & 0 deletions haskell-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,58 @@ test-suite hls-code-range-plugin-tests
, transformers
, vector

-----------------------------
-- document link
-----------------------------

flag documentLink
description: Enable documentLink plugin
default: True
manual: True

common documentLink
if flag(documentLink)
build-depends: haskell-language-server:hls-document-link-plugin
cpp-options: -Dhls_documentLink

library hls-document-link-plugin
import: defaults, pedantic, warnings
if !flag(documentLink)
buildable: False
exposed-modules:
Ide.Plugin.DocumentLink
hs-source-dirs: plugins/hls-document-link-plugin/src
build-depends:
, containers
, deepseq
, extra
, ghc
, ghcide == 2.13.0.0
, hashable
, hls-plugin-api == 2.13.0.0
, lens
, lsp
, mtl
, semigroupoids
, transformers
, vector

test-suite hls-document-link-plugin-tests
import: defaults, pedantic, test-defaults, warnings
if !flag(documentLink)
buildable: False
type: exitcode-stdio-1.0
hs-source-dirs: plugins/hls-document-link-plugin/test
main-is: Main.hs
build-depends:
, ghcide
, filepath
, haskell-language-server:hls-document-link-plugin
, hls-test-utils == 2.13.0.0
, text
, lens
, lsp-types

-----------------------------
-- change type signature plugin
-----------------------------
Expand Down Expand Up @@ -1846,6 +1898,7 @@ library
, hlint
, stan
, signatureHelp
, documentLink
, pragmas
, splice
, alternateNumberFormat
Expand Down
1 change: 1 addition & 0 deletions hls-plugin-api/src/Ide/Plugin/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ parsePluginConfig def = A.withObject "PluginConfig" $ \o -> PluginConfig
<*> o .:? "selectionRangeOn" .!= plcSelectionRangeOn def
<*> o .:? "foldingRangeOn" .!= plcFoldingRangeOn def
<*> o .:? "semanticTokensOn" .!= plcSemanticTokensOn def
<*> o .:? "documentLinkOn" .!= plcDocumentLinkOn def
<*> o .:? "config" .!= plcConfig def

-- ---------------------------------------------------------------------
1 change: 1 addition & 0 deletions hls-plugin-api/src/Ide/Plugin/ConfigUtils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ pluginsToDefaultConfig IdePlugins {..} =
SMethod_TextDocumentPrepareCallHierarchy -> ["callHierarchyOn" A..= plcCallHierarchyOn]
SMethod_TextDocumentSemanticTokensFull -> ["semanticTokensOn" A..= plcSemanticTokensOn]
SMethod_TextDocumentSemanticTokensFullDelta -> ["semanticTokensOn" A..= plcSemanticTokensOn]
SMethod_TextDocumentDocumentLink -> ["documentLinkOn" A..= plcDocumentLinkOn]
_ -> []

-- | Generates json schema used in haskell vscode extension
Expand Down
10 changes: 9 additions & 1 deletion hls-plugin-api/src/Ide/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ data PluginConfig =
, plcSelectionRangeOn :: !Bool
, plcFoldingRangeOn :: !Bool
, plcSemanticTokensOn :: !Bool
, plcDocumentLinkOn :: !Bool
, plcConfig :: !Object
} deriving (Show,Eq)

Expand All @@ -288,11 +289,12 @@ instance Default PluginConfig where
, plcSelectionRangeOn = True
, plcFoldingRangeOn = True
, plcSemanticTokensOn = True
, plcDocumentLinkOn = True
, plcConfig = mempty
}

instance ToJSON PluginConfig where
toJSON (PluginConfig g ch ca ih cl d h s sh c rn sr fr st cfg) = r
toJSON (PluginConfig g ch ca ih cl d h s sh c rn sr fr st dl cfg) = r
where
r = object [ "globalOn" .= g
, "callHierarchyOn" .= ch
Expand All @@ -308,6 +310,7 @@ instance ToJSON PluginConfig where
, "selectionRangeOn" .= sr
, "foldingRangeOn" .= fr
, "semanticTokensOn" .= st
, "documentLinkOn" .= dl
, "config" .= cfg
]

Expand Down Expand Up @@ -613,6 +616,8 @@ instance PluginMethod Request Method_WorkspaceExecuteCommand where
instance PluginMethod Request (Method_CustomMethod m) where
handlesRequest _ _ _ _ _ = HandlesRequest

instance PluginMethod Request Method_TextDocumentDocumentLink where

-- Plugin Notifications

instance PluginMethod Notification Method_TextDocumentDidOpen where
Expand Down Expand Up @@ -844,6 +849,9 @@ instance PluginRequestMethod Method_TextDocumentSemanticTokensFullDelta where
instance PluginRequestMethod Method_TextDocumentInlayHint where
combineResponses _ _ _ _ x = sconcat x

instance PluginRequestMethod Method_TextDocumentDocumentLink where


takeLefts :: [a |? b] -> [a]
takeLefts = mapMaybe (\x -> [res | (InL res) <- Just x])

Expand Down
126 changes: 126 additions & 0 deletions plugins/hls-document-link-plugin/src/Ide/Plugin/DocumentLink.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TypeFamilies #-}

module Ide.Plugin.DocumentLink (descriptor, Log(..)) where

import Control.DeepSeq (NFData)
import Control.Monad.Trans.Maybe (MaybeT (runMaybeT),
hoistMaybe)
import Data.Hashable (Hashable)
import qualified Data.Map.Strict as Map
import Data.Maybe (maybeToList)
import Development.IDE (DocAndTyThingMap (DKMap, getDocMap),
GetDocMap (GetDocMap),
GetHieAst (GetHieAst),
HieAstResult (HAR, hieAst),
IdeState (shakeExtras),
Pretty (pretty), Range,
Recorder, RuleResult, Rules,
Uri (Uri), WithPriority,
cmapWithPrio,
defineNoDiagnostics,
fromNormalizedFilePath,
realSrcSpanToRange)
import Development.IDE.Core.PluginUtils (runIdeActionE, useMT,
useWithStaleFastE)
import qualified Development.IDE.Core.Shake as Shake
import Development.IDE.GHC.Compat (lookupNameEnv)
import Development.IDE.GHC.Compat.Util (mkFastString)
import Development.IDE.Spans.Common (DocMap,
SpanDoc (SpanDocString, SpanDocText),
spanDocUriDoc)
import GHC.Generics (Generic)
import GHC.Iface.Ext.Types (HieAST (nodeChildren, nodeSpan, sourcedNodeInfo),
HieASTs (getAsts),
Identifier,
NodeInfo (nodeIdentifiers),
NodeOrigin (SourceInfo),
SourcedNodeInfo (getSourcedNodeInfo),
Span, pattern HiePath)
import Ide.Plugin.Error (getNormalizedFilePathE)
import Ide.Types (PluginDescriptor (pluginHandlers),
PluginId,
PluginMethodHandler,
defaultPluginDescriptor,
mkPluginHandler, pluginRules)
import Language.LSP.Protocol.Message (Method (Method_TextDocumentDocumentLink),
SMethod (SMethod_TextDocumentDocumentLink))
import Language.LSP.Protocol.Types (DocumentLink (..),
DocumentLinkParams (DocumentLinkParams),
TextDocumentIdentifier (TextDocumentIdentifier),
type (|?) (InL))

newtype Log = LogShake Shake.Log

instance Pretty Log where
pretty = \case {
LogShake shakeLog -> pretty shakeLog
}
Comment on lines +61 to +63
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pretty = \case {
LogShake shakeLog -> pretty shakeLog
}
pretty = \case
LogShake shakeLog -> pretty shakeLog


descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
descriptor recorder pluginId =
(defaultPluginDescriptor pluginId "Provide document link of symbols")
{ Ide.Types.pluginHandlers = mkPluginHandler SMethod_TextDocumentDocumentLink documentLinkProvider,
Ide.Types.pluginRules = getDocumentLinkRule recorder
}

documentLinkProvider :: PluginMethodHandler IdeState Method_TextDocumentDocumentLink
documentLinkProvider ideState _pluginId (DocumentLinkParams _ _ (TextDocumentIdentifier uri)) = do
nfp <- getNormalizedFilePathE uri
((DocumentLinks uris), _pm) <- runIdeActionE "DocumentLink" (shakeExtras ideState) $ useWithStaleFastE GetDocumentLinks nfp
pure $ InL (map mkDocumentLink uris)

mkDocumentLink :: (Range, Uri) -> DocumentLink
mkDocumentLink (range, target) =
DocumentLink
{ _range = range,
_target = Just target,
_tooltip = Nothing,
_data_ = Nothing
}

data GetDocumentLinks = GetDocumentLinks
deriving (Eq, Show, Generic)

instance Hashable GetDocumentLinks

instance NFData GetDocumentLinks

newtype DocumentLinks = DocumentLinks [(Range, Uri)]
deriving (Show, NFData)

type instance RuleResult GetDocumentLinks = DocumentLinks

getDocumentLinkRule :: Recorder (WithPriority Log) -> Rules ()
getDocumentLinkRule recorder =
defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \GetDocumentLinks nfp -> runMaybeT $ do
HAR {hieAst} <- useMT GetHieAst nfp
DKMap {getDocMap} <- useMT GetDocMap nfp
ast <- hoistMaybe $ getAsts hieAst Map.!? (HiePath . mkFastString . fromNormalizedFilePath) nfp
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ast <- hoistMaybe $ getAsts hieAst Map.!? (HiePath . mkFastString . fromNormalizedFilePath) nfp
ast <- hoistMaybe $ getAsts hieAst Map.!? HiePath (mkFastString $ fromNormalizedFilePath nfp)

Just weird bracketing for my taste, feel free to ignore.

let lookup = lookupDoc getDocMap
pure $ DocumentLinks (foldAst lookup ast)

foldAst :: forall a t. Monoid a => ((Identifier, Span) -> a) -> HieAST t -> a
foldAst lookup ast = case (nodeChildren ast) of
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
foldAst lookup ast = case (nodeChildren ast) of
foldAst lookup ast = case nodeChildren ast of

Comment on lines +108 to +109
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some docs would be nice, what is this folding over?

[] -> visitLeaf ast
asts -> foldMap (foldAst lookup) asts
where
visitLeaf leaf =
let span = nodeSpan leaf
mNodeInfo = Map.lookup SourceInfo $ getSourcedNodeInfo (sourcedNodeInfo leaf)
in flip foldMap mNodeInfo $ \nodeInfo ->
foldMap (\ident -> lookup (ident, span)) (Map.keys $ nodeIdentifiers nodeInfo)

lookupDoc :: DocMap -> (Identifier, Span) -> [(Range, Uri)]
lookupDoc dm (identifier, span) = do
Right name <- [identifier]
doc <- maybeToList $ lookupNameEnv dm name
uris <- maybeToList $ spanDocUriDoc $ case doc of
SpanDocString _ uris -> uris
SpanDocText _ uris -> uris
pure (realSrcSpanToRange span, Uri uris)
Loading
Loading