85 lines
4 KiB
Forth
85 lines
4 KiB
Forth
|
|
/// 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
|