Skip to content

Commit 4b3e314

Browse files
feat: drag and drop file paths into terminal
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 #746, fixes #2813
1 parent 343d009 commit 4b3e314

3 files changed

Lines changed: 29 additions & 1 deletion

File tree

emain/preload.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2025, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import { contextBridge, ipcRenderer, Rectangle, WebviewTag } from "electron";
4+
import { contextBridge, ipcRenderer, Rectangle, webUtils, WebviewTag } from "electron";
55

66
// update type in custom.d.ts (ElectronApi type)
77
contextBridge.exposeInMainWorld("api", {
@@ -69,6 +69,7 @@ contextBridge.exposeInMainWorld("api", {
6969
openBuilder: (appId?: string) => ipcRenderer.send("open-builder", appId),
7070
setBuilderWindowAppId: (appId: string) => ipcRenderer.send("set-builder-window-appid", appId),
7171
doRefresh: () => ipcRenderer.send("do-refresh"),
72+
getPathForFile: (file: File): string => webUtils.getPathForFile(file),
7273
});
7374

7475
// Custom event for "new-window"

frontend/app/view/term/termwrap.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { TabRpcClient } from "@/app/store/wshrpcutil";
88
import {
99
atoms,
1010
fetchWaveFile,
11+
getApi,
1112
getOverrideConfigAtom,
1213
getSettingsKeyAtom,
1314
globalStore,
@@ -197,6 +198,31 @@ export class TermWrap {
197198
this.heldData = [];
198199
this.handleResize_debounced = debounce(50, this.handleResize.bind(this));
199200
this.terminal.open(this.connectElem);
201+
202+
this.connectElem.addEventListener("dragover", (e: DragEvent) => {
203+
e.preventDefault();
204+
if (e.dataTransfer) {
205+
e.dataTransfer.dropEffect = "copy";
206+
}
207+
});
208+
this.connectElem.addEventListener("drop", (e: DragEvent) => {
209+
e.preventDefault();
210+
if (!e.dataTransfer || e.dataTransfer.files.length === 0) {
211+
return;
212+
}
213+
const paths: string[] = [];
214+
for (let i = 0; i < e.dataTransfer.files.length; i++) {
215+
const file = e.dataTransfer.files[i];
216+
const filePath = getApi().getPathForFile(file);
217+
if (filePath) {
218+
const quoted = "'" + filePath.replace(/'/g, "'\\''") + "'";
219+
paths.push(quoted);
220+
}
221+
}
222+
if (paths.length > 0) {
223+
this.terminal.paste(paths.join(" "));
224+
}
225+
});
200226
this.handleResize();
201227
const pasteHandler = this.pasteHandler.bind(this);
202228
this.connectElem.addEventListener("paste", pasteHandler, true);

frontend/types/custom.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ declare global {
135135
openBuilder: (appId?: string) => void; // open-builder
136136
setBuilderWindowAppId: (appId: string) => void; // set-builder-window-appid
137137
doRefresh: () => void; // do-refresh
138+
getPathForFile: (file: File) => string; // webUtils.getPathForFile
138139
};
139140

140141
type ElectronContextMenuItem = {

0 commit comments

Comments
 (0)