mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80fefd2ceb | ||
|
|
9750f8dfc6 | ||
|
|
cca25099c4 | ||
|
|
6456a789af | ||
|
|
1ff626ba47 | ||
|
|
c07fe37f30 | ||
|
|
17af8e20c9 | ||
|
|
f55d36e86c | ||
|
|
df445b8390 | ||
|
|
6b009ea123 | ||
|
|
2e6eedb196 | ||
|
|
d9d834b165 | ||
|
|
0fc399bbc3 | ||
|
|
56a7871d06 | ||
|
|
019d87e5c7 | ||
|
|
a9508d965d | ||
|
|
24e72ecc36 | ||
|
|
ecaf1db977 | ||
|
|
5cef963162 | ||
|
|
e89cb784da |
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -18,7 +18,7 @@ body:
|
||||
description: >
|
||||
Specify the Git commit hash if using a development or non-official build.
|
||||
If you use a custom build, please test if your issue is reproducible in official builds too.
|
||||
placeholder: 3.3.stable, 4.0.dev (3041becc6)
|
||||
placeholder: 4.2.2.stable, 4.3.rc (88d932506)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -29,7 +29,7 @@ body:
|
||||
Use the **Help > About** menu to see your current version.
|
||||
Specify the Git commit hash if using a development or non-official build.
|
||||
If you use a custom build, please test if your issue is reproducible in official builds too.
|
||||
placeholder: "1.64.2"
|
||||
placeholder: "1.91.1"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -40,7 +40,7 @@ body:
|
||||
Open the **Extensions** side panel and click on the **godot-tools** extension to see your current version.
|
||||
Specify the Git commit hash if using a development or non-official build.
|
||||
If you use a custom build, please test if your issue is reproducible in official builds too.
|
||||
placeholder: "2.0.0"
|
||||
placeholder: "2.1.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
6
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -18,7 +18,7 @@ body:
|
||||
description: >
|
||||
Specify the Git commit hash if using a development or non-official build.
|
||||
If you use a custom build, please test if your issue is reproducible in official builds too.
|
||||
placeholder: 3.3.stable, 4.0.dev (3041becc6)
|
||||
placeholder: 4.2.2.stable, 4.3.rc (88d932506)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -29,7 +29,7 @@ body:
|
||||
Use the **Help > About** menu to see your current version.
|
||||
Specify the Git commit hash if using a development or non-official build.
|
||||
If you use a custom build, please test if your issue is reproducible in official builds too.
|
||||
placeholder: "1.64.2"
|
||||
placeholder: "1.91.1"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -40,7 +40,7 @@ body:
|
||||
Open the **Extensions** side panel and click on the **godot-tools** extension to see your current version.
|
||||
Specify the Git commit hash if using a development or non-official build.
|
||||
If you use a custom build, please test if your issue is reproducible in official builds too.
|
||||
placeholder: "2.0.0"
|
||||
placeholder: "2.1.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
ls -l godot-tools.vsix
|
||||
|
||||
- name: Upload extension VSIX
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
with:
|
||||
name: godot-tools
|
||||
path: godot-tools.vsix
|
||||
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
### 2.1.0
|
||||
|
||||
- [Improve dragging items from Scene Preview into source code](https://github.com/godotengine/godot-vscode-plugin/pull/661)
|
||||
- [Improve macOS path resolution for app bundles](https://github.com/godotengine/godot-vscode-plugin/pull/632)
|
||||
- [Improve codeblock formatting in documentation](https://github.com/godotengine/godot-vscode-plugin/pull/629)
|
||||
- [Improve Scene Preview ergonomics](https://github.com/godotengine/godot-vscode-plugin/pull/665)
|
||||
- "Pinning" in the scene preview is now referred to as "locking" to avoid confusion with pinning a scene as the debug/launch target.
|
||||
- Added commands for opening the Scene Preview's target scene, and the "main script" of the target scene, if it exists.
|
||||
- Added existing "refresh scene preview" command as a button.
|
||||
- [Prevent document links from accidentally being resolved to your entire document](https://github.com/godotengine/godot-vscode-plugin/pull/639)
|
||||
- [Fix poor documentation formatting of class titles and inheritance chain](https://github.com/godotengine/godot-vscode-plugin/pull/628)
|
||||
- [Fix bad formatting on several operators](https://github.com/godotengine/godot-vscode-plugin/pull/605)
|
||||
- [Fix various formatting issues](https://github.com/godotengine/godot-vscode-plugin/pull/672)
|
||||
- [Fix various syntax highlighting issues](https://github.com/godotengine/godot-vscode-plugin/pull/674)
|
||||
- [Fix Object ID decoded as wrong signedness](https://github.com/godotengine/godot-vscode-plugin/pull/670)
|
||||
- [Fix project not found when `project.godot` file is excluded](https://github.com/godotengine/godot-vscode-plugin/pull/635)
|
||||
- [Fix LSP connection attempts not resetting](https://github.com/godotengine/godot-vscode-plugin/pull/638)
|
||||
- [Fix child processes not being killed properly](https://github.com/godotengine/godot-vscode-plugin/pull/613)
|
||||
- [Fix broken scene file parser](https://github.com/godotengine/godot-vscode-plugin/pull/603)
|
||||
- [Fix debugged process not being terminated when debugging session closes on Linux](https://github.com/godotengine/godot-vscode-plugin/pull/620)
|
||||
|
||||
### 2.0.0
|
||||
|
||||
- [**Rewrite debugger for Godot 4 support + improved maintainability**](https://github.com/godotengine/godot-vscode-plugin/pull/452)
|
||||
|
||||
15
biome.json
Normal file
15
biome.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 120,
|
||||
"lineEnding": "lf",
|
||||
"include": ["src/**/*.ts"]
|
||||
},
|
||||
"files": {
|
||||
"include": ["src/**/*.ts"],
|
||||
"ignore": ["node_modules"]
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,3 @@
|
||||
.codeblock {
|
||||
padding: 0.5em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
!background-color: #fdf6e3;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-right: 200px;
|
||||
}
|
||||
@@ -22,3 +14,34 @@ a {
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
pre:has(code) {
|
||||
position: relative;
|
||||
background-color: var(--vscode-textPreformat-background);
|
||||
padding: 2.5em 1.5em 1em 1.5em;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background-color: transparent !important;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
pre:has(code[class*="language-"])::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0.5em 1em;
|
||||
color: var(--vscode-titlebar-activeForeground);
|
||||
font-family: var(--vscode-font-family);
|
||||
}
|
||||
|
||||
pre:has(code.language-gdscript)::before {
|
||||
content: "GDScript";
|
||||
}
|
||||
|
||||
pre:has(code.language-csharp)::before {
|
||||
content: "C#";
|
||||
}
|
||||
|
||||
972
package-lock.json
generated
972
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
105
package.json
105
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "godot-tools",
|
||||
"displayName": "godot-tools",
|
||||
"icon": "icon.png",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"description": "Tools for game development with Godot Engine and GDScript",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -149,19 +149,47 @@
|
||||
{
|
||||
"category": "Godot Tools",
|
||||
"command": "godotTools.scenePreview.refresh",
|
||||
"title": "Refresh Scene Preview"
|
||||
"title": "Refresh Scene Preview",
|
||||
"icon": {
|
||||
"light": "resources/godot_icons/light/Reload.svg",
|
||||
"dark": "resources/godot_icons/dark/Reload.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": "Godot Tools",
|
||||
"command": "godotTools.scenePreview.pin",
|
||||
"title": "Pin Scene Preview",
|
||||
"icon": "resources/pin_off.svg"
|
||||
"command": "godotTools.scenePreview.openCurrentScene",
|
||||
"title": "Open the Scene Preview's current scene",
|
||||
"icon": {
|
||||
"light": "resources/godot_icons/light/PackedScene.svg",
|
||||
"dark": "resources/godot_icons/dark/PackedScene.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": "Godot Tools",
|
||||
"command": "godotTools.scenePreview.unpin",
|
||||
"title": "Unpin Scene Preview",
|
||||
"icon": "resources/pin_on.svg"
|
||||
"command": "godotTools.scenePreview.openMainScript",
|
||||
"title": "Open the main script of the Scene Preview's current scene",
|
||||
"icon": {
|
||||
"light": "resources/godot_icons/light/Script.svg",
|
||||
"dark": "resources/godot_icons/dark/Script.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": "Godot Tools",
|
||||
"command": "godotTools.scenePreview.lock",
|
||||
"title": "Lock Scene Preview",
|
||||
"icon": {
|
||||
"light": "resources/godot_icons/light/Unlock.svg",
|
||||
"dark": "resources/godot_icons/dark/Unlock.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": "Godot Tools",
|
||||
"command": "godotTools.scenePreview.unlock",
|
||||
"title": "Unlock Scene Preview",
|
||||
"icon": {
|
||||
"light": "resources/godot_icons/light/Lock.svg",
|
||||
"dark": "resources/godot_icons/dark/Lock.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": "Godot Tools",
|
||||
@@ -243,6 +271,24 @@
|
||||
"default": true,
|
||||
"description": "Whether to reveal the terminal when launching the Godot Editor"
|
||||
},
|
||||
"godotTools.formatter.maxEmptyLines": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"1 empty line. A more compact style.",
|
||||
"2 empty lines. Conforms to the official GDScript style guide."
|
||||
],
|
||||
"default": "2",
|
||||
"description": "Number of empty lines allowed anywhere in the file"
|
||||
},
|
||||
"godotTools.formatter.denseFunctionParameters": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether extra space should be removed from function parameter lists"
|
||||
},
|
||||
"godotTools.lsp.serverProtocol": {
|
||||
"type": [
|
||||
"string"
|
||||
@@ -628,10 +674,6 @@
|
||||
"command": "godotTools.listGodotClasses",
|
||||
"when": "godotTools.context.connectedToLSP"
|
||||
},
|
||||
{
|
||||
"command": "godotTools.scenePreview.refresh",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "godotTools.scenePreview.goToDefinition",
|
||||
"when": "false"
|
||||
@@ -640,14 +682,6 @@
|
||||
"command": "godotTools.scenePreview.openDocumentation",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "godotTools.scenePreview.pin",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "godotTools.scenePreview.unpin",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "godotTools.scenePreview.copyNodePath",
|
||||
"when": "false"
|
||||
@@ -693,14 +727,29 @@
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "godotTools.scenePreview.pin",
|
||||
"when": "view == scenePreview && !godotTools.context.scenePreview.pinned",
|
||||
"group": "navigation"
|
||||
"command": "godotTools.scenePreview.lock",
|
||||
"when": "view == scenePreview && !godotTools.context.scenePreview.locked",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "godotTools.scenePreview.unpin",
|
||||
"when": "view == scenePreview && godotTools.context.scenePreview.pinned",
|
||||
"group": "navigation"
|
||||
"command": "godotTools.scenePreview.unlock",
|
||||
"when": "view == scenePreview && godotTools.context.scenePreview.locked",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "godotTools.scenePreview.refresh",
|
||||
"when": "view == scenePreview",
|
||||
"group": "navigation@2"
|
||||
},
|
||||
{
|
||||
"command": "godotTools.scenePreview.openMainScript",
|
||||
"when": "view == scenePreview",
|
||||
"group": "navigation@3"
|
||||
},
|
||||
{
|
||||
"command": "godotTools.scenePreview.openCurrentScene",
|
||||
"when": "view == scenePreview",
|
||||
"group": "navigation@4"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
@@ -836,7 +885,7 @@
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"@vscode/test-cli": "^0.0.4",
|
||||
"@vscode/test-electron": "^2.3.8",
|
||||
"@vscode/vsce": "^2.21.0",
|
||||
"@vscode/vsce": "^2.29.0",
|
||||
"chai": "^4.3.10",
|
||||
"esbuild": "^0.17.15",
|
||||
"eslint": "^8.37.0",
|
||||
@@ -858,7 +907,7 @@
|
||||
"vscode-languageclient": "^7.0.0",
|
||||
"vscode-oniguruma": "^2.0.1",
|
||||
"vscode-textmate": "^9.0.0",
|
||||
"ws": "^8.13.0",
|
||||
"ws": "^8.17.1",
|
||||
"ya-bbcode": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,12 +107,11 @@ export class ServerController {
|
||||
if (args.editor_path) {
|
||||
log.info("Using 'editor_path' variable from launch.json");
|
||||
|
||||
godotPath = args.editor_path.replace(/^"/, "").replace(/"$/, "");
|
||||
|
||||
log.info(`Verifying version of '${godotPath}'`);
|
||||
result = verify_godot_version(godotPath, "3");
|
||||
|
||||
log.info(`Verifying version of '${args.editor_path}'`);
|
||||
result = verify_godot_version(args.editor_path, "3");
|
||||
godotPath = result.godotPath;
|
||||
log.info(`Verification result: ${result.status}, version: "${result.version}"`);
|
||||
|
||||
switch (result.status) {
|
||||
case "WRONG_VERSION": {
|
||||
const projectVersion = await get_project_version();
|
||||
@@ -129,16 +128,19 @@ export class ServerController {
|
||||
this.abort();
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info("Using 'editorPath.godot3' from settings");
|
||||
|
||||
const settingName = "editorPath.godot3";
|
||||
godotPath = get_configuration(settingName).replace(/^"/, "").replace(/"$/, "");
|
||||
godotPath = get_configuration(settingName);
|
||||
|
||||
log.info(`Verifying version of '${godotPath}'`);
|
||||
result = verify_godot_version(godotPath, "3");
|
||||
|
||||
godotPath = result.godotPath;
|
||||
log.info(`Verification result: ${result.status}, version: "${result.version}"`);
|
||||
|
||||
switch (result.status) {
|
||||
@@ -221,7 +223,7 @@ export class ServerController {
|
||||
}
|
||||
|
||||
log.info(`Launching game process using command: '${command}'`);
|
||||
const debugProcess = subProcess("debug", command, { shell: true });
|
||||
const debugProcess = subProcess("debug", command, { shell: true, detached: true });
|
||||
|
||||
debugProcess.stdout.on("data", (data) => { });
|
||||
debugProcess.stderr.on("data", (data) => { });
|
||||
@@ -375,10 +377,16 @@ export class ServerController {
|
||||
break;
|
||||
}
|
||||
case "message:inspect_object": {
|
||||
const id = BigInt(command.parameters[0]);
|
||||
let id = BigInt(command.parameters[0]);
|
||||
const className: string = command.parameters[1];
|
||||
const properties: any[] = command.parameters[2];
|
||||
|
||||
// message:inspect_object returns the id as an unsigned 64 bit integer, but it is decoded as a signed 64 bit integer,
|
||||
// thus we need to convert it to its equivalent unsigned value here.
|
||||
if (id < 0) {
|
||||
id = id + BigInt(2) ** BigInt(64);
|
||||
}
|
||||
|
||||
const rawObject = new RawObject(className);
|
||||
properties.forEach((prop) => {
|
||||
rawObject.set(prop[0], prop[5]);
|
||||
|
||||
@@ -108,12 +108,11 @@ export class ServerController {
|
||||
if (args.editor_path) {
|
||||
log.info("Using 'editor_path' variable from launch.json");
|
||||
|
||||
godotPath = args.editor_path.replace(/^"/, "").replace(/"$/, "");
|
||||
|
||||
log.info(`Verifying version of '${godotPath}'`);
|
||||
result = verify_godot_version(godotPath, "4");
|
||||
|
||||
log.info(`Verifying version of '${args.editor_path}'`);
|
||||
result = verify_godot_version(args.editor_path, "4");
|
||||
godotPath = result.godotPath;
|
||||
log.info(`Verification result: ${result.status}, version: "${result.version}"`);
|
||||
|
||||
switch (result.status) {
|
||||
case "WRONG_VERSION": {
|
||||
const projectVersion = await get_project_version();
|
||||
@@ -130,16 +129,19 @@ export class ServerController {
|
||||
this.abort();
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info("Using 'editorPath.godot4' from settings");
|
||||
|
||||
const settingName = "editorPath.godot4";
|
||||
godotPath = get_configuration(settingName).replace(/^"/, "").replace(/"$/, "");
|
||||
godotPath = get_configuration(settingName);
|
||||
|
||||
log.info(`Verifying version of '${godotPath}'`);
|
||||
result = verify_godot_version(godotPath, "4");
|
||||
|
||||
godotPath = result.godotPath;
|
||||
log.info(`Verification result: ${result.status}, version: "${result.version}"`);
|
||||
|
||||
switch (result.status) {
|
||||
@@ -227,7 +229,7 @@ export class ServerController {
|
||||
}
|
||||
|
||||
log.info(`Launching game process using command: '${command}'`);
|
||||
const debugProcess = subProcess("debug", command, { shell: true });
|
||||
const debugProcess = subProcess("debug", command, { shell: true, detached: true });
|
||||
|
||||
debugProcess.stdout.on("data", (data) => { });
|
||||
debugProcess.stderr.on("data", (data) => { });
|
||||
@@ -374,10 +376,16 @@ export class ServerController {
|
||||
break;
|
||||
}
|
||||
case "scene:inspect_object": {
|
||||
const id = BigInt(command.parameters[0]);
|
||||
let id = BigInt(command.parameters[0]);
|
||||
const className: string = command.parameters[1];
|
||||
const properties: any[] = command.parameters[2];
|
||||
|
||||
// message:inspect_object returns the id as an unsigned 64 bit integer, but it is decoded as a signed 64 bit integer,
|
||||
// thus we need to convert it to its equivalent unsigned value here.
|
||||
if (id < 0) {
|
||||
id = id + BigInt(2) ** BigInt(64);
|
||||
}
|
||||
|
||||
const rawObject = new RawObject(className);
|
||||
properties.forEach((prop) => {
|
||||
rawObject.set(prop[0], prop[5]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from "path";
|
||||
import * as path from "node:path";
|
||||
import * as vscode from "vscode";
|
||||
import { attemptSettingsUpdate, get_extension_uri } from "./utils";
|
||||
import { attemptSettingsUpdate, get_extension_uri, clean_godot_path } from "./utils";
|
||||
import {
|
||||
GDInlayHintsProvider,
|
||||
GDHoverProvider,
|
||||
@@ -81,9 +81,13 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
async function initial_setup() {
|
||||
const projectVersion = await get_project_version();
|
||||
if (projectVersion === undefined) {
|
||||
// TODO: actually handle this?
|
||||
return;
|
||||
}
|
||||
const settingName = `editorPath.godot${projectVersion[0]}`;
|
||||
const godotPath = get_configuration(settingName);
|
||||
const result = verify_godot_version(godotPath, projectVersion[0]);
|
||||
const result = verify_godot_version(get_configuration(settingName), projectVersion[0]);
|
||||
const godotPath = result.godotPath;
|
||||
|
||||
switch (result.status) {
|
||||
case "SUCCESS": {
|
||||
@@ -153,8 +157,8 @@ async function open_workspace_with_editor() {
|
||||
const projectVersion = await get_project_version();
|
||||
|
||||
const settingName = `editorPath.godot${projectVersion[0]}`;
|
||||
const godotPath = get_configuration(settingName).replace(/^"/, "").replace(/"$/, "");
|
||||
const result = verify_godot_version(godotPath, projectVersion[0]);
|
||||
const result = verify_godot_version(get_configuration(settingName), projectVersion[0]);
|
||||
const godotPath = result.godotPath;
|
||||
|
||||
switch (result.status) {
|
||||
case "SUCCESS": {
|
||||
@@ -204,9 +208,7 @@ async function get_godot_path(): Promise<string|undefined> {
|
||||
return undefined;
|
||||
}
|
||||
const settingName = `editorPath.godot${projectVersion[0]}`;
|
||||
// Cleans up any surrounding quotes the user might put into the path.
|
||||
const godotPath : string = get_configuration(settingName).replace(/^"/, "").replace(/"$/, "");
|
||||
return godotPath;
|
||||
return clean_godot_path(get_configuration(settingName));
|
||||
}
|
||||
|
||||
class GodotEditorTerminal implements vscode.Pseudoterminal {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as vscode from "vscode";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import * as path from "node:path";
|
||||
import * as fs from "node:fs";
|
||||
|
||||
import { format_document } from "./textmate";
|
||||
import { format_document, type FormatterOptions } from "./textmate";
|
||||
|
||||
import * as chai from "chai";
|
||||
const expect = chai.expect;
|
||||
@@ -10,6 +10,20 @@ const expect = chai.expect;
|
||||
const dots = ["..", "..", ".."];
|
||||
const basePath = path.join(__filename, ...dots);
|
||||
|
||||
function get_options(testFolderPath: string) {
|
||||
const options: FormatterOptions = {
|
||||
maxEmptyLines: 2,
|
||||
denseFunctionParameters: false,
|
||||
};
|
||||
const optionsPath = path.join(testFolderPath, "config.json");
|
||||
if (fs.existsSync(optionsPath)) {
|
||||
const file = fs.readFileSync(optionsPath).toString();
|
||||
const config = JSON.parse(file);
|
||||
return { ...options, ...config } as FormatterOptions;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
suite("GDScript Formatter Tests", () => {
|
||||
// Search for all folders in the snapshots folder and run a test for each
|
||||
// comparing the output of the formatter with the expected output.
|
||||
@@ -18,15 +32,19 @@ suite("GDScript Formatter Tests", () => {
|
||||
const snapshotsFolderPath = path.join(basePath, "src/formatter/snapshots");
|
||||
const testFolders = fs.readdirSync(snapshotsFolderPath);
|
||||
|
||||
// biome-ignore lint/complexity/noForEach: <explanation>
|
||||
testFolders.forEach((testFolder) => {
|
||||
const testFolderPath = path.join(snapshotsFolderPath, testFolder);
|
||||
if (fs.statSync(testFolderPath).isDirectory()) {
|
||||
test(`Snapshot Test: ${testFolder}`, async () => {
|
||||
const uriIn = vscode.Uri.file(path.join(testFolderPath, "in.gd"));
|
||||
const uriOut = vscode.Uri.file(path.join(testFolderPath, "out.gd"));
|
||||
|
||||
const documentIn = await vscode.workspace.openTextDocument(uriIn);
|
||||
const documentOut = await vscode.workspace.openTextDocument(uriOut);
|
||||
const edits = format_document(documentIn);
|
||||
|
||||
const options = get_options(testFolderPath);
|
||||
const edits = format_document(documentIn, options);
|
||||
|
||||
// Apply the formatting edits
|
||||
const workspaceEdit = new vscode.WorkspaceEdit();
|
||||
@@ -34,7 +52,9 @@ suite("GDScript Formatter Tests", () => {
|
||||
await vscode.workspace.applyEdit(workspaceEdit);
|
||||
|
||||
// Compare the result with the expected output
|
||||
expect(documentIn.getText().replace("\r\n", "\n")).to.equal(documentOut.getText().replace("\r\n", "\n"));
|
||||
expect(documentIn.getText().replace("\r\n", "\n")).to.equal(
|
||||
documentOut.getText().replace("\r\n", "\n"),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
0
src/formatter/snapshot_template/in.gd
Normal file
0
src/formatter/snapshot_template/in.gd
Normal file
0
src/formatter/snapshot_template/out.gd
Normal file
0
src/formatter/snapshot_template/out.gd
Normal file
@@ -26,6 +26,9 @@ func f():
|
||||
a= 1.0* .2
|
||||
a =1.0 * 2.
|
||||
|
||||
a = 10**10
|
||||
a = min(10, 10**10)
|
||||
|
||||
a= a% b
|
||||
a =1%2
|
||||
|
||||
@@ -36,3 +39,16 @@ func f():
|
||||
|
||||
var v = Vector2( 1 , - 1 )
|
||||
var w = Vector2(1,10-1)
|
||||
|
||||
print( - 1 )
|
||||
print( 1 - 1 )
|
||||
|
||||
print( - 1 + (1-1))
|
||||
print( - 1 + (-1-1))
|
||||
|
||||
if a > - 1:
|
||||
if a < - 1:
|
||||
if a == - 1:
|
||||
pass
|
||||
|
||||
return - 1
|
||||
|
||||
@@ -26,6 +26,9 @@ func f():
|
||||
a = 1.0 * .2
|
||||
a = 1.0 * 2.
|
||||
|
||||
a = 10 ** 10
|
||||
a = min(10, 10 ** 10)
|
||||
|
||||
a = a % b
|
||||
a = 1 % 2
|
||||
|
||||
@@ -36,3 +39,16 @@ func f():
|
||||
|
||||
var v = Vector2(1, -1)
|
||||
var w = Vector2(1, 10 - 1)
|
||||
|
||||
print(-1)
|
||||
print(1 - 1)
|
||||
|
||||
print(-1 + (1 - 1))
|
||||
print(-1 + (-1 - 1))
|
||||
|
||||
if a > -1:
|
||||
if a < -1:
|
||||
if a == -1:
|
||||
pass
|
||||
|
||||
return -1
|
||||
|
||||
15
src/formatter/snapshots/assignment-operators/in.gd
Normal file
15
src/formatter/snapshots/assignment-operators/in.gd
Normal file
@@ -0,0 +1,15 @@
|
||||
func f():
|
||||
# arithmetic
|
||||
x += 1
|
||||
x -= 1
|
||||
x *= 1
|
||||
x /= 1
|
||||
x %= 1
|
||||
|
||||
# bitwise
|
||||
x |= 1
|
||||
x &= 1
|
||||
x ~= 1
|
||||
x /= 1
|
||||
x >>= 1
|
||||
x <<= 1
|
||||
15
src/formatter/snapshots/assignment-operators/out.gd
Normal file
15
src/formatter/snapshots/assignment-operators/out.gd
Normal file
@@ -0,0 +1,15 @@
|
||||
func f():
|
||||
# arithmetic
|
||||
x += 1
|
||||
x -= 1
|
||||
x *= 1
|
||||
x /= 1
|
||||
x %= 1
|
||||
|
||||
# bitwise
|
||||
x |= 1
|
||||
x &= 1
|
||||
x ~= 1
|
||||
x /= 1
|
||||
x >>= 1
|
||||
x <<= 1
|
||||
5
src/formatter/snapshots/bitwise-operators/in.gd
Normal file
5
src/formatter/snapshots/bitwise-operators/in.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
func f():
|
||||
collision_mask = 1 << 1 | 1 << 3
|
||||
collision_mask = 1 << 1 & 1 << 3
|
||||
collision_mask = ~1
|
||||
collision_mask = 1 ^ ~ 1
|
||||
5
src/formatter/snapshots/bitwise-operators/out.gd
Normal file
5
src/formatter/snapshots/bitwise-operators/out.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
func f():
|
||||
collision_mask = 1 << 1 | 1 << 3
|
||||
collision_mask = 1 << 1 & 1 << 3
|
||||
collision_mask = ~1
|
||||
collision_mask = 1 ^ ~1
|
||||
@@ -8,3 +8,7 @@ func f():
|
||||
func g():
|
||||
print(true and ( not false ) or ( true))
|
||||
print(true and not false or not (true) )
|
||||
|
||||
func h():
|
||||
print(true && ( not false ) || ( true))
|
||||
print(true && not false || not (true) )
|
||||
|
||||
@@ -8,3 +8,7 @@ func f():
|
||||
func g():
|
||||
print(true and (not false) or (true))
|
||||
print(true and not false or not (true))
|
||||
|
||||
func h():
|
||||
print(true && (not false) || (true))
|
||||
print(true && not false || not (true))
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
|
||||
func test():
|
||||
|
||||
pass
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
func test():
|
||||
|
||||
pass
|
||||
@@ -2,14 +2,14 @@ var a = 10
|
||||
var b := 10
|
||||
var c: int = 10
|
||||
|
||||
func f(b:=10):
|
||||
return func(c:=10):
|
||||
func f(b := 10):
|
||||
return func(c := 10):
|
||||
pass
|
||||
|
||||
func f(b: int=10):
|
||||
return func(c: int=10):
|
||||
func f(b: int = 10):
|
||||
return func(c: int = 10):
|
||||
pass
|
||||
|
||||
func f(b=10):
|
||||
return func(c=10):
|
||||
func f(b = 10):
|
||||
return func(c = 10):
|
||||
pass
|
||||
|
||||
10
src/formatter/snapshots/keywords/in.gd
Normal file
10
src/formatter/snapshots/keywords/in.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
func f():
|
||||
const a = preload("res://a.gd")
|
||||
const b = load("res://b.gd")
|
||||
|
||||
var origin: Vector2 = Vector2.ZERO
|
||||
origin.x = 1
|
||||
var andigin: Vector2 = Vector2.ZERO
|
||||
andigin.x = 1
|
||||
|
||||
print(a)
|
||||
10
src/formatter/snapshots/keywords/out.gd
Normal file
10
src/formatter/snapshots/keywords/out.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
func f():
|
||||
const a = preload("res://a.gd")
|
||||
const b = load("res://b.gd")
|
||||
|
||||
var origin: Vector2 = Vector2.ZERO
|
||||
origin.x = 1
|
||||
var andigin: Vector2 = Vector2.ZERO
|
||||
andigin.x = 1
|
||||
|
||||
print(a)
|
||||
4
src/formatter/snapshots/match/in.gd
Normal file
4
src/formatter/snapshots/match/in.gd
Normal file
@@ -0,0 +1,4 @@
|
||||
func f(x):
|
||||
match x:
|
||||
var y when y>20:
|
||||
pass
|
||||
4
src/formatter/snapshots/match/out.gd
Normal file
4
src/formatter/snapshots/match/out.gd
Normal file
@@ -0,0 +1,4 @@
|
||||
func f(x):
|
||||
match x:
|
||||
var y when y > 20:
|
||||
pass
|
||||
3
src/formatter/snapshots/max-empty-lines-1/config.json
Normal file
3
src/formatter/snapshots/max-empty-lines-1/config.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"maxEmptyLines": 1
|
||||
}
|
||||
27
src/formatter/snapshots/max-empty-lines-1/in.gd
Normal file
27
src/formatter/snapshots/max-empty-lines-1/in.gd
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
|
||||
|
||||
class Test:
|
||||
|
||||
|
||||
|
||||
|
||||
func _ready():
|
||||
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
func test():
|
||||
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
# comments
|
||||
func with_comments():
|
||||
|
||||
pass
|
||||
14
src/formatter/snapshots/max-empty-lines-1/out.gd
Normal file
14
src/formatter/snapshots/max-empty-lines-1/out.gd
Normal file
@@ -0,0 +1,14 @@
|
||||
class Test:
|
||||
|
||||
func _ready():
|
||||
|
||||
pass
|
||||
|
||||
func test():
|
||||
|
||||
pass
|
||||
|
||||
# comments
|
||||
func with_comments():
|
||||
|
||||
pass
|
||||
28
src/formatter/snapshots/max-empty-lines-2/in.gd
Normal file
28
src/formatter/snapshots/max-empty-lines-2/in.gd
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
|
||||
class Test:
|
||||
|
||||
|
||||
|
||||
|
||||
func _ready():
|
||||
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
func test():
|
||||
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
# comments
|
||||
func with_comments():
|
||||
|
||||
|
||||
pass
|
||||
20
src/formatter/snapshots/max-empty-lines-2/out.gd
Normal file
20
src/formatter/snapshots/max-empty-lines-2/out.gd
Normal file
@@ -0,0 +1,20 @@
|
||||
class Test:
|
||||
|
||||
|
||||
func _ready():
|
||||
|
||||
|
||||
pass
|
||||
|
||||
|
||||
func test():
|
||||
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# comments
|
||||
func with_comments():
|
||||
|
||||
|
||||
pass
|
||||
@@ -69,3 +69,6 @@ var a = $"%Child/GrandChild".some_method()
|
||||
var a = $Child.get_node('GrandChild').some_method()
|
||||
var a = $"Child".get_node('GrandChild').some_method()
|
||||
var a = $"%Child".get_node('GrandChild').some_method()
|
||||
|
||||
func f():
|
||||
$Child.add_child(%Unique)
|
||||
|
||||
@@ -69,3 +69,6 @@ var a = $"%Child/GrandChild".some_method()
|
||||
var a = $Child.get_node('GrandChild').some_method()
|
||||
var a = $"Child".get_node('GrandChild').some_method()
|
||||
var a = $"%Child".get_node('GrandChild').some_method()
|
||||
|
||||
func f():
|
||||
$Child.add_child(%Unique)
|
||||
|
||||
@@ -20,7 +20,7 @@ export const keywords = [
|
||||
"is",
|
||||
"master",
|
||||
"mastersync",
|
||||
"match",
|
||||
"when",
|
||||
"not",
|
||||
"onready",
|
||||
"or",
|
||||
@@ -58,10 +58,14 @@ export const symbols = [
|
||||
"&=",
|
||||
"^=",
|
||||
"|=",
|
||||
"~=",
|
||||
"<<=",
|
||||
">>=",
|
||||
":=",
|
||||
"->",
|
||||
"&",
|
||||
"|",
|
||||
"^",
|
||||
"-",
|
||||
"+",
|
||||
"/",
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { Range, TextDocument, TextEdit } from "vscode";
|
||||
import * as fs from "fs";
|
||||
import { TextEdit } from "vscode";
|
||||
import type { TextDocument, TextLine } from "vscode";
|
||||
import * as fs from "node:fs";
|
||||
import * as vsctm from "vscode-textmate";
|
||||
import * as oniguruma from "vscode-oniguruma";
|
||||
import { keywords, symbols } from "./symbols";
|
||||
import { get_extension_uri, createLogger } from "../utils";
|
||||
import { get_configuration, get_extension_uri, createLogger } from "../utils";
|
||||
|
||||
const log = createLogger("formatter.tm");
|
||||
|
||||
// Promisify readFile
|
||||
function readFile(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, (error, data) => error ? reject(error) : resolve(data));
|
||||
fs.readFile(path, (error, data) => (error ? reject(error) : resolve(data)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,17 +23,21 @@ const wasmBin = fs.readFileSync(wasmPath).buffer;
|
||||
const registry = new vsctm.Registry({
|
||||
onigLib: oniguruma.loadWASM(wasmBin).then(() => {
|
||||
return {
|
||||
createOnigScanner(patterns) { return new oniguruma.OnigScanner(patterns); },
|
||||
createOnigString(s) { return new oniguruma.OnigString(s); }
|
||||
createOnigScanner(patterns) {
|
||||
return new oniguruma.OnigScanner(patterns);
|
||||
},
|
||||
createOnigString(s) {
|
||||
return new oniguruma.OnigString(s);
|
||||
},
|
||||
};
|
||||
}),
|
||||
loadGrammar: (scopeName) => {
|
||||
if (scopeName === "source.gdscript") {
|
||||
return readFile(grammarPath).then(data => vsctm.parseRawGrammar(data.toString(), grammarPath));
|
||||
return readFile(grammarPath).then((data) => vsctm.parseRawGrammar(data.toString(), grammarPath));
|
||||
}
|
||||
// console.log(`Unknown scope name: ${scopeName}`);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
interface Token {
|
||||
@@ -47,6 +52,20 @@ interface Token {
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
export interface FormatterOptions {
|
||||
maxEmptyLines: 1 | 2;
|
||||
denseFunctionParameters: boolean;
|
||||
}
|
||||
|
||||
function get_formatter_options() {
|
||||
const options: FormatterOptions = {
|
||||
maxEmptyLines: get_configuration("formatter.maxEmptyLines") === "1" ? 1 : 2,
|
||||
denseFunctionParameters: get_configuration("formatter.denseFunctionParameters"),
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function parse_token(token: Token) {
|
||||
if (token.scopes.includes("string.quoted.gdscript")) {
|
||||
token.string = true;
|
||||
@@ -56,6 +75,12 @@ function parse_token(token: Token) {
|
||||
}
|
||||
if (token.scopes.includes("meta.literal.nodepath.gdscript")) {
|
||||
token.skip = true;
|
||||
token.type = "nodepath";
|
||||
return;
|
||||
}
|
||||
if (token.scopes.includes("keyword.control.flow.gdscript")) {
|
||||
token.type = "keyword";
|
||||
return;
|
||||
}
|
||||
if (keywords.includes(token.value)) {
|
||||
token.type = "keyword";
|
||||
@@ -65,6 +90,10 @@ function parse_token(token: Token) {
|
||||
token.type = "symbol";
|
||||
return;
|
||||
}
|
||||
// "preload" is highlighted as a keyword but it behaves like a function
|
||||
if (token.value === "preload") {
|
||||
return;
|
||||
}
|
||||
if (token.scopes.includes("keyword.language.gdscript")) {
|
||||
token.type = "keyword";
|
||||
return;
|
||||
@@ -79,7 +108,7 @@ function parse_token(token: Token) {
|
||||
}
|
||||
}
|
||||
|
||||
function between(tokens: Token[], current: number) {
|
||||
function between(tokens: Token[], current: number, options: FormatterOptions) {
|
||||
const nextToken = tokens[current];
|
||||
const prevToken = tokens[current - 1];
|
||||
const next = nextToken.value;
|
||||
@@ -89,26 +118,44 @@ function between(tokens: Token[], current: number) {
|
||||
|
||||
if (!prev) return "";
|
||||
|
||||
if (next === "##") return " ";
|
||||
if (next === "#") return " ";
|
||||
if (prevToken.skip && nextToken.skip) return "";
|
||||
|
||||
if (prev === "(") return "";
|
||||
|
||||
if (nextToken.param) {
|
||||
if (prev === "-" && tokens[current - 2]?.value === ",") {
|
||||
return "";
|
||||
if (options.denseFunctionParameters) {
|
||||
if (prev === "-") {
|
||||
if (tokens[current - 2]?.value === "=") return "";
|
||||
if (["keyword", "symbol"].includes(tokens[current - 2].type)) {
|
||||
return "";
|
||||
}
|
||||
if ([",", "("].includes(tokens[current - 2]?.value)) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
if (next === "%") return " ";
|
||||
if (prev === "%") return " ";
|
||||
if (next === "=") {
|
||||
if (tokens[current - 2]?.value === ":") return " ";
|
||||
return "";
|
||||
}
|
||||
if (prev === "=") {
|
||||
if (tokens[current - 3]?.value === ":") return " ";
|
||||
return "";
|
||||
}
|
||||
if (prevToken?.type === "symbol") return " ";
|
||||
if (nextToken.type === "symbol") return " ";
|
||||
} else {
|
||||
if (next === ":") {
|
||||
if (tokens[current + 1]?.value === "=") return " ";
|
||||
}
|
||||
}
|
||||
if (next === "%") return " ";
|
||||
if (prev === "%") return " ";
|
||||
if (next === "=") return "";
|
||||
if (prev === "=") return "";
|
||||
if (next === ":=") return "";
|
||||
if (prev === ":=") return "";
|
||||
if (prevToken?.type === "symbol") return " ";
|
||||
if (nextToken.type === "symbol") return " ";
|
||||
}
|
||||
|
||||
if (next === ":") {
|
||||
if (["var", "const"].includes(tokens[current - 2]?.value)) {
|
||||
if (tokens[current + 1]?.value !== "=") return "";
|
||||
if (tokens[current + 1]?.value !== "=") return "";
|
||||
return " ";
|
||||
}
|
||||
@@ -117,7 +164,10 @@ function between(tokens: Token[], current: number) {
|
||||
if (prev === "@") return "";
|
||||
|
||||
if (prev === "-") {
|
||||
if (tokens[current - 2]?.value === "(") {
|
||||
if (["keyword", "symbol"].includes(tokens[current - 2].type)) {
|
||||
return "";
|
||||
}
|
||||
if ([",", "(", "["].includes(tokens[current - 2]?.value)) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -132,8 +182,10 @@ function between(tokens: Token[], current: number) {
|
||||
if (prev === ")" && nextToken.type === "keyword") return " ";
|
||||
|
||||
if (prev === "[" && nextToken.type === "symbol") return "";
|
||||
if (prev === "[" && nextToken.type === "nodepath") return "";
|
||||
if (prev === ":") return " ";
|
||||
if (prev === ";") return " ";
|
||||
if (prev === "##") return " ";
|
||||
if (prev === "#") return " ";
|
||||
if (next === "=") return " ";
|
||||
if (prev === "=") return " ";
|
||||
@@ -157,17 +209,26 @@ function between(tokens: Token[], current: number) {
|
||||
|
||||
let grammar = null;
|
||||
|
||||
registry.loadGrammar("source.gdscript").then(g => { grammar = g; });
|
||||
registry.loadGrammar("source.gdscript").then((g) => {
|
||||
grammar = g;
|
||||
});
|
||||
|
||||
export function format_document(document: TextDocument): TextEdit[] {
|
||||
function is_comment(line: TextLine): boolean {
|
||||
return line.text[line.firstNonWhitespaceCharacterIndex] === "#";
|
||||
}
|
||||
|
||||
export function format_document(document: TextDocument, _options?: FormatterOptions): TextEdit[] {
|
||||
// quit early if grammar is not loaded
|
||||
if (!grammar) {
|
||||
return [];
|
||||
}
|
||||
const edits: TextEdit[] = [];
|
||||
|
||||
const options = _options ?? get_formatter_options();
|
||||
|
||||
let lineTokens: vsctm.ITokenizeLineResult = null;
|
||||
let onlyEmptyLinesSoFar = true;
|
||||
let emptyLineCount = 0;
|
||||
for (let lineNum = 0; lineNum < document.lineCount; lineNum++) {
|
||||
const line = document.lineAt(lineNum);
|
||||
|
||||
@@ -177,24 +238,29 @@ export function format_document(document: TextDocument): TextEdit[] {
|
||||
if (onlyEmptyLinesSoFar) {
|
||||
edits.push(TextEdit.delete(line.rangeIncludingLineBreak));
|
||||
} else {
|
||||
// Limit the number of consecutive empty lines
|
||||
const maxEmptyLines: number = 1;
|
||||
if (maxEmptyLines === 1) {
|
||||
if (lineNum < document.lineCount - 1 && document.lineAt(lineNum + 1).isEmptyOrWhitespace) {
|
||||
edits.push(TextEdit.delete(line.rangeIncludingLineBreak));
|
||||
}
|
||||
} else if (maxEmptyLines === 2) {
|
||||
if (lineNum < document.lineCount - 2 && document.lineAt(lineNum + 1).isEmptyOrWhitespace && document.lineAt(lineNum + 2).isEmptyOrWhitespace) {
|
||||
edits.push(TextEdit.delete(line.rangeIncludingLineBreak));
|
||||
}
|
||||
emptyLineCount++;
|
||||
}
|
||||
|
||||
// delete empty lines at the end of the file
|
||||
if (lineNum === document.lineCount - 1) {
|
||||
for (let i = lineNum - emptyLineCount + 1; i < document.lineCount; i++) {
|
||||
edits.push(TextEdit.delete(document.lineAt(i).rangeIncludingLineBreak));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
onlyEmptyLinesSoFar = false;
|
||||
|
||||
// delete consecutive empty lines
|
||||
if (emptyLineCount) {
|
||||
for (let i = emptyLineCount - options.maxEmptyLines; i > 0; i--) {
|
||||
edits.push(TextEdit.delete(document.lineAt(lineNum - i).rangeIncludingLineBreak));
|
||||
}
|
||||
emptyLineCount = 0;
|
||||
}
|
||||
|
||||
// skip comments
|
||||
if (line.text[line.firstNonWhitespaceCharacterIndex] === "#") {
|
||||
if (is_comment(line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -228,7 +294,7 @@ export function format_document(document: TextDocument): TextEdit[] {
|
||||
if (i > 0 && tokens[i - 1].string === true && tokens[i].string === true) {
|
||||
nextLine += tokens[i].original;
|
||||
} else {
|
||||
nextLine += between(tokens, i) + tokens[i].value.trim();
|
||||
nextLine += between(tokens, i, options) + tokens[i].value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export class ClientConnectionManager {
|
||||
private status: ManagerStatus = ManagerStatus.INITIALIZING;
|
||||
private statusWidget: vscode.StatusBarItem = null;
|
||||
|
||||
private connectedVersion: string = "";
|
||||
private connectedVersion = "";
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) {
|
||||
this.context = context;
|
||||
@@ -105,9 +105,11 @@ export class ClientConnectionManager {
|
||||
targetVersion = "4.2";
|
||||
}
|
||||
const settingName = `editorPath.godot${projectVersion[0]}`;
|
||||
const godotPath = get_configuration(settingName).replace(/^"/, "").replace(/"$/, "");
|
||||
let godotPath = get_configuration(settingName);
|
||||
|
||||
const result = verify_godot_version(godotPath, projectVersion[0]);
|
||||
godotPath = result.godotPath;
|
||||
|
||||
switch (result.status) {
|
||||
case "WRONG_VERSION": {
|
||||
const message = `Cannot launch headless LSP: The current project uses Godot v${projectVersion}, but the specified Godot executable is v${result.version}`;
|
||||
@@ -125,11 +127,11 @@ export class ClientConnectionManager {
|
||||
if (result.version[2] < minimumVersion) {
|
||||
const message = `Cannot launch headless LSP: Headless LSP mode is only available on v${targetVersion} or newer, but the specified Godot executable is v${result.version}.`;
|
||||
vscode.window.showErrorMessage(message, "Select Godot executable", "Open Settings", "Disable Headless LSP", "Ignore").then(item => {
|
||||
if (item == "Select Godot executable") {
|
||||
if (item === "Select Godot executable") {
|
||||
select_godot_executable(settingName);
|
||||
} else if (item == "Open Settings") {
|
||||
} else if (item === "Open Settings") {
|
||||
vscode.commands.executeCommand("workbench.action.openSettings", settingName);
|
||||
} else if (item == "Disable Headless LSP") {
|
||||
} else if (item === "Disable Headless LSP") {
|
||||
set_configuration("lsp.headless", false);
|
||||
prompt_for_reload();
|
||||
}
|
||||
@@ -192,7 +194,7 @@ export class ClientConnectionManager {
|
||||
const message = `Connected to the GDScript language server at ${lspTarget}.`;
|
||||
|
||||
let options = ["Ok"];
|
||||
if (this.target == TargetLSP.HEADLESS) {
|
||||
if (this.target === TargetLSP.HEADLESS) {
|
||||
options = ["Restart LSP", ...options];
|
||||
}
|
||||
vscode.window.showInformationMessage(message, ...options).then(item => {
|
||||
@@ -262,6 +264,7 @@ export class ClientConnectionManager {
|
||||
break;
|
||||
case ClientStatus.CONNECTED:
|
||||
this.retry = false;
|
||||
this.reconnectionAttempts = 0;
|
||||
set_context("connectedToLSP", true);
|
||||
this.status = ManagerStatus.CONNECTED;
|
||||
if (!this.client.started) {
|
||||
@@ -271,7 +274,7 @@ export class ClientConnectionManager {
|
||||
case ClientStatus.DISCONNECTED:
|
||||
set_context("connectedToLSP", false);
|
||||
if (this.retry) {
|
||||
if (this.client.port != -1) {
|
||||
if (this.client.port !== -1) {
|
||||
this.status = ManagerStatus.INITIALIZING_LSP;
|
||||
} else {
|
||||
this.status = ManagerStatus.RETRYING;
|
||||
@@ -317,15 +320,15 @@ export class ClientConnectionManager {
|
||||
const message = `Couldn't connect to the GDScript language server at ${lspTarget}. Is the Godot editor or language server running?`;
|
||||
|
||||
let options = ["Retry", "Ignore"];
|
||||
if (this.target == TargetLSP.EDITOR) {
|
||||
if (this.target === TargetLSP.EDITOR) {
|
||||
options = ["Open workspace with Godot Editor", ...options];
|
||||
}
|
||||
|
||||
vscode.window.showErrorMessage(message, ...options).then(item => {
|
||||
if (item == "Retry") {
|
||||
if (item === "Retry") {
|
||||
this.connect_to_language_server();
|
||||
}
|
||||
if (item == "Open workspace with Godot Editor") {
|
||||
if (item === "Open workspace with Godot Editor") {
|
||||
vscode.commands.executeCommand("godotTools.openEditor");
|
||||
this.connect_to_language_server();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export enum TargetLSP {
|
||||
const CUSTOM_MESSAGE = "gdscript_client/";
|
||||
|
||||
export default class GDScriptLanguageClient extends LanguageClient {
|
||||
public readonly io: MessageIO = (get_configuration("lsp.serverProtocol") == "ws") ? new WebSocketMessageIO() : new TCPMessageIO();
|
||||
public readonly io: MessageIO = (get_configuration("lsp.serverProtocol") === "ws") ? new WebSocketMessageIO() : new TCPMessageIO();
|
||||
|
||||
private _status_changed_callbacks: ((v: ClientStatus) => void)[] = [];
|
||||
private _initialize_request: Message = null;
|
||||
@@ -29,18 +29,18 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
|
||||
public target: TargetLSP = TargetLSP.EDITOR;
|
||||
|
||||
public port: number = -1;
|
||||
public lastPortTried: number = -1;
|
||||
public port = -1;
|
||||
public lastPortTried = -1;
|
||||
public sentMessages = new Map();
|
||||
public lastSymbolHovered: string = "";
|
||||
public lastSymbolHovered = "";
|
||||
|
||||
private _started: boolean = false;
|
||||
private _started = false;
|
||||
public get started(): boolean { return this._started; }
|
||||
|
||||
private _status: ClientStatus;
|
||||
public get status(): ClientStatus { return this._status; }
|
||||
public set status(v: ClientStatus) {
|
||||
if (this._status != v) {
|
||||
if (this._status !== v) {
|
||||
this._status = v;
|
||||
for (const callback of this._status_changed_callbacks) {
|
||||
callback(v);
|
||||
@@ -49,7 +49,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
}
|
||||
|
||||
public watch_status(callback: (v: ClientStatus) => void) {
|
||||
if (this._status_changed_callbacks.indexOf(callback) == -1) {
|
||||
if (this._status_changed_callbacks.indexOf(callback) === -1) {
|
||||
this._status_changed_callbacks.push(callback);
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
port = this.port;
|
||||
}
|
||||
|
||||
if (this.target == TargetLSP.EDITOR) {
|
||||
if (this.target === TargetLSP.EDITOR) {
|
||||
if (port === 6005 || port === 6008) {
|
||||
port = 6005;
|
||||
}
|
||||
@@ -117,7 +117,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
private on_send_message(message: RequestMessage) {
|
||||
this.sentMessages.set(message.id, message);
|
||||
|
||||
if (message.method == "initialize") {
|
||||
if (message.method === "initialize") {
|
||||
this._initialize_request = message;
|
||||
}
|
||||
}
|
||||
@@ -186,7 +186,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
const contents = message["contents"];
|
||||
|
||||
let decl: string;
|
||||
if (contents instanceof Array) {
|
||||
if (Array.isArray(contents)) {
|
||||
decl = contents[0];
|
||||
} else {
|
||||
decl = contents.value;
|
||||
@@ -196,7 +196,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
}
|
||||
decl = decl.split("\n")[0].trim();
|
||||
|
||||
let match;
|
||||
let match: RegExpMatchArray;
|
||||
let result = undefined;
|
||||
match = decl.match(/(?:func|const) (@?\w+)\.(\w+)/);
|
||||
if (match) {
|
||||
@@ -222,7 +222,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
}
|
||||
|
||||
private on_disconnected() {
|
||||
if (this.target == TargetLSP.EDITOR) {
|
||||
if (this.target === TargetLSP.EDITOR) {
|
||||
const host = get_configuration("lsp.serverHost");
|
||||
let port = get_configuration("lsp.serverPort");
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ export class GDDocumentLinkProvider implements DocumentLinkProvider {
|
||||
links.push(link);
|
||||
}
|
||||
}
|
||||
for (const match of text.matchAll(/res:\/\/[^"^']*/g)) {
|
||||
for (const match of text.matchAll(/res:\/\/([^"'\n]*)/g)) {
|
||||
const r = this.create_range(document, match);
|
||||
const uri = await convert_resource_path_to_uri(match[0]);
|
||||
if (uri instanceof Uri) {
|
||||
|
||||
@@ -243,22 +243,12 @@ export function make_symbol_document(symbol: GodotNativeSymbol): string {
|
||||
}
|
||||
|
||||
if (symbol.kind == SymbolKind.Class) {
|
||||
let doc = element("h2", `Native class ${symbol.name}`);
|
||||
const parts = /extends\s+([A-z0-9]+)/.exec(symbol.detail);
|
||||
let inherits = parts && parts.length > 1 ? parts[1] : "";
|
||||
if (inherits) {
|
||||
let inherits_chain = "";
|
||||
let base_class = symbol.class_info[inherits];
|
||||
while (base_class) {
|
||||
inherits_chain += `${inherits_chain ? " >" : ""} ${make_link(
|
||||
base_class.name,
|
||||
undefined
|
||||
)}`;
|
||||
base_class = symbol.class_info[base_class.inherits];
|
||||
}
|
||||
inherits = `Inherits: ${inherits_chain}`;
|
||||
doc += element("p", inherits);
|
||||
let doc = element("h2", `Class: ${symbol.name}`);
|
||||
if (symbol.class_info.inherits) {
|
||||
const inherits = make_link(symbol.class_info.inherits, undefined);
|
||||
doc += element("p", `Inherits: ${inherits}`);
|
||||
}
|
||||
|
||||
if (symbol.class_info && symbol.class_info.extended_classes) {
|
||||
let inherited = "";
|
||||
for (const c of symbol.class_info.extended_classes) {
|
||||
@@ -267,6 +257,8 @@ export function make_symbol_document(symbol: GodotNativeSymbol): string {
|
||||
doc += element("p", `Inherited by:${inherited}`);
|
||||
}
|
||||
|
||||
doc += element("p", format_documentation(symbol.documentation, symbol.native_class));
|
||||
|
||||
let constants = "";
|
||||
let signals = "";
|
||||
let methods_index = "";
|
||||
@@ -307,10 +299,6 @@ export function make_symbol_document(symbol: GodotNativeSymbol): string {
|
||||
}
|
||||
};
|
||||
|
||||
doc += element(
|
||||
"p",
|
||||
format_documentation(symbol.documentation, symbol.native_class)
|
||||
);
|
||||
add_group("Properties", properties_index);
|
||||
add_group("Constants", constants);
|
||||
add_group("Signals", signals);
|
||||
|
||||
@@ -12,6 +12,7 @@ export class SceneParser {
|
||||
|
||||
constructor() {
|
||||
if (SceneParser.instance) {
|
||||
// biome-ignore lint/correctness/noConstructorReturn: <explanation>
|
||||
return SceneParser.instance;
|
||||
}
|
||||
SceneParser.instance = this;
|
||||
@@ -24,7 +25,7 @@ export class SceneParser {
|
||||
if (this.scenes.has(path)) {
|
||||
const scene = this.scenes.get(path);
|
||||
|
||||
if (scene.mtime == stats.mtimeMs) {
|
||||
if (scene.mtime === stats.mtimeMs) {
|
||||
return scene;
|
||||
}
|
||||
}
|
||||
@@ -83,26 +84,32 @@ export class SceneParser {
|
||||
const nodes = {};
|
||||
let lastNode = null;
|
||||
|
||||
const nodeRegex = /\[node name="([\w]*)"(?: type="([\w]*)")?(?: parent="([\w\/.]*)")?(?: instance=ExtResource\(\s*"?([\w]+)"?\s*\))?\]/g;
|
||||
const nodeRegex = /\[node.*/g;
|
||||
for (const match of text.matchAll(nodeRegex)) {
|
||||
const name = match[1];
|
||||
const type = match[2] ? match[2] : "PackedScene";
|
||||
let parent = match[3];
|
||||
const instance = match[4] ? match[4] : 0;
|
||||
const line = match[0];
|
||||
const name = line.match(/name="([\w]+)"/)?.[1];
|
||||
const type = line.match(/type="([\w]+)"/)?.[1] ?? "PackedScene";
|
||||
let parent = line.match(/parent="([\w\/.]+)"/)?.[1];
|
||||
const instance = line.match(/instance=ExtResource\(\s*"?([\w]+)"?\s*\)/)?.[1];
|
||||
|
||||
// leaving this in case we have a reason to use these node paths in the future
|
||||
// const rawNodePaths = line.match(/node_paths=PackedStringArray\(([\w",\s]*)\)/)?.[1];
|
||||
// const nodePaths = rawNodePaths?.split(",").forEach(x => x.trim().replace("\"", ""));
|
||||
|
||||
let _path = "";
|
||||
let relativePath = "";
|
||||
|
||||
if (parent == undefined) {
|
||||
if (parent === undefined) {
|
||||
root = name;
|
||||
_path = name;
|
||||
} else if (parent == ".") {
|
||||
} else if (parent === ".") {
|
||||
parent = root;
|
||||
relativePath = name;
|
||||
_path = parent + "/" + name;
|
||||
_path = `${parent}/${name}`;
|
||||
} else {
|
||||
relativePath = parent + "/" + name;
|
||||
parent = root + "/" + parent;
|
||||
_path = parent + "/" + name;
|
||||
relativePath = `${parent}/${name}`;
|
||||
parent = `${root}/${parent}`;
|
||||
_path = `${parent}/${name}`;
|
||||
}
|
||||
if (lastNode) {
|
||||
lastNode.body = text.slice(lastNode.position, match.index);
|
||||
@@ -136,7 +143,7 @@ export class SceneParser {
|
||||
}
|
||||
node.contextValue += "hasResourcePath";
|
||||
}
|
||||
if (_path == root) {
|
||||
if (_path === root) {
|
||||
scene.root = node;
|
||||
}
|
||||
if (parent in nodes) {
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import * as vscode from "vscode";
|
||||
import {
|
||||
TreeDataProvider,
|
||||
TreeDragAndDropController,
|
||||
ExtensionContext,
|
||||
type TreeDataProvider,
|
||||
type TreeDragAndDropController,
|
||||
type ExtensionContext,
|
||||
EventEmitter,
|
||||
Event,
|
||||
TreeView,
|
||||
ProviderResult,
|
||||
TreeItem,
|
||||
type Event,
|
||||
type TreeView,
|
||||
type ProviderResult,
|
||||
type TreeItem,
|
||||
TreeItemCollapsibleState,
|
||||
window,
|
||||
languages,
|
||||
Uri,
|
||||
CancellationToken,
|
||||
FileDecoration,
|
||||
DocumentDropEditProvider,
|
||||
type Uri,
|
||||
type CancellationToken,
|
||||
type FileDecoration,
|
||||
type DocumentDropEditProvider,
|
||||
workspace,
|
||||
} from "vscode";
|
||||
import * as fs from "fs";
|
||||
import * as fs from "node:fs";
|
||||
import {
|
||||
get_configuration,
|
||||
find_file,
|
||||
@@ -26,17 +26,20 @@ import {
|
||||
register_command,
|
||||
createLogger,
|
||||
make_docs_uri,
|
||||
node_name_to_snake,
|
||||
} from "../utils";
|
||||
import { SceneParser } from "./parser";
|
||||
import { SceneNode, Scene } from "./types";
|
||||
import type { SceneNode, Scene } from "./types";
|
||||
|
||||
const log = createLogger("scenes.preview");
|
||||
|
||||
export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDragAndDropController<SceneNode>, DocumentDropEditProvider {
|
||||
export class ScenePreviewProvider
|
||||
implements TreeDataProvider<SceneNode>, TreeDragAndDropController<SceneNode>, DocumentDropEditProvider
|
||||
{
|
||||
public dropMimeTypes = [];
|
||||
public dragMimeTypes = [];
|
||||
private tree: TreeView<SceneNode>;
|
||||
private scenePreviewPinned = false;
|
||||
private scenePreviewLocked = false;
|
||||
private currentScene = "";
|
||||
public parser = new SceneParser();
|
||||
public scene: Scene;
|
||||
@@ -52,7 +55,7 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
constructor(private context: ExtensionContext) {
|
||||
this.tree = vscode.window.createTreeView("scenePreview", {
|
||||
treeDataProvider: this,
|
||||
dragAndDropController: this
|
||||
dragAndDropController: this,
|
||||
});
|
||||
|
||||
const selector = [
|
||||
@@ -60,12 +63,14 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
{ language: "gdscript", scheme: "file" },
|
||||
];
|
||||
context.subscriptions.push(
|
||||
register_command("scenePreview.pin", this.pin_preview.bind(this)),
|
||||
register_command("scenePreview.unpin", this.unpin_preview.bind(this)),
|
||||
register_command("scenePreview.lock", this.lock_preview.bind(this)),
|
||||
register_command("scenePreview.unlock", this.unlock_preview.bind(this)),
|
||||
register_command("scenePreview.copyNodePath", this.copy_node_path.bind(this)),
|
||||
register_command("scenePreview.copyResourcePath", this.copy_resource_path.bind(this)),
|
||||
register_command("scenePreview.openScene", this.open_scene.bind(this)),
|
||||
register_command("scenePreview.openScript", this.open_script.bind(this)),
|
||||
register_command("scenePreview.openCurrentScene", this.open_current_scene.bind(this)),
|
||||
register_command("scenePreview.openCurrentScript", this.open_main_script.bind(this)),
|
||||
register_command("scenePreview.goToDefinition", this.go_to_definition.bind(this)),
|
||||
register_command("scenePreview.openDocumentation", this.open_documentation.bind(this)),
|
||||
register_command("scenePreview.refresh", this.refresh.bind(this)),
|
||||
@@ -82,21 +87,60 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
public handleDrag(source: readonly SceneNode[], data: vscode.DataTransfer, token: vscode.CancellationToken): void | Thenable<void> {
|
||||
public handleDrag(
|
||||
source: readonly SceneNode[],
|
||||
data: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken,
|
||||
): void | Thenable<void> {
|
||||
data.set("godot/path", new vscode.DataTransferItem(source[0].relativePath));
|
||||
data.set("godot/class", new vscode.DataTransferItem(source[0].className));
|
||||
data.set("godot/unique", new vscode.DataTransferItem(source[0].unique));
|
||||
data.set("godot/label", new vscode.DataTransferItem(source[0].label));
|
||||
}
|
||||
|
||||
public provideDocumentDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): vscode.ProviderResult<vscode.DocumentDropEdit> {
|
||||
const path = dataTransfer.get("godot/path").value;
|
||||
const className = dataTransfer.get("godot/class").value;
|
||||
public provideDocumentDropEdits(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken,
|
||||
): vscode.ProviderResult<vscode.DocumentDropEdit> {
|
||||
const path: string = dataTransfer.get("godot/path").value;
|
||||
const className: string = dataTransfer.get("godot/class").value;
|
||||
const line = document.lineAt(position.line);
|
||||
const unique = dataTransfer.get("godot/unique").value === "true";
|
||||
const label: string = dataTransfer.get("godot/label").value;
|
||||
|
||||
// TODO: compare the source scene to the target file
|
||||
// What should happen when you drag a node into a script that isn't the
|
||||
// "main" script for that scene?
|
||||
// Attempt to calculate a relative path that resolves correctly?
|
||||
|
||||
if (className) {
|
||||
// For the root node, the path is empty and needs to be replaced with the node name
|
||||
const savePath = path || label;
|
||||
|
||||
if (path && className) {
|
||||
if (document.languageId === "gdscript") {
|
||||
return new vscode.DocumentDropEdit(`$${path}`);
|
||||
let qualifiedPath = `$${savePath}`;
|
||||
|
||||
if (unique) {
|
||||
// For unique nodes, we can use the % syntax and drop the full path
|
||||
qualifiedPath = `%${label}`;
|
||||
}
|
||||
|
||||
if (line.text === "") {
|
||||
// We assume that if the user is dropping a node in an empty line, they are at the top of
|
||||
// the script and want to declare an onready variable
|
||||
return new vscode.DocumentDropEdit(
|
||||
`@onready var ${node_name_to_snake(label)}: ${className} = ${qualifiedPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
// In any other place, we assume the user wants to get a reference to the node itself
|
||||
return new vscode.DocumentDropEdit(qualifiedPath);
|
||||
}
|
||||
|
||||
if (document.languageId === "csharp") {
|
||||
return new vscode.DocumentDropEdit(`GetNode<${className}>("${path}")`);
|
||||
return new vscode.DocumentDropEdit(`GetNode<${className}>("${savePath}")`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +150,7 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
return;
|
||||
}
|
||||
setTimeout(async () => {
|
||||
if (uri.fsPath == this.currentScene) {
|
||||
if (uri.fsPath === this.currentScene) {
|
||||
this.refresh();
|
||||
} else {
|
||||
const document = await vscode.workspace.openTextDocument(uri);
|
||||
@@ -116,7 +160,7 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
}
|
||||
|
||||
public async refresh() {
|
||||
if (this.scenePreviewPinned) {
|
||||
if (this.scenePreviewLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,7 +172,7 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
if (!fileName.endsWith(".tscn")) {
|
||||
const searchName = fileName.replace(".gd", ".tscn").replace(".cs", ".tscn");
|
||||
|
||||
if (mode == "anyFolder") {
|
||||
if (mode === "anyFolder") {
|
||||
const relatedScene = await find_file(searchName);
|
||||
if (!relatedScene) {
|
||||
return;
|
||||
@@ -136,14 +180,14 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
fileName = relatedScene.fsPath;
|
||||
}
|
||||
|
||||
if (mode == "sameFolder") {
|
||||
if (mode === "sameFolder") {
|
||||
if (fs.existsSync(searchName)) {
|
||||
fileName = searchName;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mode == "off") {
|
||||
if (mode === "off") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -162,20 +206,20 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
}
|
||||
}
|
||||
|
||||
private pin_preview() {
|
||||
this.scenePreviewPinned = true;
|
||||
set_context("scenePreview.pinned", true);
|
||||
private lock_preview() {
|
||||
this.scenePreviewLocked = true;
|
||||
set_context("scenePreview.locked", true);
|
||||
}
|
||||
|
||||
private unpin_preview() {
|
||||
this.scenePreviewPinned = false;
|
||||
set_context("scenePreview.pinned", false);
|
||||
private unlock_preview() {
|
||||
this.scenePreviewLocked = false;
|
||||
set_context("scenePreview.locked", false);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private copy_node_path(item: SceneNode) {
|
||||
if (item.unique) {
|
||||
vscode.env.clipboard.writeText("%" + item.label);
|
||||
vscode.env.clipboard.writeText(`%${item.label}`);
|
||||
return;
|
||||
}
|
||||
vscode.env.clipboard.writeText(item.relativePath);
|
||||
@@ -201,6 +245,26 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
}
|
||||
}
|
||||
|
||||
private async open_current_scene() {
|
||||
if (this.currentScene) {
|
||||
const document = await vscode.workspace.openTextDocument(this.currentScene);
|
||||
vscode.window.showTextDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
private async open_main_script() {
|
||||
if (this.currentScene) {
|
||||
const root = this.scene.root;
|
||||
if (root?.hasScript) {
|
||||
const path = this.scene.externalResources[root.scriptId].path;
|
||||
const uri = await convert_resource_path_to_uri(path);
|
||||
if (uri) {
|
||||
vscode.window.showTextDocument(uri, { preview: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async go_to_definition(item: SceneNode) {
|
||||
const document = await vscode.workspace.openTextDocument(this.currentScene);
|
||||
const start = document.positionAt(item.position);
|
||||
@@ -216,7 +280,6 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
private tree_selection_changed(event: vscode.TreeViewSelectionChangeEvent<SceneNode>) {
|
||||
// const item = event.selection[0];
|
||||
// log(item.body);
|
||||
|
||||
// const editor = vscode.window.activeTextEditor;
|
||||
// const range = editor.document.getText()
|
||||
// editor.revealRange(range)
|
||||
@@ -226,12 +289,10 @@ export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDr
|
||||
if (!element) {
|
||||
if (!this.scene?.root) {
|
||||
return Promise.resolve([]);
|
||||
} else {
|
||||
return Promise.resolve([this.scene?.root]);
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve(element.children);
|
||||
return Promise.resolve([this.scene?.root]);
|
||||
}
|
||||
return Promise.resolve(element.children);
|
||||
}
|
||||
|
||||
public getTreeItem(element: SceneNode): TreeItem | Thenable<TreeItem> {
|
||||
@@ -254,13 +315,13 @@ class UniqueDecorationProvider implements vscode.FileDecorationProvider {
|
||||
return this.changeDecorationsEvent.event;
|
||||
}
|
||||
|
||||
constructor(private previewer: ScenePreviewProvider) { }
|
||||
constructor(private previewer: ScenePreviewProvider) {}
|
||||
|
||||
provideFileDecoration(uri: Uri, token: CancellationToken): FileDecoration | undefined {
|
||||
if (uri.scheme !== "godot") return undefined;
|
||||
|
||||
const node = this.previewer.scene?.nodes.get(uri.path);
|
||||
if (node && node.unique) {
|
||||
if (node?.unique) {
|
||||
return {
|
||||
badge: "%",
|
||||
};
|
||||
@@ -274,13 +335,13 @@ class ScriptDecorationProvider implements vscode.FileDecorationProvider {
|
||||
return this.changeDecorationsEvent.event;
|
||||
}
|
||||
|
||||
constructor(private previewer: ScenePreviewProvider) { }
|
||||
constructor(private previewer: ScenePreviewProvider) {}
|
||||
|
||||
provideFileDecoration(uri: Uri, token: CancellationToken): FileDecoration | undefined {
|
||||
if (uri.scheme !== "godot") return undefined;
|
||||
|
||||
const node = this.previewer.scene?.nodes.get(uri.path);
|
||||
if (node && node.hasScript) {
|
||||
if (node?.hasScript) {
|
||||
return {
|
||||
badge: "S",
|
||||
};
|
||||
|
||||
@@ -15,13 +15,13 @@ export function is_debug_mode(): boolean {
|
||||
export async function find_file(file: string): Promise<vscode.Uri | null> {
|
||||
if (fs.existsSync(file)) {
|
||||
return vscode.Uri.file(file);
|
||||
} else {
|
||||
const fileName = path.basename(file);
|
||||
const results = await vscode.workspace.findFiles("**/" + fileName);
|
||||
if (results.length == 1) {
|
||||
return results[0];
|
||||
}
|
||||
}
|
||||
const fileName = path.basename(file);
|
||||
const results = await vscode.workspace.findFiles(`**/${fileName}`, null);
|
||||
if (results.length === 1) {
|
||||
return results[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,26 @@ export async function get_free_port(): Promise<number> {
|
||||
export function make_docs_uri(path: string, fragment?: string) {
|
||||
return vscode.Uri.from({
|
||||
scheme: "gddoc",
|
||||
path: path + ".gddoc",
|
||||
path: `${path}.gddoc`,
|
||||
fragment: fragment,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to convert a conventional node name to a snake_case variable name.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* nodeNameToVar("MyNode") // my_node
|
||||
* nodeNameToVar("Sprite2D") // sprite_2d
|
||||
* nodeNameToVar("UI") // ui
|
||||
* ```
|
||||
*/
|
||||
export function node_name_to_snake(name: string): string {
|
||||
const snakeCase: string = name.replace(/([a-z])([A-Z0-9])/g, "$1_$2").toLowerCase();
|
||||
|
||||
if (snakeCase.startsWith("_")) {
|
||||
return snakeCase.substring(1);
|
||||
}
|
||||
return snakeCase;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import * as vscode from "vscode";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import { execSync } from "child_process";
|
||||
import * as path from "node:path";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
let projectDir: string | undefined = undefined;
|
||||
let projectFile: string | undefined = undefined;
|
||||
@@ -9,11 +10,11 @@ let projectFile: string | undefined = undefined;
|
||||
export async function get_project_dir(): Promise<string | undefined> {
|
||||
let file = "";
|
||||
if (vscode.workspace.workspaceFolders !== undefined) {
|
||||
const files = await vscode.workspace.findFiles("**/project.godot");
|
||||
const files = await vscode.workspace.findFiles("**/project.godot", null);
|
||||
|
||||
if (files.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
if (files.length === 1) {
|
||||
file = files[0].fsPath;
|
||||
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
||||
@@ -21,7 +22,7 @@ export async function get_project_dir(): Promise<string | undefined> {
|
||||
}
|
||||
} else if (files.length > 1) {
|
||||
// if multiple project files, pick the top-most one
|
||||
const best = files.reduce((a, b) => a.fsPath.length <= b.fsPath.length ? a : b);
|
||||
const best = files.reduce((a, b) => (a.fsPath.length <= b.fsPath.length ? a : b));
|
||||
if (best) {
|
||||
file = best.fsPath;
|
||||
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
||||
@@ -70,14 +71,14 @@ export async function get_project_version(): Promise<string | undefined> {
|
||||
return projectVersion;
|
||||
}
|
||||
|
||||
export function find_project_file(start: string, depth: number = 20) {
|
||||
export function find_project_file(start: string, depth = 20) {
|
||||
// TODO: rename this, it's actually more like "find_parent_project_file"
|
||||
// This function appears to be fast enough, but if speed is ever an issue,
|
||||
// memoizing the result should be straightforward
|
||||
if (start === ".") {
|
||||
if (fs.existsSync("project.godot") && fs.statSync("project.godot").isFile()) {
|
||||
return "project.godot";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const folder = path.dirname(start);
|
||||
@@ -102,23 +103,47 @@ export async function convert_resource_path_to_uri(resPath: string): Promise<vsc
|
||||
|
||||
type VERIFY_STATUS = "SUCCESS" | "WRONG_VERSION" | "INVALID_EXE";
|
||||
type VERIFY_RESULT = {
|
||||
status: VERIFY_STATUS,
|
||||
version?: string,
|
||||
}
|
||||
status: VERIFY_STATUS;
|
||||
godotPath: string;
|
||||
version?: string;
|
||||
};
|
||||
|
||||
export function verify_godot_version(godotPath: string, expectedVersion: "3" | "4" | string): VERIFY_RESULT {
|
||||
let target = clean_godot_path(godotPath);
|
||||
|
||||
let output = "";
|
||||
try {
|
||||
const output = execSync(`"${godotPath}" --version`).toString().trim();
|
||||
const pattern = /^(([34])\.([0-9]+)(?:\.[0-9]+)?)/m;
|
||||
const match = output.match(pattern);
|
||||
if (!match) {
|
||||
return { status: "INVALID_EXE" };
|
||||
}
|
||||
if (match[2] !== expectedVersion) {
|
||||
return { status: "WRONG_VERSION", version: match[1] };
|
||||
}
|
||||
return { status: "SUCCESS", version: match[1] };
|
||||
output = execSync(`"${target}" --version`).toString().trim();
|
||||
} catch {
|
||||
return { status: "INVALID_EXE" };
|
||||
if (path.isAbsolute(target)) {
|
||||
return { status: "INVALID_EXE", godotPath: target };
|
||||
}
|
||||
const workspacePath = vscode.workspace.workspaceFolders[0].uri.fsPath;
|
||||
target = path.resolve(workspacePath, target);
|
||||
try {
|
||||
output = execSync(`"${target}" --version`).toString().trim();
|
||||
} catch {
|
||||
return { status: "INVALID_EXE", godotPath: target };
|
||||
}
|
||||
}
|
||||
|
||||
const pattern = /^(([34])\.([0-9]+)(?:\.[0-9]+)?)/m;
|
||||
const match = output.match(pattern);
|
||||
if (!match) {
|
||||
return { status: "INVALID_EXE", godotPath: target };
|
||||
}
|
||||
if (match[2] !== expectedVersion) {
|
||||
return { status: "WRONG_VERSION", godotPath: target, version: match[1] };
|
||||
}
|
||||
return { status: "SUCCESS", godotPath: target, version: match[1] };
|
||||
}
|
||||
|
||||
export function clean_godot_path(godotPath: string): string {
|
||||
let target = godotPath.replace(/^"/, "").replace(/"$/, "");
|
||||
|
||||
if (os.platform() === "darwin" && target.endsWith(".app")) {
|
||||
target = path.join(target, "Contents", "MacOS", "Godot");
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function killSubProcesses(owner: string) {
|
||||
} else if (process.platform === "darwin") {
|
||||
execSync(`kill -9 ${c.pid}`);
|
||||
} else {
|
||||
process.kill(c.pid);
|
||||
process.kill(-c.pid);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -55,11 +55,10 @@
|
||||
{ "include": "#nodepath_object" },
|
||||
{ "include": "#nodepath_function" },
|
||||
{ "include": "#strings" },
|
||||
{ "include": "#builtin_classes" },
|
||||
{ "include": "#const_vars" },
|
||||
{ "include": "#keywords" },
|
||||
{ "include": "#logic_operator" },
|
||||
{ "include": "#compare_operator" },
|
||||
{ "include": "#arithmetic_operator" },
|
||||
{ "include": "#operators" },
|
||||
{ "include": "#lambda_declaration" },
|
||||
{ "include": "#class_declaration" },
|
||||
{ "include": "#variable_declaration" },
|
||||
@@ -70,6 +69,9 @@
|
||||
{ "include": "#assignment_operator" },
|
||||
{ "include": "#in_keyword" },
|
||||
{ "include": "#control_flow" },
|
||||
{ "include": "#match_keyword" },
|
||||
{ "include": "#curly_braces" },
|
||||
{ "include": "#square_braces" },
|
||||
{ "include": "#round_braces" },
|
||||
{ "include": "#function_call" },
|
||||
{ "include": "#comment" },
|
||||
@@ -77,13 +79,12 @@
|
||||
{ "include": "#func" },
|
||||
{ "include": "#letter" },
|
||||
{ "include": "#numbers" },
|
||||
{ "include": "#builtin_classes" },
|
||||
{ "include": "#pascal_case_class" },
|
||||
{ "include": "#line_continuation" }
|
||||
]
|
||||
},
|
||||
"comment": {
|
||||
"match": "(#).*$\\n?",
|
||||
"match": "(##|#).*$\\n?",
|
||||
"name": "comment.line.number-sign.gdscript",
|
||||
"captures": { "1": { "name": "punctuation.definition.comment.number-sign.gdscript" } }
|
||||
},
|
||||
@@ -97,14 +98,37 @@
|
||||
"name": "constant.character.escape.gdscript",
|
||||
"match": "\\\\."
|
||||
},
|
||||
{ "include": "#string_formatting" }
|
||||
{ "include": "#string_percent_placeholders" },
|
||||
{ "include": "#string_bracket_placeholders" }
|
||||
]
|
||||
},
|
||||
"string_formatting": {
|
||||
"string_percent_placeholders": {
|
||||
"name": "meta.format.percent.gdscript",
|
||||
"match": "(?x)\n (\n % (\\([\\w\\s]*\\))?\n [-+#0 ]*\n (\\d+|\\*)? (\\.(\\d+|\\*))?\n ([hlL])?\n [diouxXeEfFgGcrsab%]\n )\n",
|
||||
"captures": { "1": { "name": "constant.character.format.placeholder.other.gdscript" } }
|
||||
},
|
||||
"string_bracket_placeholders": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "meta.format.brace.gdscript",
|
||||
"match": "(?x)\n (\n {{ | }}\n | (?:\n {\n \\w* (\\.[[:alpha:]_]\\w* | \\[[^\\]'\"]+\\])*\n (![rsa])?\n ( : \\w? [<>=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )?\n })\n )\n",
|
||||
"captures": {
|
||||
"1": { "name": "constant.character.format.placeholder.other.gdscript" },
|
||||
"3": { "name": "storage.type.format.gdscript" },
|
||||
"4": { "name": "storage.type.format.gdscript" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "meta.format.brace.gdscript",
|
||||
"match": "(?x)\n (\n {\n \\w* (\\.[[:alpha:]_]\\w* | \\[[^\\]'\"]+\\])*\n (![rsa])?\n (:)\n [^'\"{}\\n]* (?:\n \\{ [^'\"}\\n]*? \\} [^'\"{}\\n]*\n )*\n }\n )\n",
|
||||
"captures": {
|
||||
"1": { "name": "constant.character.format.placeholder.other.gdscript" },
|
||||
"3": { "name": "storage.type.format.gdscript" },
|
||||
"4": { "name": "storage.type.format.gdscript" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nodepath_object": {
|
||||
"name": "meta.literal.nodepath.gdscript",
|
||||
"begin": "(NodePath)\\s*(?:\\()",
|
||||
@@ -125,6 +149,8 @@
|
||||
]
|
||||
},
|
||||
"nodepath_function": {
|
||||
"name": "meta.function.gdscript",
|
||||
"contentName": "meta.function.parameters.gdscript",
|
||||
"begin": "(get_node_or_null|has_node|has_node_and_resource|find_node|get_node)\\s*(\\()",
|
||||
"beginCaptures": {
|
||||
"1": { "name": "entity.name.function.gdscript" },
|
||||
@@ -143,7 +169,8 @@
|
||||
"name": "keyword.control.flow"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "include": "#base_expression" }
|
||||
]
|
||||
},
|
||||
"self": {
|
||||
@@ -154,10 +181,6 @@
|
||||
"match": "\\bfunc\\b",
|
||||
"name": "keyword.language.gdscript"
|
||||
},
|
||||
"logic_operator": {
|
||||
"match": "\\b(and|or|not|!)\\b",
|
||||
"name": "keyword.operator.wordlike.gdscript"
|
||||
},
|
||||
"in_keyword": {
|
||||
"patterns": [
|
||||
{
|
||||
@@ -180,12 +203,33 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"operators": {
|
||||
"patterns": [
|
||||
{ "include": "#wordlike_operator" },
|
||||
{ "include": "#boolean_operator" },
|
||||
{ "include": "#arithmetic_operator" },
|
||||
{ "include": "#bitwise_operator" },
|
||||
{ "include": "#compare_operator" }
|
||||
]
|
||||
},
|
||||
"wordlike_operator": {
|
||||
"match": "\\b(and|or|not)\\b",
|
||||
"name": "keyword.operator.wordlike.gdscript"
|
||||
},
|
||||
"boolean_operator": {
|
||||
"match": "(&&|\\|\\|)",
|
||||
"name": "keyword.operator.boolean.gdscript"
|
||||
},
|
||||
"bitwise_operator": {
|
||||
"match": "&|\\||<<=|>>=|<<|>>|\\^|~",
|
||||
"name": "keyword.operator.bitwise.gdscript"
|
||||
},
|
||||
"compare_operator": {
|
||||
"match": "<=|>=|==|<|>|!=",
|
||||
"match": "<=|>=|==|<|>|!=|!",
|
||||
"name": "keyword.operator.comparison.gdscript"
|
||||
},
|
||||
"arithmetic_operator": {
|
||||
"match": "->|\\+=|-=|\\*=|/=|%=|&=|\\|=|\\*|/|%|\\+|-|<<|>>|&|\\||\\^|~|!",
|
||||
"match": "->|\\+=|-=|\\*=|\\^=|/=|%=|&=|~=|\\|=|\\*\\*|\\*|/|%|\\+|-",
|
||||
"name": "keyword.operator.arithmetic.gdscript"
|
||||
},
|
||||
"assignment_operator": {
|
||||
@@ -193,11 +237,15 @@
|
||||
"name": "keyword.operator.assignment.gdscript"
|
||||
},
|
||||
"control_flow": {
|
||||
"match": "\\b(?:if|elif|else|while|break|continue|pass|return|match|yield|await)\\b",
|
||||
"match": "\\b(?:if|elif|else|while|break|continue|pass|return|when|yield|await)\\b",
|
||||
"name": "keyword.control.gdscript"
|
||||
},
|
||||
"match_keyword": {
|
||||
"match": "^\n\\s*(match)",
|
||||
"captures": { "1": { "name": "keyword.control.gdscript" } }
|
||||
},
|
||||
"keywords": {
|
||||
"match": "\\b(?:class|class_name|is|onready|tool|static|export|as|void|enum|preload|assert|breakpoint|rpc|sync|remote|master|puppet|slave|remotesync|mastersync|puppetsync|trait|namespace)\\b",
|
||||
"match": "\\b(?:class|class_name|is|onready|tool|static|export|as|void|enum|assert|breakpoint|sync|remote|master|puppet|slave|remotesync|mastersync|puppetsync|trait|namespace)\\b",
|
||||
"name": "keyword.language.gdscript"
|
||||
},
|
||||
"letter": {
|
||||
@@ -233,13 +281,11 @@
|
||||
]
|
||||
},
|
||||
"variable_declaration": {
|
||||
"name": "meta.variable.gdscript",
|
||||
"begin": "\\b(?:(var)|(const))\\s+(?:(\\b[A-Z_][A-Z_0-9]*\\b)|([A-Za-z_]\\w*))\\s*",
|
||||
"name": "meta.variable.declaration.gdscript",
|
||||
"begin": "\\b(?:(var)|(const))\\b",
|
||||
"beginCaptures": {
|
||||
"1": { "name": "keyword.language.gdscript storage.type.var.gdscript" },
|
||||
"2": { "name": "keyword.language.gdscript storage.type.const.gdscript" },
|
||||
"3": { "name": "constant.language.gdscript" },
|
||||
"4": { "name": "variable.other.gdscript" }
|
||||
"2": { "name": "keyword.language.gdscript storage.type.const.gdscript" }
|
||||
},
|
||||
"end": "$|;",
|
||||
"patterns": [
|
||||
@@ -397,7 +443,7 @@
|
||||
"name": "constant.language.gdscript"
|
||||
},
|
||||
"pascal_case_class": {
|
||||
"match": "\\b([A-Z][a-z_0-9]*([A-Z]?[a-z_0-9]+)*[A-Z]?)\\b",
|
||||
"match": "\\b([A-Z]+[a-z_0-9]*([A-Z]?[a-z_0-9]+)*[A-Z]?)\\b",
|
||||
"name": "entity.name.type.class.gdscript"
|
||||
},
|
||||
"signal_declaration_bare": {
|
||||
@@ -494,7 +540,7 @@
|
||||
"beginCaptures": {
|
||||
"1": { "name": "variable.parameter.function.language.gdscript" },
|
||||
"2": { "name": "punctuation.separator.annotation.gdscript" },
|
||||
"3": { "name": "entity.name.type.class.builtin.gdscript" }
|
||||
"3": { "name": "entity.name.type.class.gdscript" }
|
||||
},
|
||||
"end": "(,)|(?=\\))",
|
||||
"endCaptures": { "1": { "name": "punctuation.separator.parameters.gdscript" } },
|
||||
@@ -506,6 +552,26 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"curly_braces": {
|
||||
"begin": "\\{",
|
||||
"end": "\\}",
|
||||
"beginCaptures": { "0": { "name": "punctuation.definition.dict.begin.gdscript" } },
|
||||
"endCaptures": { "0": { "name": "punctuation.definition.dict.end.gdscript" } },
|
||||
"patterns": [
|
||||
{ "include": "#base_expression" },
|
||||
{ "include": "#any_variable" }
|
||||
]
|
||||
},
|
||||
"square_braces": {
|
||||
"begin": "\\[",
|
||||
"end": "\\]",
|
||||
"beginCaptures": { "0": { "name": "punctuation.definition.list.begin.gdscript" } },
|
||||
"endCaptures": { "0": { "name": "punctuation.definition.list.end.gdscript" } },
|
||||
"patterns": [
|
||||
{ "include": "#base_expression" },
|
||||
{ "include": "#any_variable" }
|
||||
]
|
||||
},
|
||||
"round_braces": {
|
||||
"begin": "\\(",
|
||||
"end": "\\)",
|
||||
@@ -535,7 +601,7 @@
|
||||
},
|
||||
"any_method": {
|
||||
"match": "\\b([A-Za-z_]\\w*)\\b(?=\\s*(?:[(]))",
|
||||
"name": "support.function.any-method.gdscript"
|
||||
"name": "entity.name.function.other.gdscript"
|
||||
},
|
||||
"any_variable": {
|
||||
"match": "\\b(?<![@\\$#%])([A-Za-z_]\\w*)\\b(?![(])",
|
||||
@@ -552,8 +618,7 @@
|
||||
"function_call": {
|
||||
"name": "meta.function-call.gdscript",
|
||||
"comment": "Regular function call of the type \"name(args)\"",
|
||||
"begin": "(?x)\n \\b(?=\n ([a-zA-Z_]\\w*) \\s* (\\()\n )\n",
|
||||
"beginCaptures": { "2": { "name": "punctuation.definition.arguments.begin.gdscript" } },
|
||||
"begin": "(?=\\b[a-zA-Z_]\\w*\\b\\()",
|
||||
"end": "(\\))",
|
||||
"endCaptures": { "1": { "name": "punctuation.definition.arguments.end.gdscript" } },
|
||||
"patterns": [
|
||||
@@ -564,17 +629,21 @@
|
||||
"function_name": {
|
||||
"patterns": [
|
||||
{ "include": "#builtin_classes" },
|
||||
{
|
||||
"match": "\\b(preload)\\b",
|
||||
"name": "keyword.language.gdscript"
|
||||
},
|
||||
{
|
||||
"comment": "Some color schemas support meta.function-call.generic scope",
|
||||
"name": "support.function.any-method.gdscript",
|
||||
"match": "(?x)\n \\b ([a-zA-Z_]\\w*) \\b\n"
|
||||
"match": "\\b([a-zA-Z_]\\w*)\\b",
|
||||
"name": "entity.name.function.gdscript"
|
||||
}
|
||||
]
|
||||
},
|
||||
"function_arguments": {
|
||||
"begin": "(\\()",
|
||||
"end": "(?=\\))(?!\\)\\s*\\()",
|
||||
"beginCaptures": { "1": { "name": "punctuation.definition.arguments.begin.gdscript" } },
|
||||
"end": "(?=\\))(?!\\)\\s*\\()",
|
||||
"contentName": "meta.function.parameters.gdscript",
|
||||
"patterns": [
|
||||
{
|
||||
|
||||
@@ -45,6 +45,12 @@ func f():
|
||||
super()
|
||||
super.some_function()
|
||||
|
||||
match param3:
|
||||
3:
|
||||
print("param3 is 3!")
|
||||
_:
|
||||
print("param3 is not 3!")
|
||||
|
||||
for i in range(1): # `in` is a control keyword
|
||||
print(i in range(1)) # `in` is an operator keyword
|
||||
|
||||
|
||||
Reference in New Issue
Block a user