Add snippet/placeholder behavior to Scene Preview file drops (#813)

This commit is contained in:
David Kincaid
2025-03-04 19:11:27 -05:00
committed by GitHub
parent 54f68f15ea
commit 1dcbd651df
4 changed files with 108 additions and 74 deletions

View File

@@ -5,6 +5,7 @@ import { attemptSettingsUpdate, get_extension_uri, clean_godot_path } from "./ut
import {
GDInlayHintsProvider,
GDHoverProvider,
GDDocumentDropEditProvider,
GDDocumentLinkProvider,
GDSemanticTokensProvider,
GDCompletionItemProvider,
@@ -36,6 +37,7 @@ interface Extension {
debug?: GodotDebugger;
scenePreviewProvider?: ScenePreviewProvider;
linkProvider?: GDDocumentLinkProvider;
dropsProvider?: GDDocumentDropEditProvider;
hoverProvider?: GDHoverProvider;
inlayProvider?: GDInlayHintsProvider;
formattingProvider?: FormattingProvider;
@@ -56,6 +58,7 @@ export function activate(context: vscode.ExtensionContext) {
globals.debug = new GodotDebugger(context);
globals.scenePreviewProvider = new ScenePreviewProvider(context);
globals.linkProvider = new GDDocumentLinkProvider(context);
globals.dropsProvider = new GDDocumentDropEditProvider(context);
globals.hoverProvider = new GDHoverProvider(context);
globals.inlayProvider = new GDInlayHintsProvider(context);
globals.formattingProvider = new FormattingProvider(context);
@@ -213,7 +216,7 @@ async function open_godot_editor_settings() {
const choices: vscode.QuickPickItem[] = [];
for (const file of files) {
const pick: vscode.QuickPickItem = {
const pick: vscode.QuickPickItem = {
label: file,
description: path.join(dir, file),
};

View File

@@ -0,0 +1,86 @@
import * as vscode from "vscode";
import {
CancellationToken,
DataTransfer,
DocumentDropEdit,
DocumentDropEditProvider,
ExtensionContext,
languages,
Position,
ProviderResult,
Range,
TextDocument,
Uri,
} from "vscode";
import { createLogger, node_name_to_snake, get_project_version } from "../utils";
const log = createLogger("providers.drops");
export class GDDocumentDropEditProvider implements DocumentDropEditProvider {
constructor(private context: ExtensionContext) {
const dropEditSelector = [
{ language: "csharp", scheme: "file" },
{ language: "gdscript", scheme: "file" },
];
context.subscriptions.push(languages.registerDocumentDropEditProvider(dropEditSelector, this));
}
public async provideDocumentDropEdits(
document: TextDocument,
position: Position,
dataTransfer: DataTransfer,
token: CancellationToken,
): Promise<DocumentDropEdit> {
// log.debug("provideDocumentDropEdits", document, dataTransfer);
// const origin = dataTransfer.get("text/plain").value;
// log.debug(origin);
// 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?
const className: string = dataTransfer.get("godot/class")?.value;
if (className) {
const path: string = dataTransfer.get("godot/path")?.value;
const unique = dataTransfer.get("godot/unique")?.value === "true";
const label: string = dataTransfer.get("godot/label")?.value;
// For the root node, the path is empty and needs to be replaced with the node name
const savePath = path || label;
if (document.languageId === "gdscript") {
let qualifiedPath = `$${savePath}`;
if (unique) {
// For unique nodes, we can use the % syntax and drop the full path
qualifiedPath = `%${label}`;
}
const line = document.lineAt(position.line);
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
const snippet = new vscode.SnippetString();
if ((await get_project_version())?.startsWith("4")) {
snippet.appendText("@");
}
snippet.appendText("onready var ");
snippet.appendPlaceholder(node_name_to_snake(label));
snippet.appendText(`: ${className} = ${qualifiedPath}`);
return new vscode.DocumentDropEdit(snippet);
}
// 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}>("${savePath}")`);
}
}
}
}

View File

@@ -1,8 +1,9 @@
export * from "./completions";
export * from "./definition";
export * from "./document_drops";
export * from "./document_link";
export * from "./documentation";
export * from "./hover";
export * from "./inlay_hints";
export * from "./semantic_tokens";
export * from "./documentation";
export * from "./tasks";

View File

@@ -1,41 +1,36 @@
import * as fs from "node:fs";
import * as vscode from "vscode";
import {
type CancellationToken,
type Event,
EventEmitter,
type ExtensionContext,
type FileDecoration,
type ProviderResult,
type TreeDataProvider,
type TreeDragAndDropController,
type ExtensionContext,
EventEmitter,
type Event,
type TreeView,
type ProviderResult,
type TreeItem,
TreeItemCollapsibleState,
window,
languages,
type TreeView,
type Uri,
type CancellationToken,
type FileDecoration,
type DocumentDropEditProvider,
window,
workspace,
} from "vscode";
import * as fs from "node:fs";
import {
get_configuration,
find_file,
set_context,
convert_resource_path_to_uri,
register_command,
createLogger,
find_file,
get_configuration,
make_docs_uri,
node_name_to_snake,
register_command,
set_context,
} from "../utils";
import { SceneParser } from "./parser";
import type { SceneNode, Scene } from "./types";
import type { Scene, SceneNode } from "./types";
const log = createLogger("scenes.preview");
export class ScenePreviewProvider
implements TreeDataProvider<SceneNode>, TreeDragAndDropController<SceneNode>, DocumentDropEditProvider
{
export class ScenePreviewProvider implements TreeDataProvider<SceneNode>, TreeDragAndDropController<SceneNode> {
public dropMimeTypes = [];
public dragMimeTypes = [];
private tree: TreeView<SceneNode>;
@@ -58,10 +53,6 @@ export class ScenePreviewProvider
dragAndDropController: this,
});
const selector = [
{ language: "csharp", scheme: "file" },
{ language: "gdscript", scheme: "file" },
];
context.subscriptions.push(
register_command("scenePreview.lock", this.lock_preview.bind(this)),
register_command("scenePreview.unlock", this.unlock_preview.bind(this)),
@@ -77,7 +68,6 @@ export class ScenePreviewProvider
window.onDidChangeActiveTextEditor(this.refresh.bind(this)),
window.registerFileDecorationProvider(this.uniqueDecorator),
window.registerFileDecorationProvider(this.scriptDecorator),
languages.registerDocumentDropEditProvider(selector, this),
this.watcher.onDidChange(this.on_file_changed.bind(this)),
this.watcher,
this.tree.onDidChangeSelection(this.tree_selection_changed),
@@ -92,59 +82,13 @@ export class ScenePreviewProvider
data: vscode.DataTransfer,
token: vscode.CancellationToken,
): void | Thenable<void> {
data.set("godot/scene", new vscode.DataTransferItem(this.currentScene));
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: 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 (document.languageId === "gdscript") {
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}\n`,
);
}
// 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}>("${savePath}")`);
}
}
}
public async on_file_changed(uri: vscode.Uri) {
if (!uri.fsPath.endsWith(".tscn")) {
return;