Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add snippet/placeholder behavior to Scene Preview file drops #770

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
GDHoverProvider,
GDDocumentLinkProvider,
GDSemanticTokensProvider,
GDDocumentDropEditProvider,
GDCompletionItemProvider,
GDDocumentationProvider,
GDDefinitionProvider,
Expand Down Expand Up @@ -34,6 +35,7 @@ interface Extension {
debug?: GodotDebugger;
scenePreviewProvider?: ScenePreviewProvider;
linkProvider?: GDDocumentLinkProvider;
dropsProvider?: GDDocumentDropEditProvider;
hoverProvider?: GDHoverProvider;
inlayProvider?: GDInlayHintsProvider;
formattingProvider?: FormattingProvider;
Expand All @@ -59,6 +61,7 @@ export function activate(context: vscode.ExtensionContext) {
globals.formattingProvider = new FormattingProvider(context);
globals.docsProvider = new GDDocumentationProvider(context);
globals.definitionProvider = new GDDefinitionProvider(context);
globals.dropsProvider = new GDDocumentDropEditProvider(context);
// globals.semanticTokensProvider = new GDSemanticTokensProvider(context);
// globals.completionProvider = new GDCompletionItemProvider(context);
// globals.tasksProvider = new GDTaskProvider(context);
Expand Down
86 changes: 86 additions & 0 deletions src/providers/document_drops.ts
Original file line number Diff line number Diff line change
@@ -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, projectVersion } 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 provideDocumentDropEdits(
document: TextDocument,
position: Position,
dataTransfer: DataTransfer,
token: CancellationToken,
): ProviderResult<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 (projectVersion?.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}")`);
}
}
}
}
3 changes: 2 additions & 1 deletion src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -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";
88 changes: 16 additions & 72 deletions src/scene_tools/preview.ts
Original file line number Diff line number Diff line change
@@ -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>;
Expand All @@ -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)),
Expand All @@ -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),
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/project_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export async function get_project_file(): Promise<string | undefined> {
return projectFile;
}

let projectVersion: string | undefined = undefined;
export let projectVersion: string | undefined = undefined;

export async function get_project_version(): Promise<string | undefined> {
if (projectDir === undefined || projectFile === undefined) {
Expand Down
Loading