glean-lsp-0.1.0.0: Glean/LSP.hs
{-# LANGUAGE ApplicativeDo, RecursiveDo #-}
module Glean.LSP (main) where
import Control.Monad
import Control.Monad.Catch
import Control.Monad.Fix
import Control.Monad.IO.Class
import Control.Monad.IO.Unlift
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Control.Monad.Trans.Reader
import Data.Aeson as Aeson
import Data.Default
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.Int
import Data.List (sortBy)
import qualified Data.Map as Map
import Data.Maybe
import Data.Ord (comparing)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Language.LSP.Server as LSP
import Language.LSP.Server (LspM)
import qualified Language.LSP.Protocol.Message as LSP
import qualified Language.LSP.Protocol.Types as LSP
import Options.Applicative
import Thrift.Protocol
import UnliftIO.Async
import UnliftIO.Exception
import UnliftIO.IORef
import Util.Log.Text
import Util.Timing
-- use Glass as a library directly
import qualified Glean.Glass.Env as Glass
import qualified Glean.Glass.Main as Glass
import qualified Glean.Glass.Options as Glass
import qualified Glean.Glass.Tracing as Glass
import qualified Glean.Glass.Types as Glass
import qualified Glean.Glass.Range as Glass
import qualified Glean.Glass.Handler.Documents as Glass.Handler
import qualified Glean.Glass.Handler.Symbols as Glass.Handler
import Data.ConcurrentCache as ConcurrentCache
import Data.Path as Path
{- TODO / ideas
- go to decl / go to impl / go to type def?
- call hierarchy
- documentSymbols:
- maybe filter out type parameters?
- update index after edits
1. make it possible to re-index on the side and create a new DB,
using a separate glean-server. Need to flush the cache if we
detect that the data has changed.
2. Trigger re-indexing from the UI somehow, with a progress update
3. Support full re-index & in-memory incremental index for changed files only?
- can we show errors somehow?
- maybe use tree-sitter to get uptodate documentSymbols and
merge those with the real symbols from Glass?
- implement setTrace?
-}
-- -----------------------------------------------------------------------------
-- Command-line options
-- | Inherit CLI options from Glass
data LspOptions = LspOptions {
glass :: Glass.Config Glass.GlassTrace
}
options :: ParserInfo LspOptions
options = info (helper <*> (LspOptions <$> Glass.configParser)) fullDesc
-- -----------------------------------------------------------------------------
-- Config
-- | Config from the LSP client. e.g. for VS Code, @<workspace>/.vscode/settings.json@:
--
-- > {
-- > "glean-lsp": {
-- > "repo": "stackage"
-- > },
-- > ...
-- > }
--
data LspConfig = LspConfig {
repo :: Glass.RepoName
}
deriving (Show)
instance FromJSON LspConfig where
parseJSON = withObject "LspConfig" $ \v -> LspConfig
<$> fmap Glass.RepoName (v .: "repo")
instance Default LspConfig where
def = LspConfig {
repo = Glass.RepoName "stackage"
}
-- -----------------------------------------------------------------------------
-- Monad
-- | Environment of the running LSP server after initialisation
data LspEnv = LspEnv {
options :: LspOptions,
wsRoot :: AbsPath,
glass :: Glass.Env,
symbolCache :: ConcurrentCache RelPath Glass.DocumentSymbolIndex,
requests :: IORef (HashMap LSP.SomeLspId (Async ()))
}
newtype GleanLspM a =
GleanLspM { unGleanLspM :: ReaderT (IORef (Maybe LspEnv)) (LspM LspConfig) a }
deriving (Monad, Applicative, Functor, MonadIO, MonadThrow, MonadUnliftIO, MonadFix)
deriving instance LSP.MonadLsp LspConfig GleanLspM
runGleanLspM :: IORef (Maybe LspEnv) -> GleanLspM a -> LspM LspConfig a
runGleanLspM envRef = flip runReaderT envRef . (.unGleanLspM)
newtype GleanLspException = GleanLspException Text
deriving Show
instance Exception GleanLspException
getGleanLspEnv :: GleanLspM LspEnv
getGleanLspEnv = GleanLspM $ do
ref <- ask
r <- readIORef ref
case r of
Nothing -> throwIO $ GleanLspException "not initialized"
Just env -> return env
-- -----------------------------------------------------------------------------
-- Executing requests
-- | Perform a request asynchronously. It can subsequently be
-- cancelled by 'cancelRequest', which will send the appropriate
-- @RequestCancelled@ response back to the client if the request was
-- still in progress at the time of cancellation.
asyncRequest ::
forall (m :: LSP.Method LSP.ClientToServer LSP.Request) .
(LSP.TRequestMessage m ->
GleanLspM (Either (LSP.TResponseError m) (LSP.MessageResult m))) ->
LSP.Handler GleanLspM m
asyncRequest act = \msg respond -> mdo
env <- getGleanLspEnv
m <- readIORef env.requests
liftIO $ logInfo $ "asyncRequest: " <>
Text.pack (show (HashMap.size m)) <> " in progress"
let
register =
atomicModifyIORef env.requests $ -- not strict due to mdo
\m -> (HashMap.insert (LSP.SomeLspId msg._id) async m, ())
unregister =
atomicModifyIORef' env.requests $
\m -> (HashMap.delete (LSP.SomeLspId msg._id) m, ())
register
async <- UnliftIO.Exception.mask_ $ asyncWithUnmask $ \unmask -> do
r <- UnliftIO.Exception.trySyncOrAsync $ unmask $ act msg
-- we want to catch everything, including async cancellation
unregister
case r of
Left (ex :: SomeException)
| Just AsyncCancelled <- fromException ex ->
respond $ Left $ responseCancelled "cancelled by client"
| otherwise -> respond $ Left $ responseException ex
Right result -> respond result
return ()
-- | Cancel a request by LspId
cancelRequest ::
forall (m :: LSP.Method LSP.ClientToServer LSP.Request) .
LSP.LspId m ->
GleanLspM ()
cancelRequest id = do
env <- getGleanLspEnv
m <- readIORef env.requests
case HashMap.lookup (LSP.SomeLspId id) m of
Nothing -> logWarning $ "cancelRequest: not found"
Just async -> cancel async
responseCancelled ::
forall (m :: LSP.Method LSP.ClientToServer LSP.Request) .
Text ->
LSP.TResponseError m
responseCancelled msg =
LSP.TResponseError {
_code = LSP.InL LSP.LSPErrorCodes_RequestCancelled,
_message = msg,
_xdata = Nothing
}
responseException ::
forall (m :: LSP.Method LSP.ClientToServer LSP.Request) .
SomeException ->
LSP.TResponseError m
responseException ex =
LSP.TResponseError {
_code = LSP.InL LSP.LSPErrorCodes_RequestFailed,
_message = Text.pack (show ex),
_xdata = Nothing
}
-- -----------------------------------------------------------------------------
-- Setup & initialisation
initServer ::
Glass.Env ->
LspOptions ->
IORef (Maybe LspEnv) ->
LSP.LanguageContextEnv LspConfig ->
LSP.TMessage 'LSP.Method_Initialize ->
IO (Either (LSP.TResponseError LSP.Method_Initialize) (LSP.LanguageContextEnv LspConfig))
initServer glass options envRef serverConfig _msg = do
runExceptT $ do
wsRoot <- ExceptT $ LSP.runLspT serverConfig getWsRoot
wsRoot <- filePathToAbs wsRoot
symbolCache <- ConcurrentCache.new
requests <- newIORef HashMap.empty
writeIORef envRef (Just LspEnv { options, glass, wsRoot, symbolCache, requests })
liftIO $ logInfo $ "wsRoot: " <> Text.pack (Path.toFilePath wsRoot)
pure serverConfig
where
getWsRoot :: LSP.LspM config (Either (LSP.TResponseError LSP.Method_Initialize) FilePath)
getWsRoot = do
mRootPath <- LSP.getRootPath
pure $ case mRootPath of
Nothing -> Left $ LSP.TResponseError (LSP.InR LSP.ErrorCodes_InvalidRequest)
"No root workspace was found" Nothing
Just p -> Right p
serverDef
:: Glass.Env
-> LspOptions
-> IO (LSP.ServerDefinition LspConfig)
serverDef glass options = do
envRef <- newIORef Nothing
let
mapReq ::
forall (a :: LSP.Method LSP.ClientToServer LSP.Request).
LSP.Handler GleanLspM a ->
LSP.Handler (LspM LspConfig) a
mapReq f = \msg responseCont -> runGleanLspM envRef $
f msg (\resp -> GleanLspM (lift (responseCont resp)))
mapNot ::
forall (a :: LSP.Method LSP.ClientToServer LSP.Notification).
LSP.Handler GleanLspM a ->
LSP.Handler (LspM LspConfig) a
mapNot f = \msg -> runGleanLspM envRef $ f msg
pure
LSP.ServerDefinition
{ onConfigChange = \_conf -> runGleanLspM envRef $ do
liftIO $ logInfo "config change"
flushSymbolCache
, configSection = "glean-lsp"
, parseConfig = \_conf value ->
case fromJSON value of
Error err -> Left (Text.pack err)
Aeson.Success conf -> Right conf
, doInitialize = initServer glass options envRef
, -- TODO: Do handlers need to inspect clientCapabilities?
staticHandlers = \_clientCapabilities ->
LSP.mapHandlers mapReq mapNot $
mconcat [
handleInitialized
, handleChangeConfiguration
, handleTextDocumentHoverRequest
, handleDefinitionRequest
-- , handleTypeDefinitionRequest
-- , handleImplementationRequest
, handleReferencesRequest
-- , handleRenameRequest
-- , handlePrepareRenameRequest
, handleCancelNotification
, handleDidOpen
-- , handleDidChange
-- , handleDidSave
, handleDidClose
, handleWorkspaceSymbol
, handleSetTrace
-- , handleCodeAction
-- , handleResolveCodeAction
, handleDocumentSymbols
-- , handleCompletion
-- , handleCompletionItemResolve
]
, interpretHandler = \env -> LSP.Iso (LSP.runLspT env) liftIO
, options = lspOptions
, defaultConfig = def
}
lspOptions :: LSP.Options
lspOptions =
LSP.defaultOptions
{ LSP.optTextDocumentSync =
Just
LSP.TextDocumentSyncOptions
{ LSP._openClose = Just True
, LSP._change = Just LSP.TextDocumentSyncKind_Incremental
, LSP._willSave = Just False
, LSP._willSaveWaitUntil = Just False
, LSP._save =
Just $
LSP.InR $
LSP.SaveOptions
{ LSP._includeText = Just False
}
}
, LSP.optCompletionTriggerCharacters = Just ['.']
}
main :: IO ()
main = do
opts <- execParser options
Glass.withEnv opts.glass Nothing $ \glass -> do
server <- serverDef glass opts
void $ LSP.runServer server
-- -----------------------------------------------------------------------------
-- Handlers
handleChangeConfiguration :: LSP.Handlers GleanLspM
handleChangeConfiguration =
LSP.notificationHandler LSP.SMethod_WorkspaceDidChangeConfiguration $
pure $ pure ()
handleInitialized :: LSP.Handlers GleanLspM
handleInitialized =
LSP.notificationHandler LSP.SMethod_Initialized $
pure $ pure ()
handleCancelNotification :: LSP.Handlers GleanLspM
handleCancelNotification =
LSP.notificationHandler LSP.SMethod_CancelRequest $ \req -> do
let id = case req._params._id of
LSP.InL i -> LSP.IdInt i
LSP.InR t -> LSP.IdString t
liftIO $ logInfo $ "cancel: " <> Text.pack (show id)
cancelRequest id
handleDidOpen :: LSP.Handlers GleanLspM
handleDidOpen =
LSP.notificationHandler LSP.SMethod_TextDocumentDidOpen $ \message -> do
liftIO $ logInfo $ "did open: " <> message._params._textDocument._uri.getUri
handleDidClose :: LSP.Handlers GleanLspM
handleDidClose =
LSP.notificationHandler LSP.SMethod_TextDocumentDidClose $ \req -> do
let uri = req._params._textDocument._uri
liftIO $ logInfo $ "did close: " <> uri.getUri
path <- uriToAbsPath uri
removeCachedSymbols path
handleDocumentSymbols :: LSP.Handlers GleanLspM
handleDocumentSymbols =
LSP.requestHandler LSP.SMethod_TextDocumentDocumentSymbol $ asyncRequest $ \req ->
logTimed ("documentSymbols: " <> req._params._textDocument._uri.getUri) $ do
let params = req._params
path <- uriToAbsPath params._textDocument._uri
syms <- getDocumentSymbols path
liftIO $ logInfo $ "symbols: " <> Text.pack (show (length syms))
return $ Right $ LSP.InR $ LSP.InL syms
handleDefinitionRequest :: LSP.Handlers GleanLspM
handleDefinitionRequest =
LSP.requestHandler LSP.SMethod_TextDocumentDefinition $ asyncRequest $ \req -> do
let params = req._params
logTimed ("definition: " <> params._textDocument._uri.getUri <>
Text.pack (show req._params._position)) $ do
path <- uriToAbsPath params._textDocument._uri
defs <- getDefinition path params._position
return $ Right . LSP.InR $ LSP.InL defs
handleSetTrace :: LSP.Handlers GleanLspM
handleSetTrace = LSP.notificationHandler LSP.SMethod_SetTrace $ \_ -> pure ()
handleTextDocumentHoverRequest :: LSP.Handlers GleanLspM
handleTextDocumentHoverRequest =
LSP.requestHandler LSP.SMethod_TextDocumentHover $ asyncRequest $ \req -> do
let hoverParams = req._params
logTimed ("hover: " <> hoverParams._textDocument._uri.getUri <>
Text.pack (show hoverParams._position)) $ do
path <- uriToAbsPath hoverParams._textDocument._uri
hover <- retrieveHover path hoverParams._position
return $ Right $ LSP.maybeToNull hover
handleReferencesRequest :: LSP.Handlers GleanLspM
handleReferencesRequest =
LSP.requestHandler LSP.SMethod_TextDocumentReferences $ asyncRequest $ \req -> do
let params = req._params
logTimed ("references: " <> params._textDocument._uri.getUri <>
Text.pack (show params._position)) $ do
path <- uriToAbsPath params._textDocument._uri
refs <- findRefs path params._position
return $ Right $ LSP.InL refs
handleWorkspaceSymbol :: LSP.Handlers GleanLspM
handleWorkspaceSymbol =
LSP.requestHandler LSP.SMethod_WorkspaceSymbol $ asyncRequest $ \req ->
logTimed ("search: " <> req._params._query) $ do
symbols <- symbolSearch req._params._query
return $ Right . LSP.InL $ symbols
-- -----------------------------------------------------------------------------
-- Glean / Glass stuff
getDefinition ::
AbsPath ->
LSP.Position ->
GleanLspM [LSP.DefinitionLink]
getDefinition path lineCol = do
env <- getGleanLspEnv
symbols <- findSymbol lineCol <$> getSymbolsCached path
return $ fmap (LSP.DefinitionLink . locationToLocationLink) $
refTargets env.wsRoot symbols
getSymbols ::
RelPath ->
Bool ->
GleanLspM Glass.DocumentSymbolIndex
getSymbols path includeRefs = do
env <- getGleanLspEnv
cfg :: LspConfig <- LSP.getConfig
let
query = def {
Glass.documentSymbolsRequest_repository = cfg.repo
, Glass.documentSymbolsRequest_filepath =
Glass.Path (Text.pack (Path.toFilePath path))
, Glass.documentSymbolsRequest_include_refs = includeRefs
}
opts = def
liftIO $ Glass.Handler.documentSymbolIndex env.glass query opts
getSymbolsCached :: AbsPath -> GleanLspM Glass.DocumentSymbolIndex
getSymbolsCached path = do
env <- getGleanLspEnv
let relPath = Path.makeRelative env.wsRoot path
ConcurrentCache.insert relPath env.symbolCache $
getSymbols relPath True {- includeRefs -}
flushSymbolCache :: GleanLspM ()
flushSymbolCache = do
env <- getGleanLspEnv
ConcurrentCache.flush env.symbolCache
removeCachedSymbols :: AbsPath -> GleanLspM ()
removeCachedSymbols path = do
env <- getGleanLspEnv
let relPath = Path.makeRelative env.wsRoot path
ConcurrentCache.remove relPath env.symbolCache
findSymbol ::
LSP.Position ->
Glass.DocumentSymbolIndex ->
[Glass.SymbolX]
findSymbol (LSP.Position l c) ix =
[ sym
| sym <- Map.findWithDefault [] (fromIntegral (l+1)) refs
, inRange (fromIntegral (l+1)) (fromIntegral (c+1)) sym.symbolX_range
]
where
refs = Glass.documentSymbolIndex_symbols ix
inRange line col (Glass.Range lb cb le ce) =
line >= lb && line <= le &&
(if line == lb then col >= cb else True) &&
(if line == le then col <= ce else True)
retrieveHover ::
AbsPath ->
LSP.Position ->
GleanLspM (Maybe LSP.Hover)
retrieveHover path position = do
syms <- getSymbolsCached path
case findSymbol position syms of
(sym:_) | Just ty <- attrSymbolSignature sym.symbolX_attributes -> do
-- TODO: pick the innermost match
logInfo $ "hover: " <> Text.pack (show sym)
return $ Just $ LSP.Hover
{ _range = Just $ toLspRange sym.symbolX_range
, _contents = LSP.InL $ LSP.MarkupContent LSP.MarkupKind_PlainText ty
}
_ -> return Nothing
findRefs ::
AbsPath ->
LSP.Position ->
GleanLspM [LSP.Location]
findRefs path pos = do
logInfo $ "Glean.findRefs: " <> Text.pack (show pos)
env <- getGleanLspEnv
syms <- getSymbolsCached path
case findSymbol pos syms of
[] -> do
logInfo $ "not found"
return []
(defn:_) -> do
-- TODO: pick the innermost or tightest range if there are many
logInfo $ "found: " <> Text.pack (show defn)
ranges <- liftIO $
Glass.Handler.findReferenceRanges env.glass defn.symbolX_sym def
return (map (toLspLocation env.wsRoot) ranges)
-- | Document symbols, used to generate the outline.
--
-- We want a hierarchical symbol structure for the outline. Glass
-- provides a symbolParent attribute, but this only reflects the
-- parent container of the qualified name which is usually the module
-- or namespace, not the full containment relation.
--
-- We could ask Glass for the containment relation, but that would
-- likely be expensive. So instead we reconstruct an approximate
-- symbol hierarchy using source ranges - arguably this is what we
-- want for the outline anyway.
--
getDocumentSymbols ::
AbsPath ->
GleanLspM [LSP.DocumentSymbol]
getDocumentSymbols path = do
syms <- getSymbolsCached path
let
defs =
[ sym
| sym <- concat $ Map.elems syms.documentSymbolIndex_symbols
, isNothing sym.symbolX_target -- only definitions
]
return $ mkSymbolTree (sortBy (comparing (.symbolX_range)) defs)
where
mkSymbolTree [] = []
mkSymbolTree (sym : rest)
| Just name <- attrSymbolName sym.symbolX_attributes =
parent name : mkSymbolTree others
| otherwise = mkSymbolTree rest
where
(children, others) = span isChild rest
isChild child = sym.symbolX_range `Glass.rangeContains` child.symbolX_range
parent name = LSP.DocumentSymbol {
_name = name,
_detail = attrSymbolSignature sym.symbolX_attributes,
_kind = kind,
_tags = Nothing, -- TODO?
_deprecated = Nothing,
_range = range,
_selectionRange = range,
_children = case mkSymbolTree children of
[] -> Nothing
some -> Just some
}
kind = fromMaybe LSP.SymbolKind_Function (attrSymbolKind sym.symbolX_attributes)
range = toLspRange sym.symbolX_range
symbolSearch ::
Text ->
GleanLspM [LSP.SymbolInformation]
symbolSearch query = do
env <- getGleanLspEnv
cfg :: LspConfig <- LSP.getConfig
let
req = def {
Glass.symbolSearchRequest_name = query,
Glass.symbolSearchRequest_repo_name = Just cfg.repo,
Glass.symbolSearchRequest_options = def {
Glass.symbolSearchOptions_detailedResults = True -- we need kinds
}
}
opts = def
res <- liftIO $ Glass.Handler.searchSymbol env.glass req opts
return [
LSP.SymbolInformation {
_name = sym.symbolDescription_name.qualifiedName_localName.unName,
_kind = maybe LSP.SymbolKind_Function toLspSymbolKind sym.symbolDescription_kind,
_tags = Nothing,
_deprecated = Nothing,
_location = toLspLocation env.wsRoot sym.symbolDescription_sym_location,
_containerName = Nothing
}
| sym <- res.symbolSearchResult_symbolDetails
]
-- -----------------------------------------------------------------------------
-- Data conversion Glass <-> LSP
refTargets :: AbsPath -> [Glass.SymbolX] -> [LSP.Location]
refTargets wsRoot syms =
[ toLspLocation wsRoot rg
| Glass.SymbolX{symbolX_target = Just rg} <- syms
]
toLspLocation :: AbsPath -> Glass.LocationRange -> LSP.Location
toLspLocation wsRoot locRange =
LSP.Location uri (toLspRange locRange.locationRange_range)
where
path = Glass.unPath locRange.locationRange_filepath
uri = absPathToUri (wsRoot Path.</> Path.filePathToRel (Text.unpack path))
toLspPosition :: Int64 -> Int64 -> LSP.Position
toLspPosition line col =
LSP.Position (fromIntegral (line-1)) (fromIntegral (col-1))
toLspRange :: Glass.Range -> LSP.Range
toLspRange range = LSP.Range begin end
where
begin = toLspPosition range.range_lineBegin range.range_columnBegin
end = toLspPosition range.range_lineEnd range.range_columnEnd
toLspSymbolKind :: Glass.SymbolKind -> LSP.SymbolKind
toLspSymbolKind = \case
Glass.SymbolKind_Package -> LSP.SymbolKind_Package
Glass.SymbolKind_Type -> LSP.SymbolKind_Class -- ?
Glass.SymbolKind_Value -> LSP.SymbolKind_Constant -- ?
Glass.SymbolKind_File -> LSP.SymbolKind_File
Glass.SymbolKind_Module -> LSP.SymbolKind_Module
Glass.SymbolKind_Namespace -> LSP.SymbolKind_Namespace
Glass.SymbolKind_Class_ -> LSP.SymbolKind_Class
Glass.SymbolKind_Method -> LSP.SymbolKind_Method
Glass.SymbolKind_Property -> LSP.SymbolKind_Property
Glass.SymbolKind_Field -> LSP.SymbolKind_Field
Glass.SymbolKind_Constructor -> LSP.SymbolKind_Constructor
Glass.SymbolKind_Enum -> LSP.SymbolKind_Enum
Glass.SymbolKind_Interface -> LSP.SymbolKind_Interface
Glass.SymbolKind_Function -> LSP.SymbolKind_Function
Glass.SymbolKind_Variable -> LSP.SymbolKind_Variable
Glass.SymbolKind_Constant -> LSP.SymbolKind_Constant
Glass.SymbolKind_String -> LSP.SymbolKind_String
Glass.SymbolKind_Number -> LSP.SymbolKind_Number
Glass.SymbolKind_Boolean -> LSP.SymbolKind_Boolean
Glass.SymbolKind_Array -> LSP.SymbolKind_Array
Glass.SymbolKind_Object -> LSP.SymbolKind_Object
Glass.SymbolKind_Key -> LSP.SymbolKind_Key
Glass.SymbolKind_Null -> LSP.SymbolKind_Null
Glass.SymbolKind_Enumerator -> LSP.SymbolKind_EnumMember
Glass.SymbolKind_Struct -> LSP.SymbolKind_Struct
Glass.SymbolKind_Event -> LSP.SymbolKind_Event
Glass.SymbolKind_Operator -> LSP.SymbolKind_Operator
Glass.SymbolKind_TypeParameter -> LSP.SymbolKind_TypeParameter
Glass.SymbolKind_Union -> LSP.SymbolKind_Class -- ?
Glass.SymbolKind_Macro -> LSP.SymbolKind_Function -- ?
Glass.SymbolKind_Trait -> LSP.SymbolKind_Interface -- ?
Glass.SymbolKind_Fragment -> LSP.SymbolKind_Struct -- ?
Glass.SymbolKind_Operation -> LSP.SymbolKind_Method -- ?
Glass.SymbolKind_Directive -> LSP.SymbolKind_Null -- ?
_ -> LSP.SymbolKind_Null
attrSymbolSignature :: Glass.Attributes -> Maybe Text
attrSymbolSignature attrs =
case Map.lookup "symbolSignature" attrs.unAttributes of
Just (Glass.Attribute_aString ty) -> Just ty
_ -> Nothing
attrSymbolName :: Glass.Attributes -> Maybe Text
attrSymbolName attrs =
case Map.lookup "symbolName" attrs.unAttributes of
Just (Glass.Attribute_aString nm) -> Just nm
_ -> Nothing
attrSymbolKind :: Glass.Attributes -> Maybe LSP.SymbolKind
attrSymbolKind attrs =
case Map.lookup "symbolKind" attrs.unAttributes of
Just (Glass.Attribute_aInteger kind) ->
Just (toLspSymbolKind (toThriftEnum (fromIntegral kind)))
_ -> Nothing
-- -----------------------------------------------------------------------------
-- Utils
uriToAbsPath :: (MonadThrow m) => LSP.Uri -> m AbsPath
uriToAbsPath uri =
case LSP.uriToFilePath uri of
Nothing -> throwM $ GleanLspException $ "URI is not a file: " <> LSP.getUri uri
Just path -> Path.filePathToAbsThrow path
absPathToUri :: AbsPath -> LSP.Uri
absPathToUri = LSP.filePathToUri . Path.toFilePath
locationToLocationLink :: LSP.Location -> LSP.LocationLink
locationToLocationLink LSP.Location {_uri, _range} =
LSP.LocationLink
{ _originSelectionRange = Nothing
, _targetUri = _uri
, _targetRange = _range
, _targetSelectionRange = _range
}
logTimed :: MonadIO m => Text -> m a -> m a
logTimed msg io = do
(t, b, a) <- timeIt io
liftIO $ logInfo $ msg <> ": " <>
Text.pack (showTime t) <> ", " <> Text.pack (showAllocs b)
return a