diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/actions/CheckIssueDetail.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/actions/CheckIssueDetail.kt index 08cc3525aa36..7054a76fa73a 100644 --- a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/actions/CheckIssueDetail.kt +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/actions/CheckIssueDetail.kt @@ -26,10 +26,12 @@ class CheckIssueDetail : AnAction() { val server = connectionService.getServerAsync() ?: return@launch val command = combinedState.state.agentStatus?.command ?: return@launch - server.workspaceFeature.executeCommand(ExecuteCommandParams( - command.command, - command.arguments, - )) + server.workspaceFeature.executeCommand( + ExecuteCommandParams( + command.command, + command.arguments, + ) + ) } } diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/chat/ChatBrowser.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/chat/ChatBrowser.kt index b1b98e3c87e9..5e1e4d900240 100644 --- a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/chat/ChatBrowser.kt +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/chat/ChatBrowser.kt @@ -3,6 +3,7 @@ package com.tabbyml.intellijtabby.chat import com.google.gson.Gson import com.google.gson.annotations.SerializedName import com.google.gson.reflect.TypeToken +import com.intellij.ide.BrowserUtil import com.intellij.openapi.application.invokeLater import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.WriteCommandAction @@ -14,6 +15,7 @@ import com.intellij.openapi.editor.colors.EditorColorsListener import com.intellij.openapi.editor.colors.EditorColorsManager import com.intellij.openapi.editor.colors.EditorFontType import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.OpenFileDescriptor import com.intellij.openapi.progress.util.BackgroundTaskUtil import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir @@ -22,6 +24,7 @@ import com.intellij.ui.jcef.JBCefBrowser import com.intellij.ui.jcef.JBCefBrowserBase import com.intellij.ui.jcef.JBCefJSQuery import com.tabbyml.intellijtabby.events.CombinedState +import com.tabbyml.intellijtabby.findVirtualFile import com.tabbyml.intellijtabby.git.GitProvider import com.tabbyml.intellijtabby.lsp.ConnectionService import com.tabbyml.intellijtabby.lsp.protocol.Config @@ -30,6 +33,7 @@ import com.tabbyml.intellijtabby.lsp.protocol.StatusRequestParams import io.github.z4kn4fein.semver.Version import io.github.z4kn4fein.semver.constraints.Constraint import io.github.z4kn4fein.semver.constraints.satisfiedBy +import io.ktor.http.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -210,12 +214,23 @@ class ChatBrowser(private val project: Project) : JBCefBrowser( ), filepath = relativePath ?: "", content = context.first, - gitUrl = gitRepo?.remotes?.firstOrNull()?.url ?: "", + gitUrl = gitRepo?.let { getDefaultRemoteUrl(it) } ?: "", ) } } } + private fun navigateToFileContext(fileContext: FileContext) { + val virtualFile = project.findVirtualFile(fileContext.filepath) + ?: gitRemoteUrlToLocalRoot[fileContext.gitUrl]?.let { project.findVirtualFile(it.appendUrlPathSegments(fileContext.filepath)) } + ?: project.guessProjectDir()?.url?.let { project.findVirtualFile(it.appendUrlPathSegments(fileContext.filepath)) } + ?: return + invokeLater { + val descriptor = OpenFileDescriptor(project, virtualFile, fileContext.range.start, 0) + FileEditorManager.getInstance(project).openTextEditor(descriptor, true) + } + } + private fun handleLoaded() { jsInjectHandlers() jsApplyStyle() @@ -321,7 +336,15 @@ class ChatBrowser(private val project: Project) : JBCefBrowser( when (request.method) { "navigate" -> { logger.debug("navigate: request: ${request.params}") - // FIXME(@icycodes): not implemented yet + val context = request.params.getOrNull(0)?.let { + gson.fromJson(gson.toJson(it), FileContext::class.java) + } ?: return + val options = request.params.getOrNull(1) as Map<*, *>? + if (options?.get("openInEditor") == true) { + navigateToFileContext(context) + } else { + currentConfig?.let { buildCodeBrowserUrl(it, context) }?.let { BrowserUtil.browse(it) } + } } "refresh" -> { @@ -601,6 +624,34 @@ class ChatBrowser(private val project: Project) : JBCefBrowser( return String.format("%.0f, %.0f%%, %.0f%%", h, s, l) } + private val gitRemoteUrlToLocalRoot = mutableMapOf() + + private fun getDefaultRemoteUrl(repo: GitProvider.Repository): String? { + if (repo.remotes.isNullOrEmpty()) { + return null + } + val remoteUrl = repo.remotes.firstOrNull { it.name == "origin" }?.url + ?: repo.remotes.firstOrNull { it.name == "upstream" }?.url + ?: repo.remotes.firstOrNull()?.url + if (remoteUrl != null) { + gitRemoteUrlToLocalRoot[remoteUrl] = repo.root + } + return remoteUrl + } + + private fun String.appendUrlPathSegments(path: String): String { + return URLBuilder(this).appendPathSegments(path).toString() + } + + private fun buildCodeBrowserUrl(config: Config.ServerConfig, context: FileContext): String { + return URLBuilder(config.endpoint).apply { + appendPathSegments("files") + parameters.append("redirect_git_url", context.gitUrl) + parameters.append("redirect_filepath", context.filepath) + fragment = "L${context.range.start}-L${context.range.end}" + }.buildString() + } + private const val TABBY_CHAT_PANEL_API_VERSION_RANGE = "~0.2.0" private const val TABBY_SERVER_VERSION_RANGE = ">=0.18.0" diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/protocol/ProtocolData.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/protocol/ProtocolData.kt index 5e2a449d5107..ed8a5384d84a 100644 --- a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/protocol/ProtocolData.kt +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/protocol/ProtocolData.kt @@ -5,6 +5,9 @@ import com.tabbyml.intellijtabby.lsp.protocol.ClientProvidedConfig.Keybindings import com.tabbyml.intellijtabby.lsp.protocol.EventParams.EventType import com.tabbyml.intellijtabby.lsp.protocol.EventParams.SelectKind import com.tabbyml.intellijtabby.lsp.protocol.ReadFileParams.Format +import com.tabbyml.intellijtabby.lsp.protocol.StatusIgnoredIssuesEditParams.Operation +import com.tabbyml.intellijtabby.lsp.protocol.StatusIgnoredIssuesEditParams.StatusIssuesName +import com.tabbyml.intellijtabby.lsp.protocol.StatusInfo.Status import org.eclipse.lsp4j.* data class InitializeParams( diff --git a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/settings/SettingsPanel.kt b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/settings/SettingsPanel.kt index 608c61ac0d0c..d9b4041c2859 100644 --- a/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/settings/SettingsPanel.kt +++ b/clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/settings/SettingsPanel.kt @@ -16,7 +16,9 @@ import com.intellij.util.ui.FormBuilder import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import com.tabbyml.intellijtabby.lsp.ConnectionService -import com.tabbyml.intellijtabby.lsp.protocol.* +import com.tabbyml.intellijtabby.lsp.protocol.StatusIgnoredIssuesEditParams +import com.tabbyml.intellijtabby.lsp.protocol.StatusInfo +import com.tabbyml.intellijtabby.lsp.protocol.StatusRequestParams import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job