Skip to content

feat: drag and drop file paths into terminal#2857

Open
JustHereToHelp wants to merge 1 commit intowavetermdev:mainfrom
JustHereToHelp:feat/terminal-drag-drop
Open

feat: drag and drop file paths into terminal#2857
JustHereToHelp wants to merge 1 commit intowavetermdev:mainfrom
JustHereToHelp:feat/terminal-drag-drop

Conversation

@JustHereToHelp
Copy link

@JustHereToHelp JustHereToHelp commented Feb 11, 2026

Fixes #746, fixes #2813

Drag a file from Finder into a terminal and it pastes the quoted path. Uses webUtils.getPathForFile() through a preload bridge since Electron 32 killed File.path. Handles spaces in filenames.

Needs app restart after install (preload change).

Drag files from Finder or Wave's file browser into a terminal block
to insert the quoted path. Uses webUtils.getPathForFile() through
a preload bridge since Electron 32 removed File.path.

Paths with spaces are properly shell-quoted.

Fixes wavetermdev#746, fixes wavetermdev#2813
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

Walkthrough

The pull request introduces drag-and-drop file support for the terminal by adding a new API method to retrieve file paths from File objects. The change spans three files: the electron preload layer exposes a getPathForFile method wrapping electron.webUtils, the global type definitions declare this method, and the terminal component implements drag-and-drop handlers that convert dropped files to escaped file paths and paste them into the terminal.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: drag and drop file paths into terminal' clearly and concisely summarizes the main change: implementing drag-and-drop functionality for file paths into the terminal.
Description check ✅ Passed The description is clearly related to the changeset, explaining the implementation of drag-and-drop file path insertion, the use of webUtils.getPathForFile(), handling of spaces in filenames, and the need for app restart.
Linked Issues check ✅ Passed The PR fully implements the objectives from linked issues #746 and #2813: drag-and-drop file paths into terminal from both file managers and the application's file browser, with proper path handling.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the drag-and-drop feature: preload API for file path resolution, terminal drag-drop handler, and type definitions. No unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@JustHereToHelp JustHereToHelp marked this pull request as ready for review February 26, 2026 18:53
const file = e.dataTransfer.files[i];
const filePath = getApi().getPathForFile(file);
if (filePath) {
const quoted = "'" + filePath.replace(/'/g, "'\\''") + "'";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Shell quoting issue on Windows

The POSIX-style quoting '\'' won't work correctly on Windows shells (cmd.exe, PowerShell). Windows uses different quoting rules:

  • cmd.exe: Uses " for quoting and ^ for escaping
  • PowerShell: Uses " for quoting and ` for escaping

Consider adding platform-specific quoting:

import { PLATFORM, PlatformWindows } from "@/util/platformutil";

// In the drop handler:
if (filePath) {
    let quoted: string;
    if (PLATFORM === PlatformWindows) {
        // Windows: use double quotes and escape internal quotes
        quoted = '"' + filePath.replace(/"/g, '""') + '"';
    } else {
        // POSIX: use single quotes and escape internal quotes
        quoted = "'" + filePath.replace(/'/g, "'\\''" + "'";
    }
    paths.push(quoted);
}

@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Feb 26, 2026

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
frontend/app/view/term/termwrap.ts 218 Shell quoting uses POSIX-style which won't work on Windows (cmd.exe/PowerShell)
Files Reviewed (3 files)
  • emain/preload.ts - No issues
  • frontend/app/view/term/termwrap.ts - 1 issue
  • frontend/types/custom.d.ts - No issues

Fix these issues in Kilo Cloud

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/view/term/termwrap.ts`:
- Around line 202-225: The dragover/drop listeners attached to this.connectElem
are inline and never removed; refactor by extracting the callbacks into named
bound methods (e.g., onDragOver and onDrop) or stored function properties on the
class, replace the inline addEventListener calls with those references, and in
the class dispose() (or equivalent teardown) call
this.connectElem.removeEventListener("dragover", this.onDragOver) and
removeEventListener("drop", this.onDrop) so listeners are cleaned up and not
duplicated on re-init.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 343d009 and 4b3e314.

📒 Files selected for processing (3)
  • emain/preload.ts
  • frontend/app/view/term/termwrap.ts
  • frontend/types/custom.d.ts

Comment on lines +202 to +225
this.connectElem.addEventListener("dragover", (e: DragEvent) => {
e.preventDefault();
if (e.dataTransfer) {
e.dataTransfer.dropEffect = "copy";
}
});
this.connectElem.addEventListener("drop", (e: DragEvent) => {
e.preventDefault();
if (!e.dataTransfer || e.dataTransfer.files.length === 0) {
return;
}
const paths: string[] = [];
for (let i = 0; i < e.dataTransfer.files.length; i++) {
const file = e.dataTransfer.files[i];
const filePath = getApi().getPathForFile(file);
if (filePath) {
const quoted = "'" + filePath.replace(/'/g, "'\\''") + "'";
paths.push(quoted);
}
}
if (paths.length > 0) {
this.terminal.paste(paths.join(" "));
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clean up drag/drop listeners on dispose.

These listeners are added with inline callbacks and never removed. On re-init/rebind of the same terminal element, this can duplicate drop handling.

♻️ Suggested fix
-        this.connectElem.addEventListener("dragover", (e: DragEvent) => {
+        const dragOverHandler = (e: DragEvent) => {
             e.preventDefault();
             if (e.dataTransfer) {
                 e.dataTransfer.dropEffect = "copy";
             }
-        });
-        this.connectElem.addEventListener("drop", (e: DragEvent) => {
+        };
+        const dropHandler = (e: DragEvent) => {
             e.preventDefault();
             if (!e.dataTransfer || e.dataTransfer.files.length === 0) {
                 return;
             }
             const paths: string[] = [];
             for (let i = 0; i < e.dataTransfer.files.length; i++) {
                 const file = e.dataTransfer.files[i];
                 const filePath = getApi().getPathForFile(file);
                 if (filePath) {
                     const quoted = "'" + filePath.replace(/'/g, "'\\''") + "'";
                     paths.push(quoted);
                 }
             }
             if (paths.length > 0) {
                 this.terminal.paste(paths.join(" "));
             }
-        });
+        };
+        this.connectElem.addEventListener("dragover", dragOverHandler);
+        this.connectElem.addEventListener("drop", dropHandler);
+        this.toDispose.push({
+            dispose: () => {
+                this.connectElem.removeEventListener("dragover", dragOverHandler);
+                this.connectElem.removeEventListener("drop", dropHandler);
+            },
+        });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.connectElem.addEventListener("dragover", (e: DragEvent) => {
e.preventDefault();
if (e.dataTransfer) {
e.dataTransfer.dropEffect = "copy";
}
});
this.connectElem.addEventListener("drop", (e: DragEvent) => {
e.preventDefault();
if (!e.dataTransfer || e.dataTransfer.files.length === 0) {
return;
}
const paths: string[] = [];
for (let i = 0; i < e.dataTransfer.files.length; i++) {
const file = e.dataTransfer.files[i];
const filePath = getApi().getPathForFile(file);
if (filePath) {
const quoted = "'" + filePath.replace(/'/g, "'\\''") + "'";
paths.push(quoted);
}
}
if (paths.length > 0) {
this.terminal.paste(paths.join(" "));
}
});
const dragOverHandler = (e: DragEvent) => {
e.preventDefault();
if (e.dataTransfer) {
e.dataTransfer.dropEffect = "copy";
}
};
const dropHandler = (e: DragEvent) => {
e.preventDefault();
if (!e.dataTransfer || e.dataTransfer.files.length === 0) {
return;
}
const paths: string[] = [];
for (let i = 0; i < e.dataTransfer.files.length; i++) {
const file = e.dataTransfer.files[i];
const filePath = getApi().getPathForFile(file);
if (filePath) {
const quoted = "'" + filePath.replace(/'/g, "'\\''") + "'";
paths.push(quoted);
}
}
if (paths.length > 0) {
this.terminal.paste(paths.join(" "));
}
};
this.connectElem.addEventListener("dragover", dragOverHandler);
this.connectElem.addEventListener("drop", dropHandler);
this.toDispose.push({
dispose: () => {
this.connectElem.removeEventListener("dragover", dragOverHandler);
this.connectElem.removeEventListener("drop", dropHandler);
},
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/term/termwrap.ts` around lines 202 - 225, The dragover/drop
listeners attached to this.connectElem are inline and never removed; refactor by
extracting the callbacks into named bound methods (e.g., onDragOver and onDrop)
or stored function properties on the class, replace the inline addEventListener
calls with those references, and in the class dispose() (or equivalent teardown)
call this.connectElem.removeEventListener("dragover", this.onDragOver) and
removeEventListener("drop", this.onDrop) so listeners are cleaned up and not
duplicated on re-init.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Drag and drop file / folder path to CLI Drag and Drop files support.

1 participant