Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: display AI Fix ⚡️ [IDE-580] #596

Merged
merged 28 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1ea7c69
wip: enable AI Fix
Aug 28, 2024
73b5081
feat: wip - bridge button action with LS wrapper
Sep 3, 2024
5563b32
feat: get Code Diff from LS
Sep 3, 2024
b156653
debug: test Language Server response
Sep 4, 2024
575fa56
fix: wip parsing of command
bastiandoetsch Sep 4, 2024
7421d80
feat: display AI suggestions ⚡️
Sep 4, 2024
ff1400b
chore: update `Apply fix` button style
Sep 4, 2024
8a4b271
chore: group similar JS logic
Sep 4, 2024
e03954c
fix: responsive UI during AI Fix call with LS async request
Sep 4, 2024
6c0b5fc
feat: wip - apply fix
Sep 4, 2024
281670a
fix: added support for navigating between AI Fixes
acke Sep 5, 2024
c987b11
feat: show section for error, if ai fix reqest fails to generate fixes
acke Sep 5, 2024
ddea6a7
fix: add the same spacing for error section as for show fixes section
acke Sep 5, 2024
dbaacd1
chore: wip - apply fix
Sep 5, 2024
d0937ba
chore: wip add test to handle patches
Sep 6, 2024
8a387b6
fix: fix flow for regenerating ai fixes after error
acke Sep 6, 2024
e0fc1d2
tidy: replace hardcoded colors with JBUI theme colors
acke Sep 11, 2024
adaca2a
tidy: remove println's and disable devtools
acke Sep 12, 2024
097875a
chore: updating logging and error handling, changing threading to run…
acke Sep 13, 2024
16ae0a1
fix: solved a problem with applying patch
acke Sep 16, 2024
4387f5d
chore: replace colors with colors from JBUI
acke Sep 16, 2024
cc02eb7
tidy: cleanup code, remove temporary logging
acke Sep 16, 2024
a174e45
fix: update unit test and remove duplicate code
acke Sep 16, 2024
ee0b923
fix: improve error handling and added balloon notifier for failing to…
acke Sep 16, 2024
280a979
tidy: refactor functions to a new patcher
acke Sep 16, 2024
f155b48
tidy: remove unused parameters
acke Sep 16, 2024
69e7c1e
fix: updating logging and refactor types
acke Sep 19, 2024
2d00d29
fix: add balloon notifier for file not found
acke Sep 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/main/kotlin/io/snyk/plugin/DiffPatcher.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
package io.snyk.plugin

import io.snyk.plugin.ui.jcef.Change
import io.snyk.plugin.ui.jcef.DiffPatch
import io.snyk.plugin.ui.jcef.Hunk
data class DiffPatch(
val originalFile: String,
val fixedFile: String,
val hunks: List<Hunk>
)

data class Hunk(
val startLineOriginal: Int,
val numLinesOriginal: Int,
val startLineFixed: Int,
val numLinesFixed: Int,
val changes: List<Change>
)

sealed class Change {
data class Addition(val line: String) : Change()
data class Deletion(val line: String) : Change()
data class Context(val line: String) : Change() // Unchanged line for context
}

class DiffPatcher {

Expand Down
94 changes: 31 additions & 63 deletions src/main/kotlin/io/snyk/plugin/ui/jcef/ApplyFixHandler.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.snyk.plugin.ui.jcef

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.diagnostic.LogLevel
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
Expand All @@ -14,34 +14,17 @@ import org.cef.browser.CefFrame
import org.cef.handler.CefLoadHandlerAdapter
import org.jetbrains.concurrency.runAsync
import io.snyk.plugin.ui.SnykBalloonNotificationHelper
import snyk.common.lsp.LanguageServerWrapper
import java.io.IOException


data class DiffPatch(
val originalFile: String,
val fixedFile: String,
val hunks: List<Hunk>
)

data class Hunk(
val startLineOriginal: Int,
val numLinesOriginal: Int,
val startLineFixed: Int,
val numLinesFixed: Int,
val changes: List<Change>
)

sealed class Change {
data class Addition(val line: String) : Change()
data class Deletion(val line: String) : Change()
data class Context(val line: String) : Change() // Unchanged line for context
}

class ApplyFixHandler(private val project: Project) {

private val enableDebug = Logger.getInstance("Snyk Language Server").isDebugEnabled
private val enableTrace = Logger.getInstance("Snyk Language Server").isTraceEnabled
private val logger = Logger.getInstance(this::class.java)
val logger = Logger.getInstance(this::class.java).apply {
// tie log level to language server log level
val languageServerWrapper = LanguageServerWrapper.getInstance()
if (languageServerWrapper.logger.isDebugEnabled) this.setLevel(LogLevel.DEBUG)
if (languageServerWrapper.logger.isTraceEnabled) this.setLevel(LogLevel.TRACE)
}


fun generateApplyFixCommand(jbCefBrowser: JBCefBrowserBase): CefLoadHandlerAdapter {
Expand All @@ -54,32 +37,30 @@ class ApplyFixHandler(private val project: Project) {

// Avoid blocking the UI thread
runAsync {
//var success = true
val result = try {
applyPatchAndSave(project, filePath, patch)
} catch (e: IOException) { // Catch specific file-related exceptions
log("Error applying patch to file: $filePath. e:$e")
logger.error("Error applying patch to file: $filePath. e:$e")
Result.failure(e)
} catch (e: Exception) {
log("Unexpected error applying patch. e:$e")
logger.error("Unexpected error applying patch. e:$e")
Result.failure(e)
}

ApplicationManager.getApplication().invokeLater {
if (result.isSuccess) {
val script = """
if (result.isSuccess) {
val script = """
window.receiveApplyFixResponse(true);
""".trimIndent()
jbCefBrowser.cefBrowser.executeJavaScript(script, jbCefBrowser.cefBrowser.url, 0)
} else {
val errorMessage = "Error applying fix: ${result.exceptionOrNull()?.message}"
SnykBalloonNotificationHelper.showError(errorMessage, project)
val errorScript = """
jbCefBrowser.cefBrowser.executeJavaScript(script, jbCefBrowser.cefBrowser.url, 0)
} else {
val errorMessage = "Error applying fix: ${result.exceptionOrNull()?.message}"
SnykBalloonNotificationHelper.showError(errorMessage, project)
val errorScript = """
window.receiveApplyFixResponse(false, "$errorMessage");
""".trimIndent()
jbCefBrowser.cefBrowser.executeJavaScript(errorScript, jbCefBrowser.cefBrowser.url, 0)
}
jbCefBrowser.cefBrowser.executeJavaScript(errorScript, jbCefBrowser.cefBrowser.url, 0)
}

}
return@addHandler JBCefJSQuery.Response("success")
}
Expand All @@ -105,34 +86,21 @@ class ApplyFixHandler(private val project: Project) {
val virtualFile = filePath.toVirtualFile()
val patcher = DiffPatcher()

return try {
WriteCommandAction.runWriteCommandAction(project) {
val document = FileDocumentManager.getInstance().getDocument(virtualFile)
if (document != null) {
val originalContent = document.text
val patchedContent = patcher.applyPatch(originalContent, patcher.parseDiff(patch))
if (originalContent != patchedContent) {
document.setText(patchedContent)
} else {
log("[applyPatchAndSave] Patch did not modify content: $filePath")
}
WriteCommandAction.runWriteCommandAction(project) {
val document = FileDocumentManager.getInstance().getDocument(virtualFile)
if (document != null) {
val originalContent = document.text
val patchedContent = patcher.applyPatch(originalContent, patcher.parseDiff(patch))
if (originalContent != patchedContent) {
document.setText(patchedContent)
} else {
log("[applyPatchAndSave] Failed to find document for: $filePath")
return@runWriteCommandAction
logger.warn("[applyPatchAndSave] Patch did not modify content: $filePath")
}
} else {
logger.error("[applyPatchAndSave] Failed to find document for: $filePath")
acke marked this conversation as resolved.
Show resolved Hide resolved
return@runWriteCommandAction
}
Result.success(Unit)
} catch (e: Exception) {
log("[applyPatchAndSave] Error applying patch to: $filePath. e: $e")
Result.failure(e)
}
}

private fun log(logMessage: String) {
when {
enableDebug -> logger.debug(logMessage)
enableTrace -> logger.trace(logMessage)
else -> logger.error(logMessage)
}
return Result.success(Unit)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import org.cef.browser.CefFrame
import org.cef.handler.CefLoadHandlerAdapter
import org.jetbrains.concurrency.runAsync
import snyk.common.lsp.LanguageServerWrapper
import snyk.common.lsp.Fix


class GenerateAIFixHandler() {

Expand All @@ -22,7 +24,7 @@ class GenerateAIFixHandler() {


runAsync {
val responseDiff: List<LanguageServerWrapper.Fix> =
val responseDiff: List<Fix> =
LanguageServerWrapper.getInstance().sendCodeFixDiffsCommand(folderURI, fileURI, issueID)

val script = """
Expand Down
7 changes: 0 additions & 7 deletions src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package snyk.common.lsp

import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
Expand Down Expand Up @@ -497,11 +495,6 @@ class LanguageServerWrapper(
return this.getFeatureFlagStatus("snykCodeConsistentIgnores")
}

data class Fix(
@SerializedName("fixId") val fixId: String,
@SerializedName("unifiedDiffsPerFile") val unifiedDiffsPerFile: Map<String, String>
)

@Suppress("UNCHECKED_CAST")
fun sendCodeFixDiffsCommand(folderURI: String, fileURI: String, issueID: String): List<Fix> {
if (!ensureLanguageServerInitialized()) return emptyList()
acke marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
5 changes: 5 additions & 0 deletions src/main/kotlin/snyk/common/lsp/Types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,11 @@ data class ExampleCommitFix(
@SerializedName("lines") val lines: List<CommitChangeLine>,
)

data class Fix(
@SerializedName("fixId") val fixId: String,
@SerializedName("unifiedDiffsPerFile") val unifiedDiffsPerFile: Map<String, String>
)

data class CommitChangeLine(
@SerializedName("line") val line: String,
@SerializedName("lineNumber") val lineNumber: Int,
Expand Down
Loading