feat: explain commits from vcs log tree (relates #688)

This commit is contained in:
Carl-Robert Linnupuu 2024-11-15 09:07:32 +00:00
parent 5ee8ef941d
commit 2ab6d1a54c
8 changed files with 154 additions and 9 deletions

View file

@ -34,6 +34,7 @@ import ee.carlrobert.codegpt.ui.textarea.AppliedActionInlay;
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel;
import ee.carlrobert.codegpt.util.EditorUtil;
import ee.carlrobert.codegpt.util.file.FileUtil;
import git4idea.GitCommit;
import java.awt.BorderLayout;
import java.io.File;
import java.io.IOException;
@ -117,6 +118,14 @@ public class ChatToolWindowTabPanel implements Disposable {
totalTokensPanel.updateConversationTokens(conversation);
}
public void addSelection(String fileName, SelectionModel selectionModel) {
userInputPanel.addSelection(fileName, selectionModel);
}
public void addCommitReferences(List<GitCommit> gitCommits) {
userInputPanel.addCommitReferences(gitCommits);
}
public List<ReferencedFile> getReferencedFiles() {
var referencedFiles = new LinkedHashMap<String, ReferencedFile>();
@ -417,8 +426,4 @@ public class ChatToolWindowTabPanel implements Disposable {
rootPanel.add(createUserPromptPanel(), BorderLayout.SOUTH);
return rootPanel;
}
public void addSelection(String fileName, SelectionModel selectionModel) {
userInputPanel.addSelection(fileName, selectionModel);
}
}

View file

@ -0,0 +1,77 @@
package ee.carlrobert.codegpt.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.VcsDataKeys
import com.intellij.openapi.vcs.history.VcsRevisionNumber
import ee.carlrobert.codegpt.CodeGPTBundle
import ee.carlrobert.codegpt.Icons
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager
import ee.carlrobert.codegpt.util.GitUtil
import git4idea.GitCommit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class ExplainGitCommitAction : AnAction(
CodeGPTBundle.get("action.explainGitCommit.title"),
CodeGPTBundle.get("action.explainGitCommit.description"),
Icons.DefaultSmall
) {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
scope.launch {
val gitCommits =
getCommitsForRevisions(project, e.getData(VcsDataKeys.VCS_REVISION_NUMBERS))
project.service<ChatToolWindowContentManager>().apply {
runInEdt {
displayChatTab()
tryFindActiveChatTabPanel()
.ifPresent {
it.addCommitReferences(gitCommits)
}
}
}
}
}
private fun getCommitsForRevisions(
project: Project,
revisionNumbers: Array<VcsRevisionNumber>?
): List<GitCommit> {
if (revisionNumbers == null) {
throw IllegalArgumentException("No commit revisions found")
}
val gitCommits = GitUtil.getProjectRepository(project)?.let { repository ->
GitUtil.getCommitsForHashes(
project,
repository,
revisionNumbers.map { it.asString() })
} ?: throw IllegalStateException("Unable to find git repository")
if (gitCommits.isEmpty()) {
throw IllegalStateException(
"Unable to find commits for given revisions: ${
revisionNumbers.joinToString(",") { it.asString() }
}"
)
}
return gitCommits
}
}

View file

@ -71,9 +71,9 @@ class PromptTextField(
fun addInlayElement(actionPrefix: String, text: String?, actionItem: SuggestionActionItem?) {
editor?.let {
val startOffset = it.document.text.lastIndexOf(AT_CHAR)
var startOffset = it.document.text.lastIndexOf(AT_CHAR)
if (startOffset == -1) {
throw IllegalStateException("No '@' symbol found in the text")
startOffset = it.document.textLength
}
addInlayElement(startOffset, actionPrefix, text, actionItem)

View file

@ -5,6 +5,7 @@ import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.SelectionModel
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
@ -28,7 +29,9 @@ import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.ModelComboBoxAction
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel
import ee.carlrobert.codegpt.ui.IconActionButton
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.GitCommitActionItem
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel
import git4idea.GitCommit
import java.awt.*
import java.awt.geom.Area
import java.awt.geom.Rectangle2D
@ -96,7 +99,14 @@ class UserInputPanel(
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
val area = Area(Rectangle2D.Float(0f, 0f, width.toFloat(), height.toFloat()))
val roundedRect = RoundRectangle2D.Float(0f, 0f, width.toFloat(), height.toFloat(), CORNER_RADIUS.toFloat(), CORNER_RADIUS.toFloat())
val roundedRect = RoundRectangle2D.Float(
0f,
0f,
width.toFloat(),
height.toFloat(),
CORNER_RADIUS.toFloat(),
CORNER_RADIUS.toFloat()
)
area.intersect(Area(roundedRect))
g2.clip = area
@ -210,4 +220,25 @@ class UserInputPanel(
promptTextField.requestFocusInWindow()
selectionModel.removeSelection()
}
fun addCommitReferences(gitCommits: List<GitCommit>) {
runInEdt {
if (promptTextField.text.isEmpty()) {
promptTextField.text = if (gitCommits.size == 1) {
"Explain the commit: "
} else {
"Explain the commits: "
}
}
gitCommits.forEach {
promptTextField.addInlayElement(
"commit",
it.id.toShortString(),
GitCommitActionItem(project, it)
)
}
promptTextField.requestFocusInWindow()
}
}
}

View file

@ -114,7 +114,7 @@ class GitCommitActionItem(
?: return@runProcessWithProgressSynchronously ""
val commitId = gitCommit.id.asString()
val diff = GitUtil.getCommitDiff(project, repository, commitId)
val diff = GitUtil.getCommitDiffs(project, repository, commitId)
.joinToString("\n")
service<EncodingManager>().truncateText(diff, MAX_TOKENS, true)

View file

@ -71,7 +71,25 @@ object GitUtil {
}
@Throws(VcsException::class)
fun getCommitDiff(
fun getCommitsForHashes(
project: Project,
repository: GitRepository,
commitHashes: List<String>
): List<GitCommit> {
val result = mutableListOf<GitCommit>()
GitHistoryUtils
.loadDetails(project, repository.root, { commit ->
if (commitHashes.contains(commit.id.asString())) {
result.add(commit)
}
})
return result
}
@Throws(VcsException::class)
fun getCommitDiffs(
project: Project,
gitRepository: GitRepository,
commitHash: String

View file

@ -171,6 +171,18 @@
<separator/>
</group>
<group id="CodeGPT.VcsLogContextMenu">
<separator/>
<action
id="CodeGPT.ExplainGitCommitAction"
class="ee.carlrobert.codegpt.actions.ExplainGitCommitAction"/>
<add-to-group
group-id="Vcs.Log.ContextMenu"
relative-to-action="Vcs.Log.CompareRevisions"
anchor="after"/>
<separator/>
</group>
<group id="CodeGPT.FloatingCodeToolbarMenuRootGroup">
<action
id="CodeGPT.FloatingMenuEditCodeAction"

View file

@ -27,6 +27,8 @@ action.statusbar.disableCompletions.description=Disable Code Completions
action.statusbar.disableCompletions.MainMenu.text=Disable Completions
action.compareWithOriginal.title=Compare with Original
action.applyDirectly.title=Auto Apply
action.explainGitCommit.title=Explain Commit with CodeGPT
action.explainGitCommit.description=Generate a detailed explanation of the commit changes using CodeGPT
settings.displayName=CodeGPT: Settings
settings.openaiQuotaExceeded=OpenAI quota exceeded.
settingsConfigurable.displayName.label=Display name: