@@ -18,6 +18,12 @@ import org.jetbrains.plugins.terminal.LocalTerminalDirectRunner
1818import org.jetbrains.plugins.terminal.ShellStartupOptions
1919import org.jetbrains.plugins.terminal.ShellTerminalWidget
2020import java.awt.BorderLayout
21+ import java.awt.datatransfer.DataFlavor
22+ import java.awt.dnd.DnDConstants
23+ import java.awt.dnd.DropTarget
24+ import java.awt.dnd.DropTargetAdapter
25+ import java.awt.dnd.DropTargetDropEvent
26+ import java.io.File
2127import java.lang.reflect.Proxy
2228import javax.swing.JPanel
2329
@@ -92,14 +98,17 @@ class ClassicTuiPanel(
9298 Disposer .register(this , widget)
9399 terminalPanel =
94100 ShellTerminalWidget .asShellJediTermWidget(widget)?.terminalPanel
95- ?.also { installEmbeddedTerminalDataProvider(project, it) }
101+ ?.also { panel ->
102+ installEmbeddedTerminalDataProvider(project, panel)
103+ installFileDropTarget(panel)
104+ }
96105
97106 // When the shell process exits, clean up and notify the owner.
98107 widget.addTerminationCallback({
99108 ApplicationManager .getApplication().invokeLater {
100109 logger.debug(" Classic terminal process terminated" )
101110 if (terminalWidget == = widget) {
102- uninstallEmbeddedTerminalDataProvider ()
111+ uninstallEmbeddedTerminalIntegrations ()
103112 terminalWidget = null
104113 remove(widget.component)
105114 revalidate()
@@ -133,7 +142,7 @@ class ClassicTuiPanel(
133142
134143 private fun tearDown () {
135144 val widget = terminalWidget ? : return
136- uninstallEmbeddedTerminalDataProvider ()
145+ uninstallEmbeddedTerminalIntegrations ()
137146 terminalWidget = null
138147 remove(widget.component)
139148 revalidate()
@@ -148,11 +157,55 @@ class ClassicTuiPanel(
148157
149158 override fun dispose () = tearDown()
150159
151- private fun uninstallEmbeddedTerminalDataProvider () {
152- terminalPanel?.let (::uninstallTerminalToolWindowOverride)
160+ private fun uninstallEmbeddedTerminalIntegrations () {
161+ terminalPanel?.let { panel ->
162+ uninstallEmbeddedTerminalDataProvider(panel)
163+ panel.dropTarget = null
164+ }
153165 terminalPanel = null
154166 }
155167
168+ private fun installFileDropTarget (panel : JBTerminalPanel ) {
169+ panel.dropTarget = DropTarget (panel, DnDConstants .ACTION_COPY , object : DropTargetAdapter () {
170+ override fun drop (event : DropTargetDropEvent ) {
171+ if (! event.isDataFlavorSupported(DataFlavor .javaFileListFlavor)) {
172+ event.rejectDrop()
173+ return
174+ }
175+
176+ event.acceptDrop(DnDConstants .ACTION_COPY )
177+ try {
178+ val files = droppedFiles(event)
179+ if (files.isEmpty()) {
180+ event.dropComplete(false )
181+ return
182+ }
183+
184+ panel.requestFocusInWindow()
185+ sendBracketedPaste(panel, files.joinToString(" " ) { file -> shellQuoteIfNeeded(file.absolutePath) })
186+ event.dropComplete(true )
187+ } catch (e: Exception ) {
188+ logger.warn(" Failed to handle dropped file for OpenCode TUI" , e)
189+ event.dropComplete(false )
190+ }
191+ }
192+ }, true )
193+ }
194+
195+ private fun sendBracketedPaste (panel : JBTerminalPanel , text : String ) {
196+ panel.terminalOutputStream.sendString(" \u001b [200~$text \u001b [201~" , false )
197+ }
198+
199+ private fun shellQuoteIfNeeded (path : String ): String {
200+ if (path.none { it.isWhitespace() }) return path
201+ return " '${path.replace(" '" , " '\\ ''" )} '"
202+ }
203+
204+ private fun droppedFiles (event : DropTargetDropEvent ): List <File > {
205+ val data = event.transferable.getTransferData(DataFlavor .javaFileListFlavor) as ? List <* > ? : return emptyList()
206+ return data.filterIsInstance<File >()
207+ }
208+
156209 companion object {
157210 private val logger = Logger .getInstance(ClassicTuiPanel ::class .java)
158211
0 commit comments