[terminal] IJPL-206783 detect absolute paths as hyperlinks on mouse hover in the reworked terminal

This feature can be disabled by `terminal.generic.hyperlinks` registry key.

GitOrigin-RevId: d330c37cb8655eb8bdfc558b887c65ec651f70b1
This commit is contained in:
Sergey Simonchik
2025-10-13 23:42:12 +00:00
committed by intellij-monorepo-bot
parent 9b848c8505
commit d098eb720d
4 changed files with 38 additions and 10 deletions
@@ -65,9 +65,10 @@
description="Whether popup-based command completion feature is enabled in the Reworked Terminal"/>
<registryKey key="terminal.type.ahead" defaultValue="true"
description="Whether terminal should predict how shell handles typing to minimize the typing latency. Monolith-only at the moment"/>
<registryKey key="terminal.generic.hyperlinks" defaultValue="false"
<registryKey key="terminal.generic.hyperlinks" defaultValue="true"
description="Show generic file hyperlinks in the reworked terminal. Such hyperlinks are highlighted on mouse hover.
Open a new terminal after enabling this option to take effect."/>
<consoleFilterProvider implementation="org.jetbrains.plugins.terminal.block.reworked.hyperlinks.TerminalGenericFileFilterProvider"/>
<experimentalFeature id="terminal.shell.command.handling" percentOfUsers="100"/>
<terminal.shellCommandHandler implementation="org.jetbrains.plugins.terminal.RunAnythingTerminalBridge"/>
@@ -5,18 +5,17 @@ import com.intellij.execution.filters.CompositeFilter
import com.intellij.execution.filters.ConsoleFilterProvider
import com.intellij.execution.filters.Filter
import com.intellij.execution.impl.ConsoleViewUtil
import com.intellij.openapi.application.*
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.UiWithModelAccess
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.application.readAction
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.TestOnly
import org.jetbrains.plugins.terminal.hyperlinks.TerminalFilterScope
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicBoolean
@@ -59,7 +58,7 @@ class CompositeFilterWrapper(private val project: Project, coroutineScope: Corou
private suspend fun computeFilter(): CompositeFilter {
val filters = readAction {
ConsoleViewUtil.computeConsoleFilters(project, null, GlobalSearchScope.allScope(project))
ConsoleViewUtil.computeConsoleFilters(project, null, TerminalFilterScope(project))
}
return CompositeFilter(project, customFilters + filters).also {
it.setForceUseAllFilters(true)
@@ -1,13 +1,17 @@
package org.jetbrains.plugins.terminal.block.reworked.hyperlinks
import com.intellij.execution.filters.ConsoleFilterProviderEx
import com.intellij.execution.filters.Filter
import com.intellij.execution.filters.OpenFileHyperlinkInfo
import com.intellij.openapi.editor.markup.EffectType
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.ui.JBColor
import org.jetbrains.plugins.terminal.hyperlinks.TerminalFilterScope
import java.awt.Font
internal enum class ParsingState {
@@ -21,7 +25,10 @@ internal enum class ParsingState {
* output, sync output, run test output, built-in terminal emulator, etc. Therefore, we manually parse the string instead of using regex
* for maximum performance.
*/
internal class GenericFileFilter(private val project: Project, private val localFileSystem: LocalFileSystem) : Filter {
internal class TerminalGenericFileFilter(
private val project: Project,
private val localFileSystem: LocalFileSystem
) : Filter {
companion object {
/**
* Max filename considered during parsing. Do not confuse with file path, which may contain several file names separated by '/' or '\'.
@@ -226,3 +233,15 @@ private fun String.takeWhileFromIndex(index: Int, predicate: (Char) -> Boolean):
private val EMPTY_ATTRS: TextAttributes = TextAttributes(null, null, null, null, Font.PLAIN)
private val HOVERED_ATTRS: TextAttributes = TextAttributes(null, null, JBColor.BLACK, EffectType.LINE_UNDERSCORE, Font.PLAIN)
internal class TerminalGenericFileFilterProvider : ConsoleFilterProviderEx {
override fun getDefaultFilters(project: Project, scope: GlobalSearchScope): Array<out Filter> {
if (scope is TerminalFilterScope && Registry.`is`("terminal.generic.hyperlinks", false)) {
return arrayOf(TerminalGenericFileFilter(project, LocalFileSystem.getInstance()))
}
return emptyArray()
}
override fun getDefaultFilters(project: Project): Array<out Filter?> = emptyArray()
}
@@ -0,0 +1,9 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.terminal.hyperlinks
import com.intellij.openapi.project.Project
import com.intellij.psi.search.DelegatingGlobalSearchScope
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
class TerminalFilterScope(project: Project): DelegatingGlobalSearchScope(allScope(project))