diff --git a/src/extension.ts b/src/extension.ts index 15f849b..b6676d4 100644 --- a/src/extension.ts +++ b/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), }; diff --git a/src/providers/document_drops.ts b/src/providers/document_drops.ts new file mode 100644 index 0000000..d6dc1c3 --- /dev/null +++ b/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 { + // 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}")`); + } + } + } +} diff --git a/src/providers/index.ts b/src/providers/index.ts index 9373428..4b50d70 100644 --- a/src/providers/index.ts +++ b/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"; diff --git a/src/scene_tools/preview.ts b/src/scene_tools/preview.ts index 3e61ab5..c044093 100644 --- a/src/scene_tools/preview.ts +++ b/src/scene_tools/preview.ts @@ -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, TreeDragAndDropController, DocumentDropEditProvider -{ +export class ScenePreviewProvider implements TreeDataProvider, TreeDragAndDropController { public dropMimeTypes = []; public dragMimeTypes = []; private tree: TreeView; @@ -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 { + 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 { - 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;