From 5d8d2e8d257b2652d5394d43e3472497291acb75 Mon Sep 17 00:00:00 2001 From: JohnnyMorganz Date: Sun, 19 Jun 2022 17:50:31 +0100 Subject: [PATCH] Add support for registering definition files --- CHANGELOG.md | 2 ++ README.md | 4 ++- editors/code/package-lock.json | 17 ++++++++-- editors/code/package.json | 18 +++++++++-- editors/code/src/extension.ts | 50 ++++++++++++++++++++++++++---- src/Workspace.cpp | 23 ++++++++------ src/include/LSP/Client.hpp | 2 +- src/include/LSP/LanguageServer.hpp | 4 +-- src/main.cpp | 6 ++-- 9 files changed, 98 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ce8b54c..b3b00c31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Added predicate logic to `EnumItem:IsA("Type")` so it will now narrow the type of an EnumItem when used as a predicate - Added setting to configure whether all Luau FFlags are enabled by default. This can be configured using `luau-lsp.fflags.enableByDefault` or `--no-flags-enabled` command line option. Currently, all FFlags are enabled by default, but you can manually sync/override them. +- Added support for adding extra definition files to load using `luau-lsp.types.definitionFiles` +- Roblox definitions can now be disabled using `luau-lsp.types.roblox` ### Changed diff --git a/README.md b/README.md index 33207170..a944c19b 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,14 @@ Install the extension from the marketplace: https://marketplace.visualstudio.com The langauge server should be immediately usable for general Luau code after installation. String require support is provided for relative module paths, using `require("../module")`. +Type definitions can be provided by configuring `luau-lsp.types.definitionFiles`. + If there are specific features you require in the language server for your use case, feel free to open an issue. ### For Rojo Users Rojo instance tree and requiring support is provided by default, and the language server should be able to directly emulate Studio. -The extension will automatically populate the latest API types and documentation. +The extension will automatically populate the latest API types and documentation (which can be disabled by configuring `luau-lsp.types.roblox`). To resolve your instance tree and provide module resolution, the language server uses Rojo sourcemaps. The language server will automatically create a `sourcemap.json` in your workspace root on startup and whenever files are added/created/renamed. diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index e5faefbd..898a4841 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -1,16 +1,17 @@ { "name": "luau-lsp", - "version": "0.2.0", + "version": "1.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "luau-lsp", - "version": "0.2.0", + "version": "1.4.0", "license": "MIT", "dependencies": { "node-fetch": "^3.2.4", - "vscode-languageclient": "^7.0.0" + "vscode-languageclient": "^7.0.0", + "vscode-uri": "^3.0.3" }, "devDependencies": { "@types/glob": "^7.2.0", @@ -3461,6 +3462,11 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" }, + "node_modules/vscode-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.3.tgz", + "integrity": "sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==" + }, "node_modules/watchpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", @@ -6261,6 +6267,11 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" }, + "vscode-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.3.tgz", + "integrity": "sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==" + }, "watchpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", diff --git a/editors/code/package.json b/editors/code/package.json index 825aea06..0abdfbad 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -122,6 +122,19 @@ "markdownDescription": "Compute diagnostics for the whole workspace", "type": "boolean", "default": false + }, + "luau-lsp.types.definitionFiles": { + "markdownDescription": "A list of paths to definition files to load in to the type checker. Note that definition file syntax is currently unstable and may change at any time", + "type": "array", + "default": [], + "items": { + "type": "string" + } + }, + "luau-lsp.types.roblox": { + "markdownDescription": "Load in and automatically update Roblox type definitions for the type checker", + "type": "boolean", + "default": true } } } @@ -157,6 +170,7 @@ }, "dependencies": { "node-fetch": "^3.2.4", - "vscode-languageclient": "^7.0.0" + "vscode-languageclient": "^7.0.0", + "vscode-uri": "^3.0.3" } -} \ No newline at end of file +} diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index d2c4c4c6..71b83cb0 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -8,6 +8,7 @@ import { LanguageClientOptions, } from "vscode-languageclient/node"; import { spawn } from "child_process"; +import { Utils as UriUtils } from "vscode-uri"; const CURRENT_VERSION_TXT = "https://raw.githubusercontent.com/CloneTrooper1019/Roblox-Client-Tracker/roblox/version.txt"; @@ -118,12 +119,38 @@ const getFFlags = async () => { export async function activate(context: vscode.ExtensionContext) { console.log("Luau LSP activated"); - await updateApiInfo(context); - const args = [ - `lsp`, - `--definitions=${globalTypesUri(context).fsPath}`, - `--docs=${apiDocsUri(context).fsPath}`, - ]; + const args = ["lsp"]; + + // Load roblox type definitions + const typesConfig = vscode.workspace.getConfiguration("luau-lsp.types"); + if (typesConfig.get("roblox")) { + await updateApiInfo(context); + args.push(`--definitions=${globalTypesUri(context).fsPath}`); + args.push(`--docs=${apiDocsUri(context).fsPath}`); + } + + // Load extra type definitions + const definitionFiles = typesConfig.get("definitionFiles"); + if (definitionFiles) { + for (const definitionPath of definitionFiles) { + let uri; + if (vscode.workspace.workspaceFolders) { + uri = UriUtils.resolvePath( + vscode.workspace.workspaceFolders[0].uri, + definitionPath + ); + } else { + uri = vscode.Uri.file(definitionPath); + } + if (await exists(uri)) { + args.push(`--definitions=${uri.fsPath}`); + } else { + vscode.window.showWarningMessage( + `Definitions file at ${definitionPath} does not exist, types will not be provided from this file` + ); + } + } + } // Handle FFlags const fflags: FFlags = {}; @@ -314,6 +341,17 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.executeCommand("workbench.action.reloadWindow"); } }); + } else if (e.affectsConfiguration("luau-lsp.types")) { + vscode.window + .showInformationMessage( + "Luau type definitions have been changed, reload your workspace for this to take effect.", + "Reload Workspace" + ) + .then((command) => { + if (command === "Reload Workspace") { + vscode.commands.executeCommand("workbench.action.reloadWindow"); + } + }); } }) ); diff --git a/src/Workspace.cpp b/src/Workspace.cpp index c9017787..44dca629 100644 --- a/src/Workspace.cpp +++ b/src/Workspace.cpp @@ -1037,26 +1037,29 @@ void WorkspaceFolder::setup() Luau::registerBuiltinTypes(frontend.typeChecker); Luau::registerBuiltinTypes(frontend.typeCheckerForAutocomplete); - if (client->definitionsFile) + if (client->definitionsFiles.empty()) { - client->sendLogMessage(lsp::MessageType::Info, "Loading definitions file: " + client->definitionsFile->generic_string()); - auto result = types::registerDefinitions(frontend.typeChecker, *client->definitionsFile); - types::registerDefinitions(frontend.typeCheckerForAutocomplete, *client->definitionsFile); + // TODO: should we disable this warning - maybe its sometimes not necessary if its vanilla Luau? + client->sendLogMessage(lsp::MessageType::Error, "Definitions file was not provided by the client. Extended types will not be provided"); + client->sendWindowMessage(lsp::MessageType::Error, "Definitions file was not provided by the client. Extended types will not be provided"); + } + + for (auto definitionsFile : client->definitionsFiles) + { + client->sendLogMessage(lsp::MessageType::Info, "Loading definitions file: " + definitionsFile.generic_string()); + auto result = types::registerDefinitions(frontend.typeChecker, definitionsFile); + types::registerDefinitions(frontend.typeCheckerForAutocomplete, definitionsFile); if (!result.success) { client->sendWindowMessage(lsp::MessageType::Error, "Failed to read definitions file. Extended types will not be provided"); - // TODO: Display diagnostics? + // TODO: Display diagnostics? We can't right now since this is currently called during initialisation, need to move } } - else - { - client->sendLogMessage(lsp::MessageType::Error, "Definitions file was not provided by the client. Extended types will not be provided"); - client->sendWindowMessage(lsp::MessageType::Error, "Definitions file was not provided by the client. Extended types will not be provided"); - } Luau::freeze(frontend.typeChecker.globalTypes); Luau::freeze(frontend.typeCheckerForAutocomplete.globalTypes); + // TODO: disable sourcemap lookup if not Roblox related if (!isNullWorkspace() && !updateSourceMap()) { client->sendWindowMessage( diff --git a/src/include/LSP/Client.hpp b/src/include/LSP/Client.hpp index bc066cf7..7e4945b6 100644 --- a/src/include/LSP/Client.hpp +++ b/src/include/LSP/Client.hpp @@ -16,7 +16,7 @@ class Client lsp::ClientCapabilities capabilities; lsp::TraceValue traceMode = lsp::TraceValue::Off; /// A registered definitions file passed by the client - std::optional definitionsFile = std::nullopt; + std::vector definitionsFiles; /// A registered documentation file passed by the client std::optional documentationFile = std::nullopt; /// Parsed documentation database diff --git a/src/include/LSP/LanguageServer.hpp b/src/include/LSP/LanguageServer.hpp index be48cba2..4076132b 100644 --- a/src/include/LSP/LanguageServer.hpp +++ b/src/include/LSP/LanguageServer.hpp @@ -24,10 +24,10 @@ class LanguageServer std::vector workspaceFolders; ClientPtr client; - LanguageServer(std::optional definitionsFile, std::optional documentationFile) + LanguageServer(std::vector definitionsFiles, std::optional documentationFile) : client(std::make_shared()) { - client->definitionsFile = definitionsFile; + client->definitionsFiles = definitionsFiles; client->documentationFile = documentationFile; parseDocumentation(documentationFile, client->documentation, client); nullWorkspace = std::make_shared(client, "$NULL_WORKSPACE", Uri()); diff --git a/src/main.cpp b/src/main.cpp index beefe99a..c20c5000 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -120,13 +120,13 @@ int startLanguageServer(int argc, char** argv) _setmode(_fileno(stdout), _O_BINARY); #endif - std::optional definitionsFile; + std::vector definitionsFiles; std::optional documentationFile; for (int i = 1; i < argc; i++) { if (strncmp(argv[i], "--definitions=", 14) == 0) { - definitionsFile = std::filesystem::path(argv[i] + 14); + definitionsFiles.emplace_back(argv[i] + 14); } else if (strncmp(argv[i], "--docs=", 7) == 0) { @@ -134,7 +134,7 @@ int startLanguageServer(int argc, char** argv) } } - LanguageServer server(definitionsFile, documentationFile); + LanguageServer server(definitionsFiles, documentationFile); // Begin input loop server.processInputLoop();