Add scene preview panel (#413)

This commit is contained in:
Daelon Suzuka
2022-10-26 10:20:28 -04:00
committed by GitHub
parent 5a67e4c78d
commit 0636797c22
8 changed files with 655 additions and 14 deletions

View File

@@ -96,6 +96,40 @@
"dark": "resources/dark/icon_edit.svg"
}
},
{
"command": "godotTools.scenePreview.refresh",
"title": "Godot Tools: Refresh Scene Preview"
},
{
"command": "godotTools.scenePreview.pin",
"title": "Pin Scene Preview",
"icon": "resources/pin_off.svg"
},
{
"command": "godotTools.scenePreview.unpin",
"title": "Unpin Scene Preview",
"icon": "resources/pin_on.svg"
},
{
"command": "godotTools.scenePreview.goToDefinition",
"title": "Go to Definition"
},
{
"command": "godotTools.scenePreview.copyNodePath",
"title": "Copy Node Path"
},
{
"command": "godotTools.scenePreview.openScene",
"title": "Open Scene"
},
{
"command": "godotTools.scenePreview.openScript",
"title": "Open Script"
},
{
"command": "godotTools.switchSceneScript",
"title": "Godot Tools: Switch Scene/Script"
},
{
"command": "godot-tool.set_scene_file",
"title": "Set as Scene File"
@@ -109,6 +143,13 @@
"title": "Godot Tools: Copy Resource Path"
}
],
"keybindings": [
{
"command": "godotTools.switchSceneScript",
"key": "alt+o",
"when": "editorLangId == 'gdscript' && editorTextFocus || editorLangId == 'gdresource' && editorTextFocus"
}
],
"configuration": {
"type": "object",
"title": "Godot Tools configuration",
@@ -184,6 +225,20 @@
],
"default": "beside",
"description": "Where to place the native symbol windows"
},
"godot_tools.scenePreview.previewRelatedScenes": {
"enum": [
"anyFolder",
"sameFolder",
"off"
],
"enumDescriptions": [
"Attempt to preview a related scene from anywhere in the workspace.",
"Attempt to preview a related scene from the same folder.",
"Do not attempt to preview a related scene."
],
"default": "sameFolder",
"description": "Controls where the Scene Preview will search for related scenes when viewing a script file."
}
}
},
@@ -337,6 +392,15 @@
"language": "gdscript"
}
],
"viewsContainers": {
"activitybar": [
{
"id": "godotTools",
"title": "Godot Tools",
"icon": "resources/godot_icon.svg"
}
]
},
"views": {
"debug": [
{
@@ -349,9 +413,37 @@
"name": "Inspector",
"when": "inDebugMode && debugType == 'godot'"
}
],
"godotTools": [
{
"id": "scenePreview",
"name": "Scene Preview"
}
]
},
"menus": {
"commandPalette": [
{
"command": "godotTools.scenePreview.refresh",
"when": "false"
},
{
"command": "godotTools.scenePreview.pin",
"when": "false"
},
{
"command": "godotTools.scenePreview.unpin",
"when": "false"
},
{
"command": "godotTools.scenePreview.copyNodePath",
"when": "false"
},
{
"command": "godot-tool.copy_resource_path_context",
"when": "false"
}
],
"view/title": [
{
"command": "godot-tool.debugger.refresh_scene_tree",
@@ -362,6 +454,16 @@
"command": "godot-tool.debugger.refresh_inspector",
"when": "view == inspect-node",
"group": "navigation"
},
{
"command": "godotTools.scenePreview.pin",
"when": "view == scenePreview && !godotTools.context.scenePreviewPinned",
"group": "navigation"
},
{
"command": "godotTools.scenePreview.unpin",
"when": "view == scenePreview && godotTools.context.scenePreviewPinned",
"group": "navigation"
}
],
"view/item/context": [
@@ -379,6 +481,25 @@
"command": "godot-tool.debugger.edit_value",
"when": "view == inspect-node && viewItem == editable_value",
"group": "inline"
},
{
"command": "godotTools.scenePreview.goToDefinition",
"when": "view == scenePreview",
"group": "1@1"
},
{
"command": "godotTools.scenePreview.copyNodePath",
"when": "view == scenePreview"
},
{
"command": "godotTools.scenePreview.openScene",
"when": "view == scenePreview && viewItem =~ /PackedScene/",
"group": "1@2"
},
{
"command": "godotTools.scenePreview.openScript",
"when": "view == scenePreview && viewItem =~ /hasScript/",
"group": "1@2"
}
],
"explorer/context": [
@@ -400,8 +521,13 @@
"editor/context": [
{
"command": "godot-tool.open_type_documentation",
"when": "godotTools.connectedToEditor",
"when": "godotTools.context.connectedToEditor",
"group": "navigation@9"
},
{
"command": "godotTools.switchSceneScript",
"when": "editorLangId == 'gdscript' || editorLangId == 'gdresource'",
"group": "custom1@1"
}
]
}

View File

@@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" transform="translate(0 -1036.4)"><path d="m1 7v6c0 1.1046.89543 2 2 2h12v-8zm4 2h6l-3 4z" transform="translate(0 1036.4)"/><path d="m.71129 1040.4.28871 1.9791 2.2438-.3273-.81826-1.9018-1.7143.25zm3.6933-.5387.81826 1.9018 1.9791-.2887-.81826-1.9018zm3.9581-.5775.81826 1.9018 1.9791-.2887-.81826-1.9018zm3.9581-.5774.81826 1.9018 1.7143-.25-.28871-1.9791-2.2438.3273z"/></g></svg>

After

Width:  |  Height:  |  Size: 484 B

121
resources/godot_icon.svg Normal file
View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="1024"
height="1024"
id="svg3030"
version="1.1"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
sodipodi:docname="godot_icon.svg"
inkscape:export-filename="/home/akien/Projects/godot/godot.git/icon.png"
inkscape:export-xdpi="24"
inkscape:export-ydpi="24"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs3032" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.8359375"
inkscape:cx="511.40187"
inkscape:cy="416.29907"
inkscape:document-units="px"
inkscape:current-layer="g82-3"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:document-rotation="0"
inkscape:pagecheckerboard="0" />
<metadata
id="metadata3035">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-98.519719)"
style="display:inline">
<g
id="g78"
transform="matrix(4.162611,0,0,-4.162611,919.24059,771.67186)"
style="display:none;stroke-width:0.320312">
<path
d="m 0,0 c 0,0 -0.325,1.994 -0.515,1.976 l -36.182,-3.491 c -2.879,-0.278 -5.115,-2.574 -5.317,-5.459 l -0.994,-14.247 -27.992,-1.997 -1.904,12.912 c -0.424,2.872 -2.932,5.037 -5.835,5.037 h -38.188 c -2.902,0 -5.41,-2.165 -5.834,-5.037 l -1.905,-12.912 -27.992,1.997 -0.994,14.247 c -0.202,2.886 -2.438,5.182 -5.317,5.46 l -36.2,3.49 c -0.187,0.018 -0.324,-1.978 -0.511,-1.978 l -0.049,-7.83 30.658,-4.944 1.004,-14.374 c 0.203,-2.91 2.551,-5.263 5.463,-5.472 l 38.551,-2.75 c 0.146,-0.01 0.29,-0.016 0.434,-0.016 2.897,0 5.401,2.166 5.825,5.038 l 1.959,13.286 h 28.005 l 1.959,-13.286 c 0.423,-2.871 2.93,-5.037 5.831,-5.037 0.142,0 0.284,0.005 0.423,0.015 l 38.556,2.75 c 2.911,0.209 5.26,2.562 5.463,5.472 l 1.003,14.374 30.645,4.966 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.320312"
id="path80"
inkscape:connector-curvature="0" />
</g>
<g
id="g82-3"
transform="matrix(4.162611,0,0,-4.162611,104.69892,525.90697)"
style="display:inline;stroke-width:0.320312">
<path
id="path84-6"
style="fill:#478cbf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.33333"
d="m 417.61523,76.875 c -42.39201,9.424151 -84.32671,22.544942 -123.64257,42.33398 0.89912,34.71618 3.14362,67.97967 7.69336,101.76758 -15.26846,9.78214 -31.31507,18.17757 -45.57618,29.62891 -14.49005,11.14747 -29.2896,21.81231 -42.41015,34.84961 C 187.46773,268.1178 159.72502,251.82518 131.14453,237.44336 100.33705,270.59856 71.53124,306.38344 48,346.42773 c 18.493057,29.02877 38.32949,58.20569 56.69922,80.95899 v 197.7832 25.1211 22.86132 c 0.44956,0.004 0.89835,0.0209 1.34375,0.0625 l 150.66992,14.52735 c 7.89231,0.76176 14.07749,7.11449 14.62695,15.02343 l 4.64649,66.50977 131.42969,9.37891 9.05468,-61.38672 c 1.17386,-7.95891 8.00029,-13.85742 16.05078,-13.85742 h 158.96094 c 8.04633,0 14.87302,5.89851 16.04688,13.85742 l 9.05468,61.38672 131.4336,-9.37891 4.64258,-66.50977 c 0.55362,-7.90894 6.73464,-14.25751 14.62695,-15.02343 l 150.61133,-14.52735 c 0.4454,-0.0416 0.89028,-0.0583 1.33984,-0.0625 v -19.61132 l 0.0625,-0.0195 v -226.1348 c 21.2165,-26.70928 41.30684,-56.1715 56.69922,-80.95899 -23.52291,-40.04429 -52.34486,-75.82917 -83.15234,-108.98437 -28.57217,14.38182 -56.32515,30.67444 -82.53711,48.01172 -13.11639,-13.0373 -27.88953,-23.70214 -42.40039,-34.84961 -14.25695,-11.45134 -30.32318,-19.84677 -45.5625,-29.62891 4.53724,-33.78791 6.7803,-67.0514 7.68359,-101.76758 C 690.71123,99.419942 648.78198,86.299151 606.36914,76.875 c -16.9335,28.45977 -32.41939,59.27922 -45.90625,89.4082 -15.99275,-2.67239 -32.05995,-3.66203 -48.14844,-3.85351 v -0.0254 c -0.11239,0 -0.21676,0.0254 -0.3125,0.0254 -0.0999,0 -0.20478,-0.0254 -0.30468,-0.0254 v 0.0254 c -16.11763,0.19148 -32.17106,1.18112 -48.16797,3.85351 C 450.05076,136.15422 434.57371,105.33477 417.61523,76.875 Z M 298.41602,436.39844 c 50.15111,0 90.79882,40.61746 90.79882,90.75195 0,50.16779 -40.64771,90.80859 -90.79882,90.80859 -50.12617,0 -90.78711,-40.6408 -90.78711,-90.80859 0,-50.13449 40.66094,-90.75195 90.78711,-90.75195 z m 427.17773,0 c 50.122,0 90.7793,40.61746 90.7793,90.75195 0,50.16779 -40.6573,90.80859 -90.7793,90.80859 -50.15946,0 -90.80664,-40.6408 -90.80664,-90.80859 0,-50.13449 40.64718,-90.75195 90.80664,-90.75195 z m -213.59961,53.10937 c 16.14261,0 29.25391,11.90816 29.25391,26.56055 v 83.58984 c 0,14.66488 -13.1113,26.5625 -29.25391,26.5625 -16.1426,0 -29.22656,-11.89762 -29.22656,-26.5625 v -83.58984 c 0,-14.65239 13.08396,-26.56055 29.22656,-26.56055 z"
transform="matrix(0.24023383,0,0,-0.24023383,-25.152223,102.67288)" />
</g>
<g
id="g86-7"
transform="matrix(4.162611,0,0,-4.162611,784.07144,817.24284)"
style="display:inline;stroke-width:0.320312">
<path
d="m 0,0 -1.121,-16.063 c -0.135,-1.936 -1.675,-3.477 -3.611,-3.616 l -38.555,-2.751 c -0.094,-0.007 -0.188,-0.01 -0.281,-0.01 -1.916,0 -3.569,1.406 -3.852,3.33 l -2.211,14.994 H -81.09 l -2.211,-14.994 c -0.297,-2.018 -2.101,-3.469 -4.133,-3.32 l -38.555,2.751 c -1.936,0.139 -3.476,1.68 -3.611,3.616 L -130.721,0 -163.268,3.138 c 0.015,-3.498 0.06,-7.33 0.06,-8.093 0,-34.374 43.605,-50.896 97.781,-51.086 h 0.066 0.067 c 54.176,0.19 97.766,16.712 97.766,51.086 0,0.777 0.047,4.593 0.063,8.093 z"
style="fill:#478cbf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.320312"
id="path88-5"
inkscape:connector-curvature="0" />
</g>
<g
id="g90-3"
transform="matrix(4.162611,0,0,-4.162611,389.21484,625.67104)"
style="display:none;stroke-width:0.320312" />
<g
id="g94-6"
transform="matrix(4.162611,0,0,-4.162611,367.36686,631.05679)"
style="display:none;stroke-width:0.320312">
<path
d="m 0,0 c 0,-7.994 -6.479,-14.473 -14.479,-14.473 -7.996,0 -14.479,6.479 -14.479,14.473 0,7.994 6.483,14.479 14.479,14.479 C -6.479,14.479 0,7.994 0,0"
style="fill:#414042;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.320312"
id="path96-2"
inkscape:connector-curvature="0" />
</g>
<g
id="g98-9"
transform="matrix(4.162611,0,0,-4.162611,511.99336,724.73954)"
style="display:inline;stroke-width:0.320312" />
<g
id="g102-2"
transform="matrix(4.162611,0,0,-4.162611,634.78706,625.67104)"
style="display:none;stroke-width:0.320312" />
<g
id="g106-0"
transform="matrix(4.162611,0,0,-4.162611,656.64056,631.05679)"
style="display:none;stroke-width:0.320312">
<path
d="m 0,0 c 0,-7.994 6.477,-14.473 14.471,-14.473 8.002,0 14.479,6.479 14.479,14.473 0,7.994 -6.477,14.479 -14.479,14.479 C 6.477,14.479 0,7.994 0,0"
style="fill:#414042;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.320312"
id="path108-9"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

3
resources/pin_off.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="m9 1.2715-.70703.70703v1.4141l-2.1211 2.123 4.2422 4.2422 2.1211-2.1211h1.4141l.70703-.70703-5.6562-5.6582zm-3.5352 4.9512-3.5352.70703 7.0703 7.0703.70703-3.5352-4.2422-4.2422zm-1.4141 4.2422-1.4141 1.4141-.70703 2.1211 2.1211-.70703 1.4141-1.4141-1.4141-1.4141z" fill="#858585" fill-opacity=".98824" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 424 B

3
resources/pin_on.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="m9 1.2715-.70703.70703v1.4141l-2.1211 2.123 4.2422 4.2422 2.1211-2.1211h1.4141l.70703-.70703-5.6562-5.6582zm-3.5352 4.9512-3.5352.70703 7.0703 7.0703.70703-3.5352-4.2422-4.2422zm-1.4141 4.2422-1.4141 1.4141-.70703 2.1211 2.1211-.70703 1.4141-1.4141-1.4141-1.4141z" fill="#cccccc" fill-opacity=".98824" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 424 B

View File

@@ -1,9 +1,10 @@
import * as vscode from "vscode";
import * as path from 'path';
import * as fs from 'fs';
import * as path from "path";
import * as fs from "fs";
import { GDDocumentLinkProvider } from "./document_link_provider";
import { ScenePreviewProvider } from "./scene_preview_provider";
import GDScriptLanguageClient, { ClientStatus } from "./lsp/GDScriptLanguageClient";
import { get_configuration, set_configuration } from "./utils";
import { get_configuration, set_configuration, find_file, set_context } from "./utils";
const CONFIG_CONTAINER = "godot_tools";
const TOOL_NAME = "GodotTools";
@@ -13,6 +14,7 @@ export class GodotTools {
private context: vscode.ExtensionContext;
private client: GDScriptLanguageClient = null;
private linkProvider: GDDocumentLinkProvider = null;
private scenePreviewManager: ScenePreviewProvider = null;
// deprecated, need to replace with "vscode.workspace.workspaceFolders", but
// that's an array and not a single value
@@ -29,7 +31,7 @@ export class GodotTools {
this.connection_status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
this.linkProvider = new GDDocumentLinkProvider(p_context);
setInterval(() => {
this.retry_callback();
}, get_configuration("reconnect_cooldown", 3000));
@@ -50,17 +52,20 @@ export class GodotTools {
vscode.commands.registerCommand("godot-tool.copy_resource_path_context", this.copy_resource_path.bind(this));
vscode.commands.registerCommand("godot-tool.copy_resource_path", this.copy_resource_path.bind(this));
vscode.commands.registerCommand("godot-tool.open_type_documentation", this.open_type_documentation.bind(this));
vscode.commands.registerCommand("godotTools.switchSceneScript", this.switch_scene_script.bind(this));
vscode.commands.executeCommand('setContext', 'godotTools.connectedToEditor', false);
set_context("godotTools.context.connectedToEditor", false);
this.scenePreviewManager = new ScenePreviewProvider();
this.connection_status.text = "$(sync) Initializing";
this.connection_status.command = "godot-tool.check_status";
this.connection_status.show();
// TODO: maybe cache this result somehow
const klaw = require('klaw');
const klaw = require("klaw");
klaw(this.workspace_dir)
.on('data', item => {
.on("data", item => {
if (path.basename(item.path) == this.project_file_name) {
this.project_dir = path.dirname(item.path);
this.project_file = item.path;
@@ -104,7 +109,7 @@ export class GodotTools {
let relative_path = path.normalize(path.relative(this.project_dir, uri.fsPath));
relative_path = relative_path.split(path.sep).join(path.posix.sep);
relative_path = 'res://' + relative_path;
relative_path = "res://" + relative_path;
vscode.env.clipboard.writeText(relative_path);
}
@@ -120,6 +125,21 @@ export class GodotTools {
this.client.open_documentation(symbolName);
}
private async switch_scene_script() {
let path = vscode.window.activeTextEditor.document.uri.fsPath;
if (path.endsWith(".tscn")) {
path = path.replace(".tscn", ".gd");
} else if (path.endsWith(".gd")) {
path = path.replace(".gd", ".tscn");
}
const file = await find_file(path);
if (file) {
vscode.window.showTextDocument(file);
}
}
private set_scene_file(uri: vscode.Uri) {
let right_clicked_scene_path = uri.fsPath;
let scene_config = get_configuration("scene_file_config");
@@ -245,7 +265,7 @@ export class GodotTools {
break;
case ClientStatus.CONNECTED:
this.retry = false;
vscode.commands.executeCommand('setContext', 'godotTools.connectedToEditor', true);
set_context("godotTools.context.connectedToEditor", true);
this.connection_status.text = `$(check) Connected`;
this.connection_status.tooltip = `Connected to the GDScript language server.`;
if (!this.client.started) {
@@ -257,7 +277,7 @@ export class GodotTools {
this.connection_status.text = `$(sync) Connecting ` + this.reconnection_attempts;
this.connection_status.tooltip = `Connecting to the GDScript language server...`;
} else {
vscode.commands.executeCommand('setContext', 'godotTools.connectedToEditor', false);
set_context("godotTools.context.connectedToEditor", false);
this.connection_status.text = `$(x) Disconnected`;
this.connection_status.tooltip = `Disconnected from the GDScript language server.`;
}
@@ -294,11 +314,11 @@ export class GodotTools {
let host = get_configuration("gdscript_lsp_server_host", "localhost");
let port = get_configuration("gdscript_lsp_server_port", 6008);
let message = `Couldn't connect to the GDScript language server at ${host}:${port}. Is the Godot editor running?`;
vscode.window.showErrorMessage(message, 'Open Godot Editor', 'Retry', 'Ignore').then(item => {
if (item == 'Retry') {
vscode.window.showErrorMessage(message, "Open Godot Editor", "Retry", "Ignore").then(item => {
if (item == "Retry") {
this.reconnection_attempts = 0;
this.client.connect_to_server();
} else if (item == 'Open Godot Editor') {
} else if (item == "Open Godot Editor") {
this.client.status = ClientStatus.PENDING;
this.open_workspace_with_editor("-e").then(() => {
setTimeout(() => {

View File

@@ -0,0 +1,339 @@
import {
TreeDataProvider,
EventEmitter,
Event,
TreeView,
ProviderResult,
TreeItem,
TreeItemCollapsibleState,
} from "vscode";
import path = require("path");
import fs = require("fs");
import * as vscode from "vscode";
import { get_configuration, set_configuration, find_file, set_context, convert_resource_path_to_uri } from "./utils";
import logger from "./logger";
function log(...messages) {
logger.log("[scene preview]", messages);
}
export class ScenePreviewProvider implements TreeDataProvider<SceneNode> {
private root: SceneNode | undefined;
private tree: TreeView<SceneNode>;
private scenePreviewPinned = false;
private currentScene = "";
private externalResources = {};
private changeEvent = new EventEmitter<void>();
public get onDidChangeTreeData(): Event<void> {
return this.changeEvent.event;
}
public async refresh() {
if (this.scenePreviewPinned) {
return;
}
const editor = vscode.window.activeTextEditor;
if (editor) {
let fileName = editor.document.uri.fsPath;
const mode = get_configuration("scenePreview.previewRelatedScenes");
if (!fileName.endsWith(".tscn")) {
const searchName = fileName.replace(".gd", ".tscn");
if (mode == "anyFolder") {
const relatedScene = await find_file(searchName);
if (!relatedScene) {
return;
}
fileName = relatedScene.fsPath;
}
if (mode == "sameFolder") {
if (fs.existsSync(searchName)) {
fileName = searchName;
} else {
return;
}
}
if (mode == "off") {
return;
}
}
if (this.currentScene == fileName) {
return;
}
await this.parse_scene(fileName);
this.changeEvent.fire();
}
}
constructor() {
this.tree = vscode.window.createTreeView("scenePreview", {
treeDataProvider: this,
});
this.tree.onDidChangeSelection(this.tree_selection_changed);
vscode.commands.registerCommand("godotTools.scenePreview.pin", this.pin_preview.bind(this));
vscode.commands.registerCommand("godotTools.scenePreview.unpin", this.unpin_preview.bind(this));
vscode.commands.registerCommand("godotTools.scenePreview.copyNodePath", this.copy_node_path.bind(this));
vscode.commands.registerCommand("godotTools.scenePreview.openScene", this.open_scene.bind(this));
vscode.commands.registerCommand("godotTools.scenePreview.openScript", this.open_script.bind(this));
vscode.commands.registerCommand("godotTools.scenePreview.goToDefinition", this.go_to_definition.bind(this));
vscode.commands.registerCommand("godotTools.scenePreview.refresh", () =>
this.refresh()
);
vscode.window.onDidChangeActiveTextEditor(() => {
vscode.commands.executeCommand("godotTools.scenePreview.refresh");
});
this.refresh();
}
private pin_preview() {
this.scenePreviewPinned = true;
set_context("godotTools.context.scenePreviewPinned", true);
}
private unpin_preview() {
this.scenePreviewPinned = false;
set_context("godotTools.context.scenePreviewPinned", false);
this.refresh();
}
private copy_node_path(item: SceneNode) {
if (item.unique) {
vscode.env.clipboard.writeText("%" + item.label);
return;
}
vscode.env.clipboard.writeText(item.relativePath);
}
private async open_scene(item: SceneNode) {
const uri = await convert_resource_path_to_uri(item.resourcePath);
if (uri) {
vscode.window.showTextDocument(uri, {preview:true});
}
}
private async open_script(item: SceneNode) {
const id = this.externalResources[item.scriptId].path;
const uri = await convert_resource_path_to_uri(id);
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);
const end = document.positionAt(item.position + item.text.length);
const range = new vscode.Range(start, end);
vscode.window.showTextDocument(document, {selection:range});
}
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)
}
public async parse_scene(scene: string) {
this.currentScene = scene;
this.tree.message = path.basename(scene);
const document = await vscode.workspace.openTextDocument(scene);
const text = document.getText();
this.externalResources = {};
const resourceRegex = /\[ext_resource path="([\w.:/]*)" type="([\w]*)" id=([0-9]*)/g;
for (const match of text.matchAll(resourceRegex)) {
let path = match[1];
let type = match[2];
let id = match[3];
this.externalResources[id] = {
path: path,
type: type,
id: id,
};
}
let root = "";
let nodes = {};
let lastNode = null;
const nodeRegex = /\[node name="([\w]*)"(?: type="([\w]*)")?(?: parent="([\w\/.]*)")?(?: instance=ExtResource\( ([\w\.]*) \))?\]/g;
for (const match of text.matchAll(nodeRegex)) {
let name = match[1];
let type = match[2] ? match[2] : "PackedScene";
let parent = match[3];
let instance = match[4] ? match[4] : 0;
let path = "";
let relativePath = "";
if (parent == undefined) {
root = name;
path = name;
} else if (parent == ".") {
parent = root;
relativePath = name;
path = parent + "/" + name;
} else {
relativePath = parent + "/" + name;
parent = root + "/" + parent;
path = parent + "/" + name;
}
if (lastNode) {
lastNode.body = text.slice(lastNode.position, match.index);
lastNode.parse_body();
}
let node = new SceneNode(name, type, 0, []);
node.path = path;
node.description = type;
node.relativePath = relativePath;
node.parent = parent;
node.text = match[0];
node.position = match.index;
if (instance) {
node.tooltip = this.externalResources[instance].path;
node.resourcePath = this.externalResources[instance].path;
node.contextValue = "PackedScene";
}
if (path == root) {
this.root = node;
}
if (parent in nodes) {
nodes[parent].children.push(node);
}
nodes[path] = node;
lastNode = node;
}
lastNode.body = text.slice(lastNode.position, text.length);
lastNode.parse_body();
}
public getChildren(element?: SceneNode): ProviderResult<SceneNode[]> {
if (!element) {
if (!this.root) {
return Promise.resolve([]);
} else {
return Promise.resolve([this.root]);
}
} else {
return Promise.resolve(element.children);
}
}
public getTreeItem(element: SceneNode): TreeItem | Thenable<TreeItem> {
if (element.children.length > 0) {
element.collapsibleState = TreeItemCollapsibleState.Expanded;
} else {
element.collapsibleState = TreeItemCollapsibleState.None;
}
return element;
}
}
function match_icon_to_class(class_name: string) {
let icon_name = `icon${class_name
.replace(/(2|3)D/, "$1d")
.replace(/([A-Z0-9])/g, "_$1")
.toLowerCase()}.svg`;
return icon_name;
}
export class SceneNode extends TreeItem {
public path: string;
public relativePath: string;
public resourcePath: string;
public parent: string;
public text: string;
public position: number;
public body: string;
public unique: boolean = false;
public hasScript: boolean = false;
public scriptId: string;
constructor(
public label: string,
public class_name: string,
public object_id: number,
public children: SceneNode[],
public collapsibleState?: TreeItemCollapsibleState
) {
super(label, collapsibleState);
const iconDir = path.join(__filename, "..", "..", "resources");
if (class_name == "PackedScene") {
this.iconPath = path.join(iconDir, "InstanceOptions.svg");
return;
}
const iconName = match_icon_to_class(class_name);
let light = path.join(iconDir, "light", iconName);
if (!fs.existsSync(light)) {
light = path.join(iconDir, "light", "node.svg");
}
let dark = path.join(iconDir, "dark", iconName);
if (!fs.existsSync(dark)) {
dark = path.join(iconDir, "dark", "node.svg");
}
this.iconPath = {
light: light,
dark: dark,
};
}
public parse_body() {
const lines = this.body.split("\n");
let newLines = [];
let tags = [];
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (line.startsWith("tile_data")) {
line = "tile_data = PoolIntArray(...)";
}
if (line.startsWith("unique_name_in_owner = true")) {
tags.push("%");
this.unique = true;
}
if (line.startsWith("script = ExtResource")) {
tags.push("S");
this.hasScript = true;
this.scriptId = line.match(/script = ExtResource\( ([0-9]+) \)/)[1];
this.contextValue += "hasScript";
}
if (line != "") {
newLines.push(line);
}
}
this.body = newLines.join("\n");
let prefix = ""
if (tags.length != 0) {
prefix = tags.join(" ") + " | "
}
this.description = prefix + this.description;
const content = new vscode.MarkdownString();
content.appendCodeblock(this.body, "gdresource");
this.tooltip = content;
}
}

View File

@@ -1,4 +1,6 @@
import * as vscode from "vscode";
import * as path from "path";
import * as fs from "fs";
const CONFIG_CONTAINER = "godot_tools";
@@ -13,3 +15,29 @@ export function set_configuration(name: string, value: any) {
export function is_debug_mode(): boolean {
return process.env.VSCODE_DEBUG_MODE === "true";
}
export function set_context(name: string, value: any) {
vscode.commands.executeCommand("setContext", name, value);
}
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];
}
}
return null;
}
export async function convert_resource_path_to_uri(resPath: string): Promise<vscode.Uri|null> {
const files = await vscode.workspace.findFiles("**/project.godot");
if (!files) {
return null;
}
const project_dir = files[0].fsPath.replace("project.godot", "");
return vscode.Uri.joinPath(vscode.Uri.file(project_dir), resPath.substring(6));
}