[JEWEL-845] Add Context Menu to External Links

[JEWEL-845] Add Context Menu to External Links

Updating branch now that #3091 merged

update api dumps

applying pr suggestions

reran apichecktest

add KDoc to MessageResourceResolver

add KDoc to MessageResourceResolver

undo sample xml run config

code fixes

running ktlint and apichecktest again

remove default value for uri param in ExternalLinkImpl

fix api dump + run configs

remove empty blank line at stand_alone_sample eof

more code fixes

closes https://github.com/JetBrains/intellij-community/pull/3107

GitOrigin-RevId: c5842c900120b6d96c563e79568f524e3e96540b
This commit is contained in:
Daniel Bertoldi
2025-07-14 16:58:24 +00:00
committed by intellij-monorepo-bot
parent 9c9d20fbd4
commit 91ce83f05d
17 changed files with 306 additions and 23 deletions
@@ -26,6 +26,11 @@ f:org.jetbrains.jewel.bridge.BridgeTypography
- getMedium(androidx.compose.runtime.Composer,I):androidx.compose.ui.text.TextStyle
- getRegular(androidx.compose.runtime.Composer,I):androidx.compose.ui.text.TextStyle
- getSmall(androidx.compose.runtime.Composer,I):androidx.compose.ui.text.TextStyle
f:org.jetbrains.jewel.bridge.BridgeUriHandler
- androidx.compose.ui.platform.UriHandler
- sf:$stable:I
- sf:INSTANCE:org.jetbrains.jewel.bridge.BridgeUriHandler
- openUri(java.lang.String):V
f:org.jetbrains.jewel.bridge.BridgeUtilsKt
- sf:createVerticalBrush-8A-3gB4(java.util.List,F,F,I):androidx.compose.ui.graphics.Brush
- bs:createVerticalBrush-8A-3gB4$default(java.util.List,F,F,I,I,java.lang.Object):androidx.compose.ui.graphics.Brush
@@ -35,6 +35,12 @@ public final class org/jetbrains/jewel/bridge/BridgeTypography : org/jetbrains/j
public fun getSmall (Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/text/TextStyle;
}
public final class org/jetbrains/jewel/bridge/BridgeUriHandler : androidx/compose/ui/platform/UriHandler {
public static final field $stable I
public static final field INSTANCE Lorg/jetbrains/jewel/bridge/BridgeUriHandler;
public fun openUri (Ljava/lang/String;)V
}
public final class org/jetbrains/jewel/bridge/BridgeUtilsKt {
public static final fun createVerticalBrush-8A-3gB4 (Ljava/util/List;FFI)Landroidx/compose/ui/graphics/Brush;
public static synthetic fun createVerticalBrush-8A-3gB4$default (Ljava/util/List;FFIILjava/lang/Object;)Landroidx/compose/ui/graphics/Brush;
@@ -0,0 +1,26 @@
// 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.jewel.bridge
import com.intellij.ide.IdeBundle
import org.jetbrains.jewel.ui.util.MessageResourceResolver
/**
* A provider for fetching localized strings using the IDE's message bundle.
*
* This class implements the [MessageResourceResolver] interface and delegates the retrieval of strings to the
* [IdeBundle.message] method. It can be used to retrieve strings associated with a specific key from the IDE's resource
* bundle.
*/
internal class BridgeMessageResourceResolver : MessageResourceResolver {
/**
* Fetches a string associated with the given key from the IDE's message bundle.
*
* This function uses the [IdeBundle.message] method to retrieve a localized string based on the provided key. The
* key should correspond to a message ID defined in the IDE's resource bundle.
*
* @param key The key representing the string to fetch. This key typically corresponds to a message ID in the IDE's
* message bundle (e.g, ""action.text.copy.link.address").
* @return The string associated with the provided key. If the key is not found, it will return an empty string.
*/
override fun resolveIdeBundleMessage(key: String): String = IdeBundle.message(key)
}
@@ -0,0 +1,25 @@
// 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.jewel.bridge
import androidx.compose.ui.platform.UriHandler
import com.intellij.ide.BrowserUtil
/**
* A custom implementation of [UriHandler] that delegates the handling to IntelliJ's BrowserUtil class.
*
* This object overrides the [openUri] function to invoke the [BrowserUtil.browse] method, which attempts to open the
* provided URI. If the URI is valid, it opens in the system's default web browser. If the URI is invalid (e.g., an
* empty string), it may open a local file explorer (Windows) or Finder (macOS), depending on the URI and operating
* system.
*
* Example usage:
* ```
* BridgeUriHandler.openUri("https://www.example.com")
* BridgeUriHandler.openUri("") // May open file explorer or Finder
* ```
*/
public object BridgeUriHandler : UriHandler {
override fun openUri(uri: String) {
BrowserUtil.browse(uri)
}
}
@@ -20,6 +20,7 @@ import org.jetbrains.jewel.foundation.ExperimentalJewelApi
import org.jetbrains.jewel.foundation.InternalJewelApi
import org.jetbrains.jewel.foundation.util.JewelLogger
import org.jetbrains.jewel.ui.component.LocalPopupRenderer
import org.jetbrains.jewel.ui.util.LocalMessageResourceResolverProvider
public fun compose(config: ComposePanel.() -> Unit = {}, content: @Composable () -> Unit): JComponent =
JewelComposePanel(config, content)
@@ -73,6 +74,7 @@ public fun JewelComposeNoThemePanel(config: ComposePanel.() -> Unit = {}, conten
CompositionLocalProvider(
LocalComponent provides this@createJewelComposePanel,
LocalPopupRenderer provides JBPopupRenderer,
LocalMessageResourceResolverProvider provides BridgeMessageResourceResolver(),
) {
ComponentDataProviderBridge(jewelPanel, content = content)
}
@@ -97,6 +99,7 @@ public fun JewelToolWindowNoThemeComposePanel(
CompositionLocalProvider(
LocalComponent provides this@createJewelComposePanel,
LocalPopupRenderer provides JBPopupRenderer,
LocalMessageResourceResolverProvider provides BridgeMessageResourceResolver(),
) {
ComponentDataProviderBridge(jewelPanel, content = content)
}
@@ -7,8 +7,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalUriHandler
import org.jetbrains.jewel.bridge.BridgeMessageResourceResolver
import org.jetbrains.jewel.bridge.BridgePainterHintsProvider
import org.jetbrains.jewel.bridge.BridgeTypography
import org.jetbrains.jewel.bridge.BridgeUriHandler
import org.jetbrains.jewel.bridge.SwingBridgeReader
import org.jetbrains.jewel.bridge.clipboard.JewelBridgeClipboard
import org.jetbrains.jewel.bridge.icon.BridgeNewUiChecker
@@ -23,6 +26,7 @@ import org.jetbrains.jewel.ui.LocalTypography
import org.jetbrains.jewel.ui.icon.LocalNewUiChecker
import org.jetbrains.jewel.ui.painter.LocalPainterHintsProvider
import org.jetbrains.jewel.ui.theme.BaseJewelTheme
import org.jetbrains.jewel.ui.util.LocalMessageResourceResolverProvider
private val bridgeThemeReader by lazy { SwingBridgeReader() }
@@ -44,6 +48,8 @@ public fun SwingBridgeTheme(content: @Composable () -> Unit) {
LocalMenuItemShortcutProvider provides BridgeMenuItemShortcutProvider,
LocalMenuItemShortcutHintProvider provides BridgeMenuItemShortcutHintProvider,
LocalTypography provides BridgeTypography,
LocalUriHandler provides BridgeUriHandler,
LocalMessageResourceResolverProvider provides BridgeMessageResourceResolver(),
) {
content()
}
@@ -0,0 +1,30 @@
// 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.jewel.intui.standalone
import org.jetbrains.jewel.ui.util.MessageResourceResolver
/**
* A provider for UI strings that returns localized or default string values based on a given key.
*
* This class implements the [MessageResourceResolver] interface and is responsible for fetching string values
* associated with specific keys. It can be used to retrieve action text for UI elements, like buttons or menu items
* based on the string keys from IntelliJ Platform resource bundle.
*/
internal class IntUiMessageResourceResolver : MessageResourceResolver {
/**
* Fetches the string associated with a given key.
*
* This function looks up the provided key and returns the corresponding string value. If the key is not found, it
* returns an empty string by default.
*
* @param key The key representing the string to fetch. This key typically corresponds to a message ID in the IDE's
* message bundle (e.g, ""action.text.copy.link.address").
* @return The string associated with the provided key. If the key is not found, an empty string is returned.
*/
override fun resolveIdeBundleMessage(key: String): String =
when (key) {
"action.text.open.link.in.browser" -> "Open Link in Browser"
"action.text.copy.link.address" -> "Copy Link Address"
else -> ""
}
}
@@ -14,6 +14,7 @@ import org.jetbrains.jewel.foundation.theme.ThemeDefinition
import org.jetbrains.jewel.foundation.theme.ThemeIconData
import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme
import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme
import org.jetbrains.jewel.intui.standalone.IntUiMessageResourceResolver
import org.jetbrains.jewel.intui.standalone.IntUiTypography
import org.jetbrains.jewel.intui.standalone.StandalonePainterHintsProvider
import org.jetbrains.jewel.intui.standalone.icon.StandaloneNewUiChecker
@@ -62,6 +63,7 @@ import org.jetbrains.jewel.ui.component.styling.TooltipStyle
import org.jetbrains.jewel.ui.icon.LocalNewUiChecker
import org.jetbrains.jewel.ui.painter.LocalPainterHintsProvider
import org.jetbrains.jewel.ui.theme.BaseJewelTheme
import org.jetbrains.jewel.ui.util.LocalMessageResourceResolverProvider
/**
* Create a light theme definition.
@@ -320,6 +322,7 @@ public fun IntUiTheme(
LocalMenuItemShortcutProvider provides StandaloneShortcutProvider,
LocalMenuItemShortcutHintProvider provides StandaloneMenuItemShortcutHintProvider,
LocalTypography provides IntUiTypography,
LocalMessageResourceResolverProvider provides IntUiMessageResourceResolver(),
) {
content()
}
@@ -89,26 +89,35 @@ internal class SwingComparisonTabPanel : BorderLayoutPanel() {
}
private fun Panel.linksRow() {
row("Links:") {
browserLink("Enabled link", "")
val jewelReadmeLink = "https://github.com/JetBrains/intellij-community/tree/master/platform/jewel/#readme"
compose { ExternalLink(text = "Enabled link", enabled = true, onClick = {}) }
row("Links:") {
cell(
component =
BrowserLink(
icon = AllIcons.Ide.External_link_arrow,
text = "Enabled link",
tooltip = null,
url = "",
)
.apply { enabled(true) }
)
compose { ExternalLink(text = "Enabled link", uri = "", enabled = true) }
cell(
component =
BrowserLink(
icon = IconLoader.getDisabledIcon(AllIcons.Ide.External_link_arrow),
text = "Disabled link",
tooltip = null,
url = "",
)
icon = IconLoader.getDisabledIcon(AllIcons.Ide.External_link_arrow),
text = "Disabled link",
tooltip = null,
url = jewelReadmeLink,
)
.apply { isEnabled = false }
)
.applyToComponent {
enabled(false)
autoHideOnDisable = false
}
.applyToComponent { autoHideOnDisable = false }
compose { ExternalLink(text = "Disabled link", enabled = false, onClick = {}) }
compose { ExternalLink(text = "Disabled link", uri = "", enabled = false) }
}
.layout(RowLayout.PARENT_GRID)
}
@@ -63,8 +63,8 @@ public fun Banners() {
style = JewelTheme.defaultBannerStyle.information,
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt",
actions = {
Link("Action A", onClick = { clickLabel = "Info default no icon Action A clicked" })
Link("Action B", onClick = { clickLabel = "Info default no icon Action B clicked" })
Link("Action A", onClick = { clickLabel = "Info default with icon Action A clicked" })
Link("Action B", onClick = { clickLabel = "Info default with icon Action B clicked" })
},
)
@@ -27,12 +27,13 @@ import org.jetbrains.jewel.ui.theme.linkStyle
@Composable
public fun Links() {
val alwaysUnderline = JewelTheme.linkStyle.copy(underlineBehavior = LinkUnderlineBehavior.ShowAlways)
val jewelReadMeLink = "https://github.com/JetBrains/intellij-community/tree/master/platform/jewel/#readme"
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically) {
Link(text = "Link", onClick = {})
Link(text = "Always underlined", onClick = {}, style = alwaysUnderline)
ExternalLink("ExternalLink", {})
ExternalLink(text = "ExternalLink", uri = jewelReadMeLink)
val items = remember { listOf("Light", "Dark", "---", "High Contrast", "Darcula", "IntelliJ Light") }
var selected by remember { mutableStateOf(items.first()) }
@@ -51,7 +52,7 @@ public fun Links() {
Link(text = "Always underlined", onClick = {}, style = alwaysUnderline, enabled = false)
ExternalLink(text = "ExternalLink", onClick = {}, enabled = false)
ExternalLink(text = "ExternalLink", uri = jewelReadMeLink)
DropdownLink(text = "DropdownLink", enabled = false) {}
}
+6
View File
@@ -88,6 +88,8 @@ f:org.jetbrains.jewel.ui.component.DefaultMenuController
- onHoveredChange(Z):V
- registerShortcutAction(javax.swing.KeyStroke,kotlin.jvm.functions.Function0):V
- submenuController(kotlin.jvm.functions.Function1):org.jetbrains.jewel.ui.component.DefaultMenuController
f:org.jetbrains.jewel.ui.component.LinkKt
- sf:ExternalLink-kye4rC8(java.lang.String,java.lang.String,androidx.compose.ui.Modifier,Z,androidx.compose.ui.text.TextStyle,I,androidx.compose.foundation.interaction.MutableInteractionSource,org.jetbrains.jewel.ui.component.styling.LinkStyle,androidx.compose.runtime.Composer,I,I):V
org.jetbrains.jewel.ui.component.MenuController
- a:clearShortcutActions():V
- a:close-iuPiT84(I):Z
@@ -372,3 +374,7 @@ org.jetbrains.jewel.ui.painter.PainterProviderScope
org.jetbrains.jewel.ui.painter.ResourcePainterProviderScope
- org.jetbrains.jewel.ui.painter.PainterProviderScope
- a:getClassLoaders():java.util.Set
org.jetbrains.jewel.ui.util.MessageResourceResolver
- a:resolveIdeBundleMessage(java.lang.String):java.lang.String
f:org.jetbrains.jewel.ui.util.MessageResourceResolverKt
- sf:getLocalMessageResourceResolverProvider():androidx.compose.runtime.ProvidableCompositionLocal
+9
View File
@@ -625,6 +625,7 @@ public final class org/jetbrains/jewel/ui/component/LinearProgressBarKt {
public final class org/jetbrains/jewel/ui/component/LinkKt {
public static final fun DropdownLink-RWo7tUw (Ljava/lang/String;Landroidx/compose/ui/Modifier;ZLandroidx/compose/ui/text/TextStyle;ILandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/LinkStyle;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/MenuStyle;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
public static final fun ExternalLink-kye4rC8 (Ljava/lang/String;Ljava/lang/String;Landroidx/compose/ui/Modifier;ZLandroidx/compose/ui/text/TextStyle;ILandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/LinkStyle;Landroidx/compose/runtime/Composer;II)V
public static final fun ExternalLink-kye4rC8 (Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZLandroidx/compose/ui/text/TextStyle;ILandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/LinkStyle;Landroidx/compose/runtime/Composer;II)V
public static final fun Link-kye4rC8 (Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZLandroidx/compose/ui/text/TextStyle;ILandroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/LinkStyle;Landroidx/compose/runtime/Composer;II)V
}
@@ -5096,6 +5097,14 @@ public final class org/jetbrains/jewel/ui/util/ColorExtensionsKt {
public static final fun toRgbaHexString-8_81llA (J)Ljava/lang/String;
}
public abstract interface class org/jetbrains/jewel/ui/util/MessageResourceResolver {
public abstract fun resolveIdeBundleMessage (Ljava/lang/String;)Ljava/lang/String;
}
public final class org/jetbrains/jewel/ui/util/MessageResourceResolverKt {
public static final fun getLocalMessageResourceResolverProvider ()Landroidx/compose/runtime/ProvidableCompositionLocal;
}
public final class org/jetbrains/jewel/ui/util/ModifierExtensionsKt {
public static final fun thenIf (Landroidx/compose/ui/Modifier;ZLkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier;
}
@@ -118,7 +118,7 @@ public class ContextSubmenu(label: String, public val submenu: () -> List<Contex
public class ContextMenuItemOption(
public val icon: IconKey? = null,
public val actionType: ContextMenuItemOptionAction,
public val actionType: ContextMenuItemOptionAction? = null,
label: String,
action: () -> Unit,
) : ContextMenuItem(label, action)
@@ -1,5 +1,6 @@
package org.jetbrains.jewel.ui.component
import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.HoverInteraction
@@ -17,18 +18,26 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import java.awt.Cursor
import java.awt.datatransfer.StringSelection
import java.io.IOException
import kotlinx.coroutines.launch
import org.jetbrains.jewel.foundation.modifier.onHover
import org.jetbrains.jewel.foundation.modifier.thenIf
import org.jetbrains.jewel.foundation.state.CommonStateBitMask
@@ -40,6 +49,7 @@ import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Pressed
import org.jetbrains.jewel.foundation.state.FocusableComponentState
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.foundation.theme.JewelTheme.Companion.isSwingCompatMode
import org.jetbrains.jewel.foundation.util.JewelLogger
import org.jetbrains.jewel.ui.component.styling.LinkStyle
import org.jetbrains.jewel.ui.component.styling.LinkUnderlineBehavior.ShowAlways
import org.jetbrains.jewel.ui.component.styling.LinkUnderlineBehavior.ShowOnHover
@@ -49,7 +59,9 @@ import org.jetbrains.jewel.ui.component.styling.MenuStyle
import org.jetbrains.jewel.ui.disabledAppearance
import org.jetbrains.jewel.ui.focusOutline
import org.jetbrains.jewel.ui.icon.IconKey
import org.jetbrains.jewel.ui.icons.AllIconsKeys
import org.jetbrains.jewel.ui.painter.hints.Stateful
import org.jetbrains.jewel.ui.util.LocalMessageResourceResolverProvider
/**
* A clickable text link that follows the standard visual styling with customizable appearance.
@@ -112,8 +124,8 @@ public fun Link(
* **Usage example:**
* [`Links.kt`](https://github.com/JetBrains/intellij-community/blob/master/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Links.kt)
*
* **Swing equivalent:** [`JLabel`](https://docs.oracle.com/javase/tutorial/uiswing/components/label.html) with HTML
* link styling and external link icon
* **Swing equivalent:**
* [`BrowserLink`](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/ui/components/BrowserLink.kt)
*
* @param text The text to be displayed as a link
* @param onClick Called when the link is clicked
@@ -136,8 +148,9 @@ public fun ExternalLink(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
style: LinkStyle = LocalLinkStyle.current,
) {
LinkImpl(
ExternalLinkImpl(
text = text,
uri = "",
onClick = onClick,
modifier = modifier,
enabled = enabled,
@@ -145,10 +158,120 @@ public fun ExternalLink(
interactionSource = interactionSource,
style = style,
textStyle = textStyle,
icon = style.icons.externalLink,
)
}
/**
* An external link that follows the standard visual styling, including an external link icon.
*
* Please be aware that this ExternalLink will automatically open a link on click, unlike the other overloads.
*
* Provides a text link with an external link icon that indicates the link leads to external content. The link supports
* various states including enabled/disabled, focused, and hovered, with optional underline behavior based on the style
* configuration.
*
* **Guidelines:** [on IJP SDK webhelp](https://plugins.jetbrains.com/docs/intellij/link.html#external-link-icon)
*
* **Usage example:**
* [`Links.kt`](https://github.com/JetBrains/intellij-community/blob/master/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Links.kt)
*
* **Swing equivalent:**
* [`BrowserLink`](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-api/src/com/intellij/ui/components/BrowserLink.kt)
*
* @param text The text to be displayed as a link
* @param uri The actual uri that will be used to open in the browser. If it fails to open, the error is suppressed and
* the error is logged.
* @param modifier Modifier to be applied to the link
* @param enabled Controls whether the link can be interacted with
* @param textStyle The typography style to be applied to the link text
* @param overflow How the text should handle overflow
* @param interactionSource Source of interactions for this link
* @param style The visual styling configuration for the link
* @see javax.swing.JLabel
*/
@Composable
public fun ExternalLink(
text: String,
uri: String,
modifier: Modifier = Modifier,
enabled: Boolean = true,
textStyle: TextStyle = JewelTheme.defaultTextStyle,
overflow: TextOverflow = TextOverflow.Clip,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
style: LinkStyle = LocalLinkStyle.current,
) {
val uriHandler = LocalUriHandler.current
ExternalLinkImpl(
text = text,
onClick = { openUri(uriHandler, uri) },
uri = uri,
modifier = modifier,
enabled = enabled,
overflow = overflow,
interactionSource = interactionSource,
style = style,
textStyle = textStyle,
)
}
@Composable
private fun ExternalLinkImpl(
text: String,
onClick: () -> Unit,
uri: String,
modifier: Modifier,
enabled: Boolean,
overflow: TextOverflow,
interactionSource: MutableInteractionSource,
style: LinkStyle,
textStyle: TextStyle,
) {
val clipboard = LocalClipboard.current
val stringProvider = LocalMessageResourceResolverProvider.current
val scope = rememberCoroutineScope()
ContextMenuArea(
items = {
listOf(
ContextMenuItemOption(
label = stringProvider.resolveIdeBundleMessage("action.text.open.link.in.browser"),
action = { onClick() },
icon = AllIconsKeys.Nodes.PpWeb,
),
ContextMenuItemOption(
label = stringProvider.resolveIdeBundleMessage("action.text.copy.link.address"),
action = { scope.launch { clipboard.setClipEntry(ClipEntry(StringSelection(uri))) } },
icon = AllIconsKeys.Actions.Copy,
),
)
},
enabled = enabled,
content = {
LinkImpl(
text = text,
onClick = onClick,
modifier = modifier,
enabled = enabled,
overflow = overflow,
interactionSource = interactionSource,
style = style,
textStyle = textStyle,
icon = style.icons.externalLink,
)
},
)
}
private fun openUri(uriHandler: UriHandler, link: String) =
try {
uriHandler.openUri(link)
} catch (e: IllegalArgumentException) {
JewelLogger.getInstance("ExternalLink").error("Unable to open link ($link). Error: $e")
} catch (e: IOException) {
JewelLogger.getInstance("ExternalLink").error("Unable to open link ($link). Error: $e")
}
/**
* A dropdown link that follows the standard visual styling with customizable appearance and menu content.
*
@@ -166,7 +166,9 @@ public fun MenuContent(
val selectableItems = remember(items) { items.filterIsInstance<MenuSelectableItem>() }
val anyItemHasIcon = remember { selectableItems.any { it.iconKey != null } }
val anyItemHasKeybinding = remember { selectableItems.any { it.keybinding != null || it.itemOptionAction != null } }
val anyItemHasKeybinding = remember {
selectableItems.any { it.keybinding?.isNotEmpty() == true || it.itemOptionAction != null }
}
val localMenuController = LocalMenuController.current
val localInputModeManager = LocalInputModeManager.current
@@ -0,0 +1,29 @@
// 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.jewel.ui.util
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.staticCompositionLocalOf
import org.jetbrains.jewel.foundation.InternalJewelApi
/**
* Interface for resolving messages from the IDE's resource bundle.
*
* This interface provides a mechanism to resolve strings based on a given key. It is used to fetch UI strings from the
* IDE's internal resource bundle.
*/
@InternalJewelApi
public interface MessageResourceResolver {
/**
* Resolves a message using the provided key.
*
* @param key The key representing the message to resolve.
* @return The resolved message string associated with the provided key. If the key is not found, an empty string
* will be returned.
*/
public fun resolveIdeBundleMessage(key: String): String
}
public val LocalMessageResourceResolverProvider: ProvidableCompositionLocal<MessageResourceResolver> =
staticCompositionLocalOf {
error("No LocalMessageResourceResolverProvider provided. Have you forgotten the theme?")
}