lsp-bjolang/Project.fs

85 lines
4 KiB
Forth
Raw Normal View History

/// The project the server checks against: the packages `bjo` fetched for it,
/// which of them declared a .NET shared framework, and its NuGet packages.
///
/// `bjo` writes these under the project's `.bjo` and passes them to the
/// compiler as `--roots`, `--frameworks` and `--nuget`. A compiler process
/// serves one roots file, since every cache keyed by a path would answer from
/// a stale registry if the roots changed under it. So the server installs one
/// project's, that of the first file it is asked about, before anything is
/// resolved, and says so on a file it cannot serve as the command line would.
module BjolangLsp.Project
open System
open System.IO
/// The nearest directory at or above `dir` that holds a `manifest.bjodat`.
let rec private containing (dir: string) : string option =
if isNull dir then None
elif File.Exists(Path.Combine(dir, "manifest.bjodat")) then Some dir
else containing (Path.GetDirectoryName dir)
/// The project a file is in, if any.
let projectOf (path: string) : string option =
containing (Path.GetDirectoryName(Path.GetFullPath path))
let private rootsFile (project: string) = Path.Combine(project, ".bjo", "roots")
let private writtenAt (file: string) =
if File.Exists file then Some(File.GetLastWriteTimeUtc file) else None
type private State =
/// Nothing asked about yet.
| Undecided
/// The project whose inputs are installed, and when its roots file was
/// written then. `None` for a file in no project, or in a project whose
/// packages were not fetched: the standard library only.
| Serving of project: string option * roots: DateTime option
/// The inputs did not install, and why.
| Failed of project: string * message: string
let mutable private state = Undecided
/// Installs the inputs of the project `path` is in, the first time it is
/// called, and answers what the file's diagnostics should say about it: why
/// its packages are not seen as the command line sees them, or nothing.
let prepare (path: string) : string option =
let project = projectOf path
if state = Undecided then
state <-
match project with
| Some dir when File.Exists(rootsFile dir) ->
let frameworks = Path.Combine(dir, ".bjo", "frameworks")
let nuget = Path.Combine(dir, ".bjo", "nuget", "root")
let installed =
Bjolang.ProjectInputs.install
(Some(rootsFile dir))
(if File.Exists frameworks then Some frameworks else None)
[]
(if Directory.Exists nuget then Some nuget else None)
match installed with
| Ok() ->
Rpc.log $"checking against the packages of %s{dir}"
Serving(Some dir, writtenAt (rootsFile dir))
| Error message -> Failed(dir, message)
| Some _
| None -> Serving(None, None)
match state, project with
| Failed(dir, message), Some p when p = dir ->
Some $"The packages of this project did not load, so only the standard library is seen:\n%s{message}"
| Serving(Some dir, roots), Some p when p = dir ->
if writtenAt (rootsFile dir) <> roots then
Some "The project's packages changed after the language server started. Restart it to see them."
else
None
| Serving(None, _), Some p when not (File.Exists(rootsFile p)) ->
Some $"This project's packages are not fetched, so only the standard library is seen. Run `bjo fetch` in %s{p}, then restart the language server."
| Serving(None, _), Some p ->
Some $"The language server started on a file outside this project, and sees only the standard library. Restart it from a file in %s{p}."
| (Serving(Some dir, _) | Failed(dir, _)), Some p ->
Some $"The language server checks against the packages of %s{dir}. This file is in %s{p}: open it in a server of its own to see its packages."
| _ -> None