Skip to content

Commit

Permalink
Add purescript-language-server (#46)
Browse files Browse the repository at this point in the history
* First cut, no node_modules

* Integrate language server fully

* purs-language-server -> purescript-language-server

* Rename language server file
  • Loading branch information
thomashoneyman authored Sep 20, 2023
1 parent 7591ffe commit 401552c
Show file tree
Hide file tree
Showing 13 changed files with 285 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Pure and reproducible overlay for the standard PureScript toolchain, including s
- `spago`, the package manager
- `purs-tidy`, the code formatter
- `purs-backend-es`, the optimizer
- `purescript-language-server`, the language server protocol

> :warning: This library is unstable and may be reorganized. Use at your own risk!
Expand Down
10 changes: 8 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,19 @@
purs-backend-es = pkgs.purs-backend-es;
purs-backend-es-unstable = pkgs.purs-backend-es-unstable;
purs-backend-es-bin = pkgs.purs-backend-es-bin;

purescript-language-server = pkgs.purescript-language-server;
purescript-language-server-unstable = pkgs.purescript-language-server-unstable;
purescript-language-server-bin = pkgs.purescript-language-server-bin;
in
{
inherit purs purs-unstable spago spago-unstable purs-tidy purs-tidy-unstable purs-backend-es purs-backend-es-unstable;
inherit purs purs-unstable spago spago-unstable purs-tidy purs-tidy-unstable purs-backend-es purs-backend-es-unstable purescript-language-server purescript-language-server-unstable;
}
// purs-bin
// spago-bin
// purs-tidy-bin
// purs-backend-es-bin);
// purs-backend-es-bin
// purescript-language-server-bin);

apps = forAllSystems (system: let
pkgs = nixpkgsFor.${system};
Expand Down Expand Up @@ -143,6 +148,7 @@
self.packages.${system}.purs-unstable
self.packages.${system}.purs-tidy-unstable
self.packages.${system}.purs-backend-es-unstable
self.packages.${system}.purescript-language-server-unstable
];
};
});
Expand Down
12 changes: 11 additions & 1 deletion generate/bin/src/AppM.purs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Effect.Class (class MonadEffect)
import Lib.Foreign.Octokit (GitHubError, Octokit)
import Lib.Git (GitM(..))
import Lib.GitHub (GitHubM(..))
import Lib.Nix.Manifest (NamedManifest, PursManifest, PursTidyManifest, SpagoManifest, PursBackendEsManifest)
import Lib.Nix.Manifest (NamedManifest, PursBackendEsManifest, PursManifest, PursTidyManifest, SpagoManifest, PursLanguageServerManifest)
import Lib.Nix.Manifest as Nix.Manifest
import Lib.Nix.Manifest as Tool
import Lib.Tool (Tool(..))
Expand Down Expand Up @@ -117,3 +117,13 @@ writePursBackendEsManifest :: PursBackendEsManifest -> AppM Unit
writePursBackendEsManifest manifest = do
path <- getToolManifestPath PursBackendEs
Utils.writeJsonFile path Nix.Manifest.pursTidyManifestCodec manifest

readPursLanguageServerManifest :: AppM PursLanguageServerManifest
readPursLanguageServerManifest = do
path <- getToolManifestPath PursLanguageServer
Utils.readJsonFile path Nix.Manifest.pursLanguageServerManifestCodec

writePursLanguageServerManifest :: PursLanguageServerManifest -> AppM Unit
writePursLanguageServerManifest manifest = do
path <- getToolManifestPath PursLanguageServer
Utils.writeJsonFile path Nix.Manifest.pursLanguageServerManifestCodec manifest
19 changes: 18 additions & 1 deletion generate/bin/src/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ main = Aff.launchAff_ do
Run.verifySpago
Run.verifyPursTidy
Run.verifyPursBackendEs
Run.verifyPursLanguageServer

Prefetch dir -> do
let envFile = Path.concat [ dir, "..", "generate", ".env" ]
Expand Down Expand Up @@ -98,6 +99,12 @@ main = Aff.launchAff_ do
else
Console.log $ "New purs-backend-es releases: " <> Utils.printJson Nix.Manifest.pursBackendEsManifestCodec updates

Run.prefetchPursLanguageServer >>= \updates ->
if Map.isEmpty updates then
Console.log "No new purescript-language-server releases."
else
Console.log $ "New purescript-language-server releases: " <> Utils.printJson Nix.Manifest.pursLanguageServerManifestCodec updates

Update dir commit -> do
let envFile = Path.concat [ dir, "..", "generate", ".env" ]
Console.log $ "Loading .env file from " <> envFile
Expand All @@ -116,6 +123,7 @@ main = Aff.launchAff_ do
spagoUpdates <- Run.prefetchSpago
pursTidyUpdates <- Run.prefetchPursTidy
pursBackendEsUpdates <- Run.prefetchPursBackendEs
pursLanguageServerUpdates <- Run.prefetchPursLanguageServer

case commit of
NoCommit -> do
Expand Down Expand Up @@ -144,14 +152,20 @@ main = Aff.launchAff_ do
Console.log $ "New purs-backend-es releases, writing to disk..."
Run.writePursBackendEsUpdates pursBackendEsUpdates

if Map.isEmpty pursLanguageServerUpdates then
Console.log "No new purescript-language-server releases."
else do
Console.log $ "New purescript-language-server releases, writing to disk..."
Run.writePursLanguageServerUpdates pursLanguageServerUpdates

DoCommit -> do
Console.log "Cloning purescript-nix, opening a branch, committing, and opening a pull request..."
token <- Env.lookupRequired Env.githubToken

-- We switch the manifest dir from the user-provided input to the
-- cloned repo before we do anything else.
Reader.local (\env -> env { manifestDir = Path.concat [ env.tmpDir, "purescript-nix", "manifests" ] }) do
if Map.isEmpty pursUpdates && Map.isEmpty spagoUpdates && Map.isEmpty pursTidyUpdates && Map.isEmpty pursBackendEsUpdates then
if Map.isEmpty pursUpdates && Map.isEmpty spagoUpdates && Map.isEmpty pursTidyUpdates && Map.isEmpty pursBackendEsUpdates && Map.isEmpty pursLanguageServerUpdates then
Console.log "No new releases."
else do
Console.log "New releases, writing to disk..."
Expand All @@ -161,6 +175,7 @@ main = Aff.launchAff_ do
Run.writeSpagoUpdates spagoUpdates
Run.writePursTidyUpdates pursTidyUpdates
Run.writePursBackendEsUpdates pursBackendEsUpdates
Run.writePursLanguageServerUpdates pursLanguageServerUpdates

-- TODO: Commit message could be a lot more informative.
commitResult <- AppM.runGitM do
Expand All @@ -180,13 +195,15 @@ main = Aff.launchAff_ do
spagoVersions = Map.keys spagoUpdates
pursTidyVersions = Map.keys pursTidyUpdates
pursBackendEsVersions = Map.keys pursBackendEsUpdates
pursLanguageServerVersions = Map.keys pursLanguageServerUpdates

commitMsg = "Update " <> String.trim
( String.joinWith " "
[ guard (Set.size pursVersions > 0) $ "purs (" <> String.joinWith ", " (Set.toUnfoldable (Set.map SemVer.print pursVersions)) <> ")"
, guard (Set.size spagoVersions > 0) $ "spago (" <> String.joinWith ", " (Set.toUnfoldable (Set.map SemVer.print spagoVersions)) <> ")"
, guard (Set.size pursTidyVersions > 0) $ "purs-tidy (" <> String.joinWith ", " (Set.toUnfoldable (Set.map SemVer.print pursTidyVersions)) <> ")"
, guard (Set.size pursBackendEsVersions > 0) $ "purs-backend-es (" <> String.joinWith ", " (Set.toUnfoldable (Set.map SemVer.print pursBackendEsVersions)) <> ")"
, guard (Set.size pursLanguageServerVersions > 0) $ "purescript-language-server (" <> String.joinWith ", " (Set.toUnfoldable (Set.map SemVer.print pursLanguageServerVersions)) <> ")"
]
)

Expand Down
100 changes: 99 additions & 1 deletion generate/bin/src/Run.purs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import Lib.Foreign.Octokit (Release, ReleaseAsset)
import Lib.Foreign.Octokit as Octokit
import Lib.Git as Git
import Lib.GitHub as GitHub
import Lib.Nix.Manifest (FetchUrl, NamedManifest, PursManifest, PursTidyManifest, SpagoManifest, PursBackendEsManifest)
import Lib.Nix.Manifest (FetchUrl, NamedManifest, PursBackendEsManifest, PursManifest, PursTidyManifest, SpagoManifest, PursLanguageServerManifest)
import Lib.Nix.Prefetch as Nix.Prefetch
import Lib.Nix.System (NixSystem(..))
import Lib.Nix.System as NixSystem
Expand Down Expand Up @@ -63,6 +63,12 @@ verifyPursBackendEs = do
let entries = Map.size manifest
Console.log $ "Successfully parsed purs-backend-es.json with " <> Int.toStringAs Int.decimal entries <> " entries."

verifyPursLanguageServer :: AppM Unit
verifyPursLanguageServer = do
manifest <- AppM.readPursLanguageServerManifest
let entries = Map.size manifest
Console.log $ "Successfully parsed purescript-language-server.json with " <> Int.toStringAs Int.decimal entries <> " entries."

prefetchPurs :: AppM PursManifest
prefetchPurs = do
manifest <- AppM.readPursManifest
Expand Down Expand Up @@ -515,3 +521,95 @@ writePursBackendEsUpdates updates = do
case named' of
Nothing -> Console.log "Failed to update named manifest" *> liftEffect (Process.exit 1)
Just result -> AppM.writeNamedManifest result

prefetchPursLanguageServer :: AppM PursLanguageServerManifest
prefetchPursLanguageServer = do
manifest <- AppM.readPursLanguageServerManifest

let
existing :: Set SemVer
existing = Map.keys manifest

rawReleases <- AppM.runGitHubM (GitHub.listReleases PursLanguageServer) >>= case _ of
Left error -> do
Console.log $ "Failed to fetch releases for " <> Tool.print PursLanguageServer Tool.Executable
Console.log $ Octokit.printGitHubError error
liftEffect $ Process.exit 1
Right releases -> pure releases

Console.log $ "Retrieved " <> show (Array.length rawReleases) <> " releases for " <> Tool.print PursLanguageServer Tool.Executable

let
parsePursLanguageServerReleases :: Array Release -> Map SemVer String
parsePursLanguageServerReleases = Map.fromFoldable <<< Array.mapMaybe \release -> Either.hush do
version <- SemVer.parse $ fromMaybe release.tag $ String.stripPrefix (String.Pattern "v") release.tag
asset <- Either.note "No asset named 'purescript-language-server.js' in release." $ Array.find (\asset -> asset.name == "purescript-language-server.js") release.assets
pure $ Tuple version asset.downloadUrl

-- We only accept pre-bundled language server releases, ie. those after 0.15.5
isBeforeCutoff :: SemVer -> Boolean
isBeforeCutoff (SemVer { version }) = Version.major version == 0 && Version.minor version <= 15 && Version.patch version <= 5

supportedReleases :: Map SemVer String
supportedReleases = Map.filterKeys (not <<< isBeforeCutoff) $ parsePursLanguageServerReleases rawReleases

-- We only want to include releases that aren't already present in the
-- manifest file.
newReleases :: Map SemVer String
newReleases = Map.filterKeys (not <<< flip Set.member existing) supportedReleases

Console.log $ "Found " <> show (Map.size newReleases) <> " new releases for " <> Tool.print PursLanguageServer Tool.Executable

hashedReleases :: PursLanguageServerManifest <- forWithIndex newReleases \version downloadUrl -> do
Console.log $ "Processing release " <> SemVer.print version
Console.log $ "Fetching hashes for release asset download url " <> downloadUrl
Nix.Prefetch.nixPrefetchTarball downloadUrl >>= case _ of
Left error -> do
Console.log $ "Failed to hash release asset at url " <> downloadUrl <> ": " <> error
liftEffect $ Process.exit 1
Right hash -> pure { url: downloadUrl, hash }

pure hashedReleases

writePursLanguageServerUpdates :: PursLanguageServerManifest -> AppM Unit
writePursLanguageServerUpdates updates = do
manifest <- AppM.readPursLanguageServerManifest
let newManifest = Map.union manifest updates
AppM.writePursLanguageServerManifest newManifest

named <- AppM.readNamedManifest

let
named' :: Maybe NamedManifest
named' = do
let unstableChannel = ToolChannel { tool: PursLanguageServer, channel: Unstable }
let stableChannel = ToolChannel { tool: PursLanguageServer, channel: Stable }

ToolPackage { version: unstable } <- Map.lookup unstableChannel named
ToolPackage { version: stable } <- Map.lookup stableChannel named

let
allVersions = Map.keys newManifest
allStableVersions = allVersions

maxUnstable = Set.findMax allVersions
maxStable = Set.findMax allStableVersions

insertPackage :: ToolChannel -> SemVer -> NamedManifest -> NamedManifest
insertPackage channel version = Map.insert channel (ToolPackage { tool: PursLanguageServer, version })

updateUnstable :: NamedManifest -> NamedManifest
updateUnstable prev = case maxUnstable of
Just version | version > unstable -> insertPackage unstableChannel version prev
_ -> prev

updateStable :: NamedManifest -> NamedManifest
updateStable prev = case maxStable of
Just version | version > stable -> insertPackage stableChannel version prev
_ -> prev

pure (updateStable (updateUnstable named))

case named' of
Nothing -> Console.log "Failed to update named manifest" *> liftEffect (Process.exit 1)
Just result -> AppM.writeNamedManifest result
1 change: 1 addition & 0 deletions generate/lib/src/GitHub.purs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ toolRepo = case _ of
Spago -> { owner: "purescript", repo: "spago" }
PursTidy -> { owner: "natefaubion", repo: "purescript-tidy" }
PursBackendEs -> { owner: "aristanetworks", repo: "purescript-backend-optimizer" }
PursLanguageServer -> { owner: "nwolverson", repo: "purescript-language-server" }

listReleases :: Tool -> GitHubM (Array Release)
listReleases tool = do
Expand Down
13 changes: 11 additions & 2 deletions generate/lib/src/Nix/Manifest.purs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ filename = case _ of
Spago -> "spago.json"
PursTidy -> "purs-tidy.json"
PursBackendEs -> "purs-backend-es.json"
PursLanguageServer -> "purescript-language-server.json"

type NamedManifest = Map ToolChannel ToolPackage

Expand Down Expand Up @@ -67,15 +68,23 @@ pursTidyManifestCodec :: JsonCodec PursTidyManifest
pursTidyManifestCodec = do
let encodeKey = SemVer.print
let decodeKey = Either.hush <<< SemVer.parse
Registry.Codec.strMap "PursManifest" decodeKey encodeKey fetchUrlCodec
Registry.Codec.strMap "PursTidyManifest" decodeKey encodeKey fetchUrlCodec

type PursBackendEsManifest = Map SemVer FetchUrl

pursBackendEsManifestCodec :: JsonCodec PursBackendEsManifest
pursBackendEsManifestCodec = do
let encodeKey = SemVer.print
let decodeKey = Either.hush <<< SemVer.parse
Registry.Codec.strMap "PursManifest" decodeKey encodeKey fetchUrlCodec
Registry.Codec.strMap "PursBackendEsManifest" decodeKey encodeKey fetchUrlCodec

type PursLanguageServerManifest = Map SemVer FetchUrl

pursLanguageServerManifestCodec :: JsonCodec PursLanguageServerManifest
pursLanguageServerManifestCodec = do
let encodeKey = SemVer.print
let decodeKey = Either.hush <<< SemVer.parse
Registry.Codec.strMap "PursLanguageServerManifest" decodeKey encodeKey fetchUrlCodec

-- | A manifest entry for a package that can be fetched from git
type GitRev = { rev :: String }
Expand Down
15 changes: 10 additions & 5 deletions generate/lib/src/Tool.purs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ data Tool
| Spago
| PursTidy
| PursBackendEs
| PursLanguageServer

derive instance Eq Tool
derive instance Ord Tool
Expand Down Expand Up @@ -99,6 +100,7 @@ printExecutable = case _ of
Spago -> "spago"
PursTidy -> "purs-tidy"
PursBackendEs -> "purs-backend-es"
PursLanguageServer -> "purescript-language-server"

-- | Parse a tool from its executable name, ie. 'purs'
parseExecutable :: String -> Either String Tool
Expand All @@ -107,6 +109,7 @@ parseExecutable = case _ of
"spago" -> Right Spago
"purs-tidy" -> Right PursTidy
"purs-backend-es" -> Right PursBackendEs
"purescript-language-server" -> Right PursLanguageServer
other -> Left $ "Unknown tool: " <> other

-- | A tool and its channel, ie. 'purs-stable'
Expand Down Expand Up @@ -177,11 +180,13 @@ parseToolPrefix str = do
Just rest -> Right $ Tuple PursTidy (String.drop 1 rest)
Nothing -> case String.stripPrefix (String.Pattern (printExecutable PursBackendEs)) str of
Just rest -> Right $ Tuple PursBackendEs (String.drop 1 rest)
Nothing -> case String.stripPrefix (String.Pattern (printExecutable Purs)) str of
Just rest -> Right $ Tuple Purs (String.drop 1 rest)
Nothing -> case String.stripPrefix (String.Pattern (printExecutable Spago)) str of
Just rest -> Right $ Tuple Spago (String.drop 1 rest)
Nothing -> Left $ "Expected a tool name but got: " <> str
Nothing -> case String.stripPrefix (String.Pattern (printExecutable PursLanguageServer)) str of
Just rest -> Right $ Tuple PursLanguageServer (String.drop 1 rest)
Nothing -> case String.stripPrefix (String.Pattern (printExecutable Purs)) str of
Just rest -> Right $ Tuple Purs (String.drop 1 rest)
Nothing -> case String.stripPrefix (String.Pattern (printExecutable Spago)) str of
Just rest -> Right $ Tuple Spago (String.drop 1 rest)
Nothing -> Left $ "Expected a tool name but got: " <> str

-- | Parse a tool from its executable name and version as a package, ie.
-- | 'purs-0_14_4-0'
Expand Down
7 changes: 5 additions & 2 deletions generate/lib/test/Test/Lib/Manifest.purs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Data.Codec.Argonaut as CA
import Data.Either (Either(..))
import Data.String as String
import Data.Traversable (for)
import Lib.Nix.Manifest (NamedManifest, PursManifest, PursTidyManifest, SpagoManifest, PursBackendEsManifest, namedManifestCodec, pursBackendEsManifestCodec, pursManifestCodec, pursTidyManifestCodec, spagoManifestCodec)
import Lib.Nix.Manifest (NamedManifest, PursBackendEsManifest, PursLanguageServerManifest, PursManifest, PursTidyManifest, SpagoManifest, namedManifestCodec, pursBackendEsManifestCodec, pursLanguageServerManifestCodec, pursManifestCodec, pursTidyManifestCodec, spagoManifestCodec)
import Node.Encoding (Encoding(..))
import Node.FS.Aff as FS.Aff
import Node.Path as Path
Expand All @@ -33,6 +33,7 @@ data NixManifest
| PursManifest PursManifest
| PursTidyManifest PursTidyManifest
| PursBackendEsManifest PursBackendEsManifest
| PursLanguageServerManifest PursLanguageServerManifest
| NamedManifest NamedManifest

derive instance Eq NixManifest
Expand All @@ -45,12 +46,14 @@ nixManifestCodec = CA.codec' decode encode
PursManifest manifest -> CA.encode pursManifestCodec manifest
PursTidyManifest manifest -> CA.encode pursTidyManifestCodec manifest
PursBackendEsManifest manifest -> CA.encode pursBackendEsManifestCodec manifest
PursLanguageServerManifest manifest -> CA.encode pursLanguageServerManifestCodec manifest
NamedManifest manifest -> CA.encode namedManifestCodec manifest

decode json =
map SpagoManifest (CA.decode spagoManifestCodec json)
<|> map PursManifest (CA.decode pursManifestCodec json)
<|> map PursTidyManifest (CA.decode pursTidyManifestCodec json)
<|> map PursBackendEsManifest (CA.decode pursBackendEsManifestCodec json)
<|> map PursLanguageServerManifest (CA.decode pursLanguageServerManifestCodec json)
<|> map NamedManifest (CA.decode namedManifestCodec json)
<|> Left (CA.TypeMismatch "Expected a SpagoManifest, PursManifest, PursTidyManifest, PursBackendEsManifest, or NamedManifest")
<|> Left (CA.TypeMismatch "Expected a SpagoManifest, PursManifest, PursTidyManifest, PursBackendEsManifest, PursLanguageServerManifest, or NamedManifest")
Loading

0 comments on commit 401552c

Please sign in to comment.