lsp-types-1.3.0.0: src/Language/LSP/Types/SemanticTokens.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Language.LSP.Types.SemanticTokens where
import qualified Data.Aeson as A
import Data.Aeson.TH
import Data.Text (Text)
import Control.Monad.Except
import Language.LSP.Types.Common
import Language.LSP.Types.Location
import Language.LSP.Types.Progress
import Language.LSP.Types.StaticRegistrationOptions
import Language.LSP.Types.TextDocument
import Language.LSP.Types.Utils
import qualified Data.Algorithm.Diff as Diff
import qualified Data.Bits as Bits
import qualified Data.DList as DList
import Data.Default
import Data.Foldable hiding (length)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe,
maybeToList)
import Data.String
data SemanticTokenTypes =
SttType
| SttClass
| SttEnum
| SttInterface
| SttStruct
| SttTypeParameter
| SttParameter
| SttVariable
| SttProperty
| SttEnumMember
| SttEvent
| SttFunction
| SttMethod
| SttMacro
| SttKeyword
| SttModifier
| SttComment
| SttString
| SttNumber
| SttRegexp
| SttOperator
| SttUnknown Text
deriving (Show, Read, Eq, Ord)
instance A.ToJSON SemanticTokenTypes where
toJSON SttType = A.String "type"
toJSON SttClass = A.String "class"
toJSON SttEnum = A.String "enum"
toJSON SttInterface = A.String "interface"
toJSON SttStruct = A.String "struct"
toJSON SttTypeParameter = A.String "typeParameter"
toJSON SttParameter = A.String "parameter"
toJSON SttVariable = A.String "variable"
toJSON SttProperty = A.String "property"
toJSON SttEnumMember = A.String "enumMember"
toJSON SttEvent = A.String "event"
toJSON SttFunction = A.String "function"
toJSON SttMethod = A.String "method"
toJSON SttMacro = A.String "macro"
toJSON SttKeyword = A.String "keyword"
toJSON SttModifier = A.String "modifier"
toJSON SttComment = A.String "comment"
toJSON SttString = A.String "string"
toJSON SttNumber = A.String "number"
toJSON SttRegexp = A.String "regexp"
toJSON SttOperator = A.String "operator"
toJSON (SttUnknown t) = A.String t
instance A.FromJSON SemanticTokenTypes where
parseJSON (A.String "type") = pure SttType
parseJSON (A.String "class") = pure SttClass
parseJSON (A.String "enum") = pure SttEnum
parseJSON (A.String "interface") = pure SttInterface
parseJSON (A.String "struct") = pure SttStruct
parseJSON (A.String "typeParameter") = pure SttTypeParameter
parseJSON (A.String "parameter") = pure SttParameter
parseJSON (A.String "variable") = pure SttVariable
parseJSON (A.String "property") = pure SttProperty
parseJSON (A.String "enumMember") = pure SttEnumMember
parseJSON (A.String "event") = pure SttEvent
parseJSON (A.String "function") = pure SttFunction
parseJSON (A.String "method") = pure SttMethod
parseJSON (A.String "macro") = pure SttMacro
parseJSON (A.String "keyword") = pure SttKeyword
parseJSON (A.String "modifier") = pure SttModifier
parseJSON (A.String "comment") = pure SttComment
parseJSON (A.String "string") = pure SttString
parseJSON (A.String "number") = pure SttNumber
parseJSON (A.String "regexp") = pure SttRegexp
parseJSON (A.String "operator") = pure SttOperator
parseJSON (A.String t) = pure $ SttUnknown t
parseJSON _ = mempty
-- | The set of semantic token types which are "known" (i.e. listed in the LSP spec).
knownSemanticTokenTypes :: [SemanticTokenTypes]
knownSemanticTokenTypes = [
SttType
, SttClass
, SttEnum
, SttInterface
, SttStruct
, SttTypeParameter
, SttParameter
, SttVariable
, SttProperty
, SttEnumMember
, SttEvent
, SttFunction
, SttMethod
, SttMacro
, SttKeyword
, SttModifier
, SttComment
, SttString
, SttNumber
, SttRegexp
, SttOperator
]
data SemanticTokenModifiers =
StmDeclaration
| StmDefinition
| StmReadonly
| StmStatic
| StmDeprecated
| StmAbstract
| StmAsync
| StmModification
| StmDocumentation
| StmDefaultLibrary
| StmUnknown Text
deriving (Show, Read, Eq, Ord)
instance A.ToJSON SemanticTokenModifiers where
toJSON StmDeclaration = A.String "declaration"
toJSON StmDefinition = A.String "definition"
toJSON StmReadonly = A.String "readonly"
toJSON StmStatic = A.String "static"
toJSON StmDeprecated = A.String "deprecated"
toJSON StmAbstract = A.String "abstract"
toJSON StmAsync = A.String "async"
toJSON StmModification = A.String "modification"
toJSON StmDocumentation = A.String "documentation"
toJSON StmDefaultLibrary = A.String "defaultLibrary"
toJSON (StmUnknown t) = A.String t
instance A.FromJSON SemanticTokenModifiers where
parseJSON (A.String "declaration") = pure StmDeclaration
parseJSON (A.String "definition") = pure StmDefinition
parseJSON (A.String "readonly") = pure StmReadonly
parseJSON (A.String "static") = pure StmStatic
parseJSON (A.String "deprecated") = pure StmDeprecated
parseJSON (A.String "abstract") = pure StmAbstract
parseJSON (A.String "async") = pure StmAsync
parseJSON (A.String "modification") = pure StmModification
parseJSON (A.String "documentation") = pure StmDocumentation
parseJSON (A.String "defaultLibrary") = pure StmDefaultLibrary
parseJSON (A.String t) = pure $ StmUnknown t
parseJSON _ = mempty
-- | The set of semantic token modifiers which are "known" (i.e. listed in the LSP spec).
knownSemanticTokenModifiers :: [SemanticTokenModifiers]
knownSemanticTokenModifiers = [
StmDeclaration
, StmDefinition
, StmReadonly
, StmStatic
, StmDeprecated
, StmAbstract
, StmAsync
, StmModification
, StmDocumentation
, StmDefaultLibrary
]
data TokenFormat = TokenFormatRelative
deriving (Show, Read, Eq)
instance A.ToJSON TokenFormat where
toJSON TokenFormatRelative = A.String "relative"
instance A.FromJSON TokenFormat where
parseJSON (A.String "relative") = pure TokenFormatRelative
parseJSON _ = mempty
data SemanticTokensLegend = SemanticTokensLegend {
-- | The token types a server uses.
_tokenTypes :: List SemanticTokenTypes,
-- | The token modifiers a server uses.
_tokenModifiers :: List SemanticTokenModifiers
} deriving (Show, Read, Eq)
deriveJSON lspOptions ''SemanticTokensLegend
-- We give a default legend which just lists the "known" types and modifiers in the order they're listed.
instance Default SemanticTokensLegend where
def = SemanticTokensLegend (List knownSemanticTokenTypes) (List knownSemanticTokenModifiers)
data SemanticTokensRangeClientCapabilities = SemanticTokensRangeBool Bool | SemanticTokensRangeObj A.Value
deriving (Show, Read, Eq)
deriveJSON lspOptionsUntagged ''SemanticTokensRangeClientCapabilities
data SemanticTokensDeltaClientCapabilities = SemanticTokensDeltaClientCapabilities {
-- | The client will send the `textDocument/semanticTokens/full/delta`
-- request if the server provides a corresponding handler.
_delta :: Maybe Bool
} deriving (Show, Read, Eq)
deriveJSON lspOptions ''SemanticTokensDeltaClientCapabilities
data SemanticTokensFullClientCapabilities = SemanticTokensFullBool Bool | SemanticTokensFullDelta SemanticTokensDeltaClientCapabilities
deriving (Show, Read, Eq)
deriveJSON lspOptionsUntagged ''SemanticTokensFullClientCapabilities
data SemanticTokensRequestsClientCapabilities = SemanticTokensRequestsClientCapabilities {
-- | The client will send the `textDocument/semanticTokens/range` request
-- if the server provides a corresponding handler.
_range :: Maybe SemanticTokensRangeClientCapabilities,
-- | The client will send the `textDocument/semanticTokens/full` request
-- if the server provides a corresponding handler.
_full :: Maybe SemanticTokensFullClientCapabilities
} deriving (Show, Read, Eq)
deriveJSON lspOptions ''SemanticTokensRequestsClientCapabilities
data SemanticTokensClientCapabilities = SemanticTokensClientCapabilities {
-- | Whether implementation supports dynamic registration. If this is set to
-- `true` the client supports the new `(TextDocumentRegistrationOptions &
-- StaticRegistrationOptions)` return value for the corresponding server
-- capability as well.
_dynamicRegistration :: Maybe Bool,
-- | Which requests the client supports and might send to the server
-- depending on the server's capability. Please note that clients might not
-- show semantic tokens or degrade some of the user experience if a range
-- or full request is advertised by the client but not provided by the
-- server. If for example the client capability `requests.full` and
-- `request.range` are both set to true but the server only provides a
-- range provider the client might not render a minimap correctly or might
-- even decide to not show any semantic tokens at all.
_requests :: SemanticTokensRequestsClientCapabilities,
-- | The token types that the client supports.
_tokenTypes :: List SemanticTokenTypes,
-- | The token modifiers that the client supports.
_tokenModifiers :: List SemanticTokenModifiers,
-- | The formats the clients supports.
_formats :: List TokenFormat,
-- | Whether the client supports tokens that can overlap each other.
_overlappingTokenSupport :: Maybe Bool,
-- | Whether the client supports tokens that can span multiple lines.
_multilineTokenSupport :: Maybe Bool
} deriving (Show, Read, Eq)
deriveJSON lspOptions ''SemanticTokensClientCapabilities
makeExtendingDatatype "SemanticTokensOptions" [''WorkDoneProgressOptions]
[ ("_legend", [t| SemanticTokensLegend |])
, ("_range", [t| Maybe SemanticTokensRangeClientCapabilities |])
, ("_full", [t| Maybe SemanticTokensFullClientCapabilities |])
]
deriveJSON lspOptions ''SemanticTokensOptions
makeExtendingDatatype "SemanticTokensRegistrationOptions"
[ ''TextDocumentRegistrationOptions
, ''SemanticTokensOptions
, ''StaticRegistrationOptions] []
deriveJSON lspOptions ''SemanticTokensRegistrationOptions
makeExtendingDatatype "SemanticTokensParams"
[''WorkDoneProgressParams
, ''PartialResultParams]
[ ("_textDocument", [t| TextDocumentIdentifier |]) ]
deriveJSON lspOptions ''SemanticTokensParams
data SemanticTokens = SemanticTokens {
-- | An optional result id. If provided and clients support delta updating
-- the client will include the result id in the next semantic token request.
-- A server can then instead of computing all semantic tokens again simply
-- send a delta.
_resultId :: Maybe Text,
-- | The actual tokens.
_xdata :: List Int
} deriving (Show, Read, Eq)
deriveJSON lspOptions ''SemanticTokens
data SemanticTokensPartialResult = SemanticTokensPartialResult {
_xdata :: List Int
}
deriveJSON lspOptions ''SemanticTokensPartialResult
makeExtendingDatatype "SemanticTokensDeltaParams"
[''WorkDoneProgressParams
, ''PartialResultParams]
[ ("_textDocument", [t| TextDocumentIdentifier |])
, ("_previousResultId", [t| Text |])
]
deriveJSON lspOptions ''SemanticTokensDeltaParams
data SemanticTokensEdit = SemanticTokensEdit {
-- | The start offset of the edit.
_start :: Int,
-- | The count of elements to remove.
_deleteCount :: Int,
-- | The elements to insert.
_xdata :: Maybe (List Int)
} deriving (Show, Read, Eq)
deriveJSON lspOptions ''SemanticTokensEdit
data SemanticTokensDelta = SemanticTokensDelta {
_resultId :: Maybe Text,
-- | The semantic token edits to transform a previous result into a new
-- result.
_edits :: List SemanticTokensEdit
} deriving (Show, Read, Eq)
deriveJSON lspOptions ''SemanticTokensDelta
data SemanticTokensDeltaPartialResult = SemantictokensDeltaPartialResult {
_edits :: List SemanticTokensEdit
} deriving (Show, Read, Eq)
deriveJSON lspOptions ''SemanticTokensDeltaPartialResult
makeExtendingDatatype "SemanticTokensRangeParams"
[''WorkDoneProgressParams
, ''PartialResultParams]
[ ("_textDocument", [t| TextDocumentIdentifier |])
, ("_range", [t| Range |])
]
deriveJSON lspOptions ''SemanticTokensRangeParams
data SemanticTokensWorkspaceClientCapabilities = SemanticTokensWorkspaceClientCapabilities {
-- | Whether the client implementation supports a refresh request sent from
-- the server to the client.
--
-- Note that this event is global and will force the client to refresh all
-- semantic tokens currently shown. It should be used with absolute care
-- and is useful for situation where a server for example detect a project
-- wide change that requires such a calculation.
_refreshSupport :: Maybe Bool
} deriving (Show, Read, Eq)
deriveJSON lspOptions ''SemanticTokensWorkspaceClientCapabilities
----------------------------------------------------------
-- Tools for working with semantic tokens.
----------------------------------------------------------
-- | A single 'semantic token' as described in the LSP specification, using absolute positions.
-- This is the kind of token that is usually easiest for editors to produce.
data SemanticTokenAbsolute = SemanticTokenAbsolute {
line :: Int,
startChar :: Int,
length :: Int,
tokenType :: SemanticTokenTypes,
tokenModifiers :: [SemanticTokenModifiers]
} deriving (Show, Read, Eq, Ord)
-- Note: we want the Ord instance to sort the tokens textually: this is achieved due to the
-- order of the constructors
-- | A single 'semantic token' as described in the LSP specification, using relative positions.
data SemanticTokenRelative = SemanticTokenRelative {
deltaLine :: Int,
deltaStartChar :: Int,
length :: Int,
tokenType :: SemanticTokenTypes,
tokenModifiers :: [SemanticTokenModifiers]
} deriving (Show, Read, Eq, Ord)
-- Note: we want the Ord instance to sort the tokens textually: this is achieved due to the
-- order of the constructors
-- | Turn a list of absolutely-positioned tokens into a list of relatively-positioned tokens. The tokens are assumed to be in the
-- order that they appear in the document!
relativizeTokens :: [SemanticTokenAbsolute] -> [SemanticTokenRelative]
relativizeTokens xs = DList.toList $ go 0 0 xs mempty
where
-- Pass an accumulator to make this tail-recursive
go :: Int -> Int -> [SemanticTokenAbsolute] -> DList.DList SemanticTokenRelative -> DList.DList SemanticTokenRelative
go _ _ [] acc = acc
go lastLine lastChar (SemanticTokenAbsolute l c len ty mods:ts) acc =
let
lastCharInLine = if l == lastLine then lastChar else 0
dl = l - lastLine
dc = c - lastCharInLine
in go l c ts (DList.snoc acc (SemanticTokenRelative dl dc len ty mods))
-- | Turn a list of relatively-positioned tokens into a list of absolutely-positioned tokens. The tokens are assumed to be in the
-- order that they appear in the document!
absolutizeTokens :: [SemanticTokenRelative] -> [SemanticTokenAbsolute]
absolutizeTokens xs = DList.toList $ go 0 0 xs mempty
where
-- Pass an accumulator to make this tail-recursive
go :: Int -> Int -> [SemanticTokenRelative] -> DList.DList SemanticTokenAbsolute -> DList.DList SemanticTokenAbsolute
go _ _ [] acc = acc
go lastLine lastChar (SemanticTokenRelative dl dc len ty mods:ts) acc =
let
lastCharInLine = if dl == 0 then lastChar else 0
l = lastLine + dl
c = lastCharInLine + dc
in go l c ts (DList.snoc acc (SemanticTokenAbsolute l c len ty mods))
-- | Encode a series of relatively-positioned semantic tokens into an integer array following the given legend.
encodeTokens :: SemanticTokensLegend -> [SemanticTokenRelative] -> Either Text [Int]
encodeTokens SemanticTokensLegend{_tokenTypes=List tts,_tokenModifiers=List tms} sts =
DList.toList . DList.concat <$> traverse encodeToken sts
where
-- Note that there's no "fast" version of these (e.g. backed by an IntMap or similar)
-- in general, due to the possibility of unknown token types which are only identified by strings.
tyMap :: Map.Map SemanticTokenTypes Int
tyMap = Map.fromList $ zip tts [0..]
modMap :: Map.Map SemanticTokenModifiers Int
modMap = Map.fromList $ zip tms [0..]
lookupTy :: SemanticTokenTypes -> Either Text Int
lookupTy ty = case Map.lookup ty tyMap of
Just tycode -> pure tycode
Nothing -> throwError $ "Semantic token type " <> fromString (show ty) <> " did not appear in the legend"
lookupMod :: SemanticTokenModifiers -> Either Text Int
lookupMod modifier = case Map.lookup modifier modMap of
Just modcode -> pure modcode
Nothing -> throwError $ "Semantic token modifier " <> fromString (show modifier) <> " did not appear in the legend"
-- Use a DList here for better efficiency when concatenating all these together
encodeToken :: SemanticTokenRelative -> Either Text (DList.DList Int)
encodeToken (SemanticTokenRelative dl dc len ty mods) = do
tycode <- lookupTy ty
modcodes <- traverse lookupMod mods
let combinedModcode = foldl' Bits.setBit Bits.zeroBits modcodes
pure [dl, dc, len, tycode, combinedModcode ]
-- This is basically 'SemanticTokensEdit', but slightly easier to work with.
-- | An edit to a buffer of items.
data Edit a = Edit { editStart :: Int, editDeleteCount :: Int, editInsertions :: [a] }
deriving (Read, Show, Eq, Ord)
-- | Compute a list of edits that will turn the first list into the second list.
computeEdits :: Eq a => [a] -> [a] -> [Edit a]
computeEdits l r = DList.toList $ go 0 Nothing (Diff.getGroupedDiff l r) mempty
where
{-
Strategy: traverse the list of diffs, keeping the current index and (maybe) an in-progress 'Edit'.
Whenever we see a 'Diff' that's only one side or the other, we can bundle that in to our in-progress
'Edit'. We only have to stop if we see a 'Diff' that's on both sides (i.e. unchanged), then we
dump the 'Edit' into the accumulator.
We need the index, because 'Edit's need to say where they start.
-}
go :: Int -> Maybe (Edit a) -> [Diff.Diff [a]] -> DList.DList (Edit a) -> DList.DList (Edit a)
-- No more diffs: append the current edit if there is one and return
go _ e [] acc = acc <> DList.fromList (maybeToList e)
-- Items only on the left (i.e. deletions): increment the current index, and record the count of deletions,
-- starting a new edit if necessary.
go ix e (Diff.First ds : rest) acc =
let
deleteCount = Prelude.length ds
edit = fromMaybe (Edit ix 0 []) e
in go (ix + deleteCount) (Just (edit{editDeleteCount=editDeleteCount edit + deleteCount})) rest acc
-- Items only on the right (i.e. insertions): don't increment the current index, and record the insertions,
-- starting a new edit if necessary.
go ix e (Diff.Second as : rest) acc =
let edit = fromMaybe (Edit ix 0 []) e
in go ix (Just (edit{editInsertions=editInsertions edit <> as})) rest acc
-- Items on both sides: increment the current index appropriately (since the items appear on the left),
-- and append the current edit (if there is one) to our list of edits (since we can't continue it with a break).
go ix e (Diff.Both bs _bs : rest) acc =
let bothCount = Prelude.length bs
in go (ix + bothCount) Nothing rest (acc <> DList.fromList (maybeToList e))
-- | Convenience method for making a 'SemanticTokens' from a list of 'SemanticTokenAbsolute's. An error may be returned if
-- the tokens refer to types or modifiers which are not in the legend.
-- The resulting 'SemanticTokens' lacks a result ID, which must be set separately if you are using that.
makeSemanticTokens :: SemanticTokensLegend -> [SemanticTokenAbsolute] -> Either Text SemanticTokens
makeSemanticTokens legend sts = do
encoded <- encodeTokens legend $ relativizeTokens sts
pure $ SemanticTokens Nothing (List encoded)
-- | Convenience function for making a 'SemanticTokensDelta' from a previous and current 'SemanticTokens'.
-- The resulting 'SemanticTokensDelta' lacks a result ID, which must be set separately if you are using that.
makeSemanticTokensDelta :: SemanticTokens -> SemanticTokens -> SemanticTokensDelta
makeSemanticTokensDelta SemanticTokens{_xdata=List prevTokens} SemanticTokens{_xdata=List curTokens} =
let edits = computeEdits prevTokens curTokens
stEdits = fmap (\(Edit s ds as) -> SemanticTokensEdit s ds (Just $ List as)) edits
in SemanticTokensDelta Nothing (List stEdits)