futhark-0.25.1: src/Futhark/LSP/Compile.hs
-- | Building blocks for "recompiling" (actually just type-checking)
-- the Futhark program managed by the language server. The challenge
-- here is that if the program becomes type-invalid, we want to keep
-- the old state around.
module Futhark.LSP.Compile (tryTakeStateFromIORef, tryReCompile) where
import Colog.Core (logStringStderr, (<&))
import Control.Lens.Getter (view)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Data.IORef (IORef, readIORef, writeIORef)
import Data.Map qualified as M
import Data.Maybe (fromMaybe)
import Data.Text qualified as T
import Futhark.Compiler.Program (LoadedProg, lpFilePaths, lpWarnings, noLoadedProg, reloadProg)
import Futhark.LSP.Diagnostic (diagnosticSource, maxDiagnostic, publishErrorDiagnostics, publishWarningDiagnostics)
import Futhark.LSP.State (State (..), emptyState, updateStaleContent, updateStaleMapping)
import Futhark.LSP.Tool (computeMapping)
import Language.Futhark.Warnings (listWarnings)
import Language.LSP.Protocol.Types
( filePathToUri,
fromNormalizedFilePath,
toNormalizedUri,
uriToNormalizedFilePath,
)
import Language.LSP.Server (LspT, flushDiagnosticsBySource, getVirtualFile, getVirtualFiles)
import Language.LSP.VFS (VFS, vfsMap, virtualFileText)
-- | Try to take state from IORef, if it's empty, try to compile.
tryTakeStateFromIORef :: IORef State -> Maybe FilePath -> LspT () IO State
tryTakeStateFromIORef state_mvar file_path = do
old_state <- liftIO $ readIORef state_mvar
case stateProgram old_state of
Nothing -> do
new_state <- tryCompile old_state file_path noLoadedProg
liftIO $ writeIORef state_mvar new_state
pure new_state
Just prog -> do
-- If this is in the context of some file that is not part of
-- the program, try to reload the program from that file.
let files = lpFilePaths prog
state <- case file_path of
Just file_path'
| file_path' `notElem` files -> do
logStringStderr <& ("File not part of program: " <> show file_path')
logStringStderr <& ("Program contains: " <> show files)
tryCompile old_state file_path noLoadedProg
_ -> pure old_state
liftIO $ writeIORef state_mvar state
pure state
-- | Try to (re)-compile, replace old state if successful.
tryReCompile :: IORef State -> Maybe FilePath -> LspT () IO ()
tryReCompile state_mvar file_path = do
logStringStderr <& "(Re)-compiling ..."
old_state <- liftIO $ readIORef state_mvar
let loaded_prog = getLoadedProg old_state
new_state <- tryCompile old_state file_path loaded_prog
case stateProgram new_state of
Nothing -> do
logStringStderr <& "Failed to (re)-compile, using old state or Nothing"
logStringStderr <& "Computing PositionMapping for: " <> show file_path
mapping <- computeMapping old_state file_path
liftIO $ writeIORef state_mvar $ updateStaleMapping file_path mapping old_state
Just _ -> do
logStringStderr <& "(Re)-compile successful"
liftIO $ writeIORef state_mvar new_state
-- | Try to compile, publish diagnostics on warnings and errors, return newly compiled state.
-- Single point where the compilation is done, and shouldn't be exported.
tryCompile :: State -> Maybe FilePath -> LoadedProg -> LspT () IO State
tryCompile _ Nothing _ = pure emptyState
tryCompile state (Just path) old_loaded_prog = do
logStringStderr <& "Reloading program from " <> show path
vfs <- getVirtualFiles
res <- liftIO $ reloadProg old_loaded_prog [path] (transformVFS vfs) -- NOTE: vfs only keeps track of current opened files
flushDiagnosticsBySource maxDiagnostic diagnosticSource
case res of
Right new_loaded_prog -> do
publishWarningDiagnostics $ listWarnings $ lpWarnings new_loaded_prog
maybe_virtual_file <- getVirtualFile $ toNormalizedUri $ filePathToUri path
case maybe_virtual_file of
Nothing -> pure $ State (Just new_loaded_prog) (staleData state) -- should never happen
Just virtual_file ->
pure $ updateStaleContent path virtual_file new_loaded_prog state
-- Preserve files that have been opened should be enoguth.
-- But still might need an update on re-compile logic, don't discard all state afterwards,
-- try to compile from root file, if there is a depencency relatetion, improve performance and provide more dignostic.
Left prog_error -> do
logStringStderr <& "Compilation failed, publishing diagnostics"
publishErrorDiagnostics prog_error
pure emptyState
-- | Transform VFS to a map of file paths to file contents.
-- This is used to pass the file contents to the compiler.
transformVFS :: VFS -> M.Map FilePath T.Text
transformVFS vfs =
M.foldrWithKey
( \uri virtual_file acc ->
case uriToNormalizedFilePath uri of
Nothing -> acc
Just file_path ->
M.insert (fromNormalizedFilePath file_path) (virtualFileText virtual_file) acc
)
M.empty
(view vfsMap vfs)
getLoadedProg :: State -> LoadedProg
getLoadedProg state = fromMaybe noLoadedProg (stateProgram state)