From b941a3a19a4e46ec87dbb098ca779bbb20f0b8c8 Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Fri, 16 Jan 2026 15:07:06 +0000 Subject: [PATCH] feat: improve tool call clickable links --- .../toolwindow/agent/ui/RollbackPanel.kt | 31 ++++++++++++------ .../agent/ui/descriptor/ToolCallDescriptor.kt | 7 ++-- .../agent/ui/descriptor/ToolCallView.kt | 32 +++++++++++++------ 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/RollbackPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/RollbackPanel.kt index 557cf5b8..8bfc685b 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/RollbackPanel.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/RollbackPanel.kt @@ -260,19 +260,32 @@ class RollbackPanel( } private fun displayPath(path: String, change: FileChange): String { + val originalPath = change.originalPath?.let { toRelativePath(it) } + val truncatedPath = truncatePath(toRelativePath(path)) + val truncatedOriginal = originalPath?.let { truncatePath(it) } + + return if (change.kind == ChangeKind.MOVED && originalPath != null) { + "$truncatedPath (renamed from $truncatedOriginal)" + } else { + truncatedPath + } + } + + private fun toRelativePath(path: String): String { val base = project.basePath?.replace("\\", "/") val normalized = path.replace("\\", "/") - val relative = if (base != null && normalized.startsWith(base)) { + return if (base != null && normalized.startsWith(base)) { normalized.removePrefix(base).trimStart('/') - } else normalized - val original = change.originalPath?.replace("\\", "/") - return if (change.kind == ChangeKind.MOVED && original != null) { - val originalDisplay = if (base != null && original.startsWith(base)) { - original.removePrefix(base).trimStart('/') - } else original - "$relative (renamed from $originalDisplay)" } else { - relative + normalized + } + } + + private fun truncatePath(path: String, maxLength: Int = 80): String { + return if (path.length > maxLength) { + "..." + path.takeLast(maxLength) + } else { + path } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/descriptor/ToolCallDescriptor.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/descriptor/ToolCallDescriptor.kt index c62aba74..2651eba9 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/descriptor/ToolCallDescriptor.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/descriptor/ToolCallDescriptor.kt @@ -12,14 +12,17 @@ enum class ToolKind { data class Badge( val text: String, val color: JBColor = JBColor.GRAY, - val tooltip: String? = null + val tooltip: String? = null, + val action: (() -> Unit)? = null ) data class FileLink( val path: String, val displayName: String, val enabled: Boolean = true, - val action: ((Project) -> Unit)? = null + val action: ((Project) -> Unit)? = null, + val line: Int? = null, + val column: Int? = null ) data class ToolAction( diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/descriptor/ToolCallView.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/descriptor/ToolCallView.kt index 00c6b70a..8ed4f459 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/descriptor/ToolCallView.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/agent/ui/descriptor/ToolCallView.kt @@ -1,6 +1,8 @@ package ee.carlrobert.codegpt.toolwindow.agent.ui.descriptor import com.intellij.ide.actions.OpenFileAction +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.OpenFileDescriptor import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.vfs.LocalFileSystem @@ -110,10 +112,26 @@ private class ToolCallHeaderPanel( val project = getProject() if (project != null) { val vf = LocalFileSystem.getInstance().findFileByPath(fileLink.path) - if (vf != null) OpenFileAction.openFile(vf, project) + if (vf != null) { + if (fileLink.line != null) { + val descriptor = OpenFileDescriptor( + project, + vf, + fileLink.line - 1, + fileLink.column ?: 0 + ) + FileEditorManager.getInstance(project).openTextEditor(descriptor, true) + } else { + OpenFileAction.openFile(vf, project) + } + } } }.apply { - toolTipText = fileLink.path + toolTipText = if (fileLink.line != null) { + "${fileLink.path}:${fileLink.line}" + } else { + fileLink.path + } setExternalLinkIcon() isEnabled = fileLink.enabled } @@ -146,15 +164,9 @@ private class ToolCallHeaderPanel( private fun addSecondaryBadges() { descriptor.secondaryBadges.forEach { badge -> - val isMatchesBadge = badge.text.contains("matches", ignoreCase = true) - if (isMatchesBadge && descriptor.result != null) { + if (badge.action != null) { val link = ActionLink(badge.text) { - val content = when (val res = descriptor.result) { - is IntelliJSearchTool.Result -> res.output - is String -> res - else -> null - } - content?.let { showSearchResultsDialog(it) } + badge.action?.invoke() }.apply { font = JBUI.Fonts.smallFont() }