diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/TerminalView.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/TerminalView.kt index 476dc885f417..c43a7296e844 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/TerminalView.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/TerminalView.kt @@ -2,6 +2,7 @@ package com.intellij.terminal.frontend.view import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.DataKey +import com.intellij.platform.eel.annotations.NativePath import com.intellij.terminal.TerminalTitle import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred @@ -16,6 +17,7 @@ import org.jetbrains.plugins.terminal.view.TerminalOutputModelsSet import org.jetbrains.plugins.terminal.view.TerminalSendTextBuilder import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalShellIntegration import java.awt.event.KeyEvent +import java.nio.file.Path import javax.swing.JComponent @ApiStatus.Internal @@ -98,6 +100,17 @@ interface TerminalView { */ val keyEventsFlow: Flow + /** + * Absolute [Path] of the current working directory of the terminal process. + * + * When [TerminalShellIntegration] is available, it is used to track the working directory. + * Otherwise, the OS-specific heuristics and polling approach will be used. + * + * Can be null if process is not started yet. + * Or if shell integration is not available and OS-specific heuristics fail to determine the working directory of the process. + */ + val workingDirectoryFlow: StateFlow + /** * Can be used to get or await the shell integration initialization. * @@ -144,7 +157,8 @@ interface TerminalView { * Returns null if the shell process is not connected yet to the [TerminalView] * or if the initial value is not yet received from the backend. */ - fun getCurrentDirectory(): String? + @Deprecated("Use workingDirectoryFlow instead.", ReplaceWith("workingDirectoryFlow.value")) + fun getCurrentDirectory(): @NativePath String? /** * A shortcut to schedule sending the specified text to the shell process diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalToolWindowSplitContentProvider.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalToolWindowSplitContentProvider.kt index 7cb106124c76..cf92f46162f7 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalToolWindowSplitContentProvider.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalToolWindowSplitContentProvider.kt @@ -1,12 +1,9 @@ package com.intellij.terminal.frontend.view.impl import com.intellij.openapi.project.Project -import com.intellij.platform.eel.path.EelPath -import com.intellij.platform.eel.provider.asNioPath import com.intellij.terminal.frontend.toolwindow.TerminalToolWindowTabsManager import com.intellij.terminal.frontend.toolwindow.findTabByContent import com.intellij.terminal.frontend.toolwindow.impl.shouldUseReworkedTerminal -import com.intellij.terminal.frontend.view.TerminalView import com.intellij.toolWindow.ToolWindowSplitContentProvider import com.intellij.ui.content.Content import org.jetbrains.plugins.terminal.TerminalOptionsProvider @@ -14,8 +11,6 @@ import org.jetbrains.plugins.terminal.TerminalTabState import org.jetbrains.plugins.terminal.TerminalToolWindowManager import org.jetbrains.plugins.terminal.fus.TerminalStartupFusInfo import org.jetbrains.plugins.terminal.fus.TerminalTabOpeningWay -import org.jetbrains.plugins.terminal.util.getNow -import java.nio.file.Path internal class TerminalToolWindowSplitContentProvider : ToolWindowSplitContentProvider { override fun createContentCopy(project: Project, content: Content): Content { @@ -32,7 +27,7 @@ internal class TerminalToolWindowSplitContentProvider : ToolWindowSplitContentPr private fun createReworkedTerminalContent(project: Project, content: Content, fusInfo: TerminalStartupFusInfo): Content { val manager = TerminalToolWindowTabsManager.getInstance(project) val originalView = manager.findTabByContent(content)?.view - val currentDirectory = originalView?.getCurrentDirectoryPath() + val currentDirectory = originalView?.workingDirectoryFlow?.value return manager.createTabBuilder() .workingDirectory(currentDirectory?.toString()) @@ -42,14 +37,6 @@ internal class TerminalToolWindowSplitContentProvider : ToolWindowSplitContentPr .content } - private fun TerminalView.getCurrentDirectoryPath(): Path? { - val currentDirectory = getCurrentDirectory() ?: return null - val session = sessionDeferred.getNow() ?: return null - return runCatching { - EelPath.parse(currentDirectory, session.eelDescriptor).asNioPath() - }.getOrNull() - } - private fun createClassicTerminalContent( project: Project, originalContent: Content, diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalViewImpl.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalViewImpl.kt index 5f8625f1d750..dd525e599c95 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalViewImpl.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalViewImpl.kt @@ -21,6 +21,7 @@ import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.wm.IdeFocusManager import com.intellij.platform.eel.provider.LocalEelDescriptor import com.intellij.platform.util.coroutines.childScope +import com.intellij.platform.util.coroutines.flow.mapStateIn import com.intellij.psi.PsiDocumentManager import com.intellij.terminal.JBTerminalSystemSettingsProviderBase import com.intellij.terminal.TerminalTitle @@ -91,6 +92,7 @@ import org.jetbrains.plugins.terminal.hyperlinks.session.TerminalHyperlinksSessi import org.jetbrains.plugins.terminal.session.TerminalGridSize import org.jetbrains.plugins.terminal.session.TerminalStartupOptions import org.jetbrains.plugins.terminal.session.impl.TerminalSession +import org.jetbrains.plugins.terminal.util.convertNativePathToNioPath import org.jetbrains.plugins.terminal.util.getNow import org.jetbrains.plugins.terminal.view.TerminalContentChangeEvent import org.jetbrains.plugins.terminal.view.TerminalCursorOffsetChangeEvent @@ -117,6 +119,7 @@ import java.awt.event.ComponentEvent import java.awt.event.FocusEvent import java.awt.event.FocusListener import java.awt.event.KeyEvent +import java.nio.file.Path import javax.swing.JComponent import javax.swing.JPanel import kotlin.coroutines.cancellation.CancellationException @@ -181,6 +184,8 @@ class TerminalViewImpl( override val keyEventsFlow: Flow = mutableKeyEventsFlow.asSharedFlow() private val inputInterceptors = ContainerUtil.createLockFreeCopyOnWriteList() + override val workingDirectoryFlow: StateFlow + override val shellIntegrationDeferred: CompletableDeferred = CompletableDeferred(coroutineScope.coroutineContext.job) override val startupOptionsDeferred: CompletableDeferred = @@ -191,6 +196,12 @@ class TerminalViewImpl( init { sessionModel = TerminalSessionModelImpl() + workingDirectoryFlow = sessionModel.terminalState.mapStateIn(coroutineScope.childScope("workingDirectoryFlow")) { + val directoryString = it.currentDirectory ?: return@mapStateIn null + val eelDescriptor = sessionDeferred.getNow()?.eelDescriptor ?: return@mapStateIn null + convertNativePathToNioPath(directoryString, eelDescriptor) + } + encodingManager = TerminalKeyEncodingManager(sessionModel, coroutineScope.childScope("TerminalKeyEncodingManager")) terminalInput = TerminalInput( @@ -750,7 +761,7 @@ class TerminalViewImpl( override fun toString(): String { val commandText = startupOptionsDeferred.getNow()?.let { "${it.shellCommand}" } - return "TerminalViewImpl(state=${sessionState.value}, command=$commandText, cwd=${getCurrentDirectory()})" + return "TerminalViewImpl(state=${sessionState.value}, command=$commandText, cwd=${workingDirectoryFlow.value})" } private inner class TerminalPanel(initialContent: Editor) : BorderLayoutPanel(), UiDataProvider, TerminalPanelMarker { diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/util/TerminalUtil.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/util/TerminalUtil.kt index d55117e5ed5f..72a2b8c24fd5 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/util/TerminalUtil.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/util/TerminalUtil.kt @@ -13,6 +13,11 @@ import com.intellij.openapi.keymap.Keymap import com.intellij.openapi.keymap.KeymapManager import com.intellij.openapi.keymap.ex.KeymapManagerEx import com.intellij.openapi.util.Disposer +import com.intellij.platform.eel.EelDescriptor +import com.intellij.platform.eel.annotations.NativePath +import com.intellij.platform.eel.path.EelPath +import com.intellij.platform.eel.path.EelPathException +import com.intellij.platform.eel.provider.asNioPath import com.intellij.util.concurrency.annotations.RequiresBackgroundThread import com.intellij.util.concurrency.annotations.RequiresReadLockAbsence import com.intellij.util.io.awaitExit @@ -250,3 +255,22 @@ internal fun String?.toExistentNioDirectory(labelToLogOnFailure: String? = null) } return null } + +/** + * @return null if failed to convert [remotePath] to nio Path. + */ +@ApiStatus.Internal +fun convertNativePathToNioPath(remotePath: @NativePath String, descriptor: EelDescriptor): Path? { + val eelPath: EelPath = try { + EelPath.parse(remotePath, descriptor) + } + catch (_: EelPathException) { + return null + } + return try { + eelPath.asNioPath() + } + catch (_: IllegalArgumentException) { + null + } +}