浏览代码

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

David Kincaid 4 月之前
父节点
当前提交
1dcbd651df
共有 4 个文件被更改,包括 108 次插入74 次删除
  1. 4 1
      src/extension.ts
  2. 86 0
      src/providers/document_drops.ts
  3. 2 1
      src/providers/index.ts
  4. 16 72
      src/scene_tools/preview.ts

+ 4 - 1
src/extension.ts

@@ -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),
 		};

+ 86 - 0
src/providers/document_drops.ts

@@ -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}")`);
+			}
+		}
+	}
+}

+ 2 - 1
src/providers/index.ts

@@ -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";

+ 16 - 72
src/scene_tools/preview.ts

@@ -1,41 +1,36 @@
+import * as fs from "node:fs";
 import * as vscode from "vscode";
 import {
-	type TreeDataProvider,
-	type TreeDragAndDropController,
-	type ExtensionContext,
-	EventEmitter,
+	type CancellationToken,
 	type Event,
-	type TreeView,
+	EventEmitter,
+	type ExtensionContext,
+	type FileDecoration,
 	type ProviderResult,
+	type TreeDataProvider,
+	type TreeDragAndDropController,
 	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;