Skip to content

Commit 3aff9bc

Browse files
committed
feat(git): add GitActionLocationEditor and GitToolchainVariableProvider implementations #379
1 parent 7f91dd7 commit 3aff9bc

File tree

4 files changed

+246
-0
lines changed

4 files changed

+246
-0
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,7 @@ project(":exts:ext-git") {
601601
}
602602

603603
implementation(project(":core"))
604+
implementation(project(":exts:devins-lang"))
604605
implementation("cc.unitmesh:git-commit-message:0.4.6") {
605606
excludeKotlinDeps()
606607
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package cc.unitmesh.git.provider
2+
3+
import cc.unitmesh.devti.language.ast.config.ShireActionLocation
4+
import cc.unitmesh.devti.language.provider.ActionLocationEditor
5+
import com.intellij.openapi.application.invokeAndWaitIfNeeded
6+
import com.intellij.openapi.diagnostic.logger
7+
import com.intellij.openapi.editor.Editor
8+
import com.intellij.openapi.project.Project
9+
import com.intellij.openapi.vcs.ui.CommitMessage
10+
11+
class GitActionLocationEditor : ActionLocationEditor {
12+
private var commitUi: CommitMessage? = null
13+
14+
override fun isApplicable(hole: ShireActionLocation): Boolean {
15+
val commitMessage = getCommitUi(hole)
16+
if (commitMessage != null) {
17+
commitUi = commitMessage
18+
}
19+
20+
return hole == ShireActionLocation.COMMIT_MENU && commitMessage != null
21+
}
22+
23+
override fun resolve(project: Project, hole: ShireActionLocation): Editor? {
24+
val commitMessageUi = commitUi ?: getCommitUi(hole) ?: return null
25+
val editorField = commitMessageUi.editorField
26+
27+
@Suppress("UnstableApiUsage")
28+
invokeAndWaitIfNeeded { editorField.text = "" }
29+
30+
return editorField.editor
31+
}
32+
33+
private fun getCommitUi(hole: ShireActionLocation): CommitMessage? {
34+
if (hole != ShireActionLocation.COMMIT_MENU) return null
35+
36+
val commitMessageUi = getCommitWorkflowUi()?.commitMessageUi as? CommitMessage
37+
38+
if (commitMessageUi == null) {
39+
logger<GitActionLocationEditor>().error("Failed to get commit message UI")
40+
return null
41+
}
42+
43+
return commitMessageUi
44+
}
45+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package cc.unitmesh.git.provider
2+
3+
import cc.unitmesh.devti.language.ast.variable.ToolchainVariable
4+
import cc.unitmesh.devti.language.ast.variable.toolchain.VcsToolchainVariable
5+
import cc.unitmesh.devti.language.provider.ToolchainVariableProvider
6+
import cc.unitmesh.devti.language.provider.action.VariableActionEventDataHolder
7+
import cc.unitmesh.devti.vcs.VcsPrompting
8+
import com.intellij.ide.DataManager
9+
import com.intellij.openapi.actionSystem.DataContext
10+
import com.intellij.openapi.diagnostic.logger
11+
import com.intellij.openapi.editor.Editor
12+
import com.intellij.openapi.project.Project
13+
import com.intellij.openapi.vcs.VcsDataKeys
14+
import com.intellij.openapi.vcs.changes.Change
15+
import com.intellij.openapi.vcs.changes.CurrentContentRevision
16+
import com.intellij.openapi.vfs.VirtualFile
17+
import com.intellij.psi.PsiElement
18+
import com.intellij.vcs.commit.CommitWorkflowUi
19+
import com.intellij.vcs.log.VcsFullCommitDetails
20+
import com.intellij.vcs.log.VcsLogDataKeys
21+
import com.intellij.vcs.log.VcsLogFilterCollection
22+
import com.intellij.vcs.log.VcsLogProvider
23+
import com.intellij.vcs.log.impl.VcsProjectLog
24+
import com.intellij.vcs.log.visible.filters.VcsLogFilterObject
25+
import java.awt.EventQueue.invokeAndWait
26+
27+
28+
class GitToolchainVariableProvider : ToolchainVariableProvider {
29+
private val logger = logger<GitToolchainVariableProvider>()
30+
31+
override fun isResolvable(variable: ToolchainVariable, psiElement: PsiElement?, project: Project): Boolean {
32+
return when (variable) {
33+
VcsToolchainVariable.CurrentChanges -> true
34+
VcsToolchainVariable.HistoryCommitMessages -> true
35+
VcsToolchainVariable.CurrentBranch -> true
36+
VcsToolchainVariable.Diff -> true
37+
else -> false
38+
}
39+
}
40+
41+
override fun resolve(
42+
variable: ToolchainVariable,
43+
project: Project,
44+
editor: Editor,
45+
psiElement: PsiElement?,
46+
): ToolchainVariable {
47+
when (variable) {
48+
VcsToolchainVariable.CurrentChanges -> {
49+
val commitWorkflowUi = getCommitWorkflowUi()
50+
if (commitWorkflowUi !is CommitWorkflowUi) {
51+
logger.warn("Cannot get commit workflow UI, you may not be in a commit workflow.")
52+
return variable
53+
}
54+
var changes: List<Change>? = null
55+
invokeAndWait {
56+
changes = getDiff(commitWorkflowUi)
57+
}
58+
59+
if (changes == null) {
60+
logger.warn("Cannot get changes.")
61+
return variable
62+
}
63+
64+
val diffContext = project.getService(VcsPrompting::class.java).prepareContext(changes!!)
65+
66+
if (diffContext.isEmpty() || diffContext == "\n") {
67+
logger.warn("Diff context is empty or cannot get enough useful context.")
68+
return variable
69+
}
70+
71+
variable.value = diffContext
72+
return variable
73+
}
74+
75+
VcsToolchainVariable.CurrentBranch -> {
76+
val logProviders = VcsProjectLog.getLogProviders(project)
77+
val entry = logProviders.entries.firstOrNull() ?: return variable
78+
79+
val logProvider = entry.value
80+
val branch = logProvider.getCurrentBranch(entry.key) ?: return variable
81+
82+
variable.value = branch
83+
}
84+
85+
VcsToolchainVariable.HistoryCommitMessages -> {
86+
val exampleCommitMessages = getHistoryCommitMessages(project)
87+
if (exampleCommitMessages != null) {
88+
variable.value = exampleCommitMessages
89+
}
90+
}
91+
92+
VcsToolchainVariable.Diff -> {
93+
val dataContext = VariableActionEventDataHolder.getData()?.dataContext
94+
variable.value = analysisLog(dataContext, project)
95+
}
96+
}
97+
98+
return variable
99+
}
100+
101+
private fun analysisLog(dataContext: DataContext?, project: Project): String {
102+
if (dataContext == null) {
103+
return ""
104+
}
105+
106+
107+
val vcsLog = dataContext.getData(VcsLogDataKeys.VCS_LOG) ?: return ""
108+
109+
val details: List<VcsFullCommitDetails> = vcsLog.selectedDetails.toList()
110+
val selectList = dataContext.getData(VcsDataKeys.SELECTED_CHANGES).orEmpty().toList()
111+
112+
val vcsPrompting = project.getService(VcsPrompting::class.java)
113+
val fullChangeContent =
114+
vcsPrompting.buildDiffPrompt(details, selectList.toList(), project)
115+
116+
return fullChangeContent ?: ""
117+
}
118+
119+
/**
120+
* Finds example commit messages based on the project's VCS log, takes the first three commits.
121+
* If the no user or user has committed anything yet, the current branch name is used instead.
122+
*
123+
* @param project The project for which to find example commit messages.
124+
* @return A string containing example commit messages, or null if no example messages are found.
125+
*/
126+
private fun getHistoryCommitMessages(project: Project): String? {
127+
val logProviders = VcsProjectLog.getLogProviders(project)
128+
val entry = logProviders.entries.firstOrNull() ?: return null
129+
130+
val logProvider = entry.value
131+
val branch = logProvider.getCurrentBranch(entry.key) ?: return null
132+
val user = logProvider.getCurrentUser(entry.key)
133+
134+
val logFilter = if (user != null) {
135+
VcsLogFilterObject.collection(VcsLogFilterObject.fromUser(user, setOf()))
136+
} else {
137+
VcsLogFilterObject.collection(VcsLogFilterObject.fromBranch(branch))
138+
}
139+
140+
return collectExamples(logProvider, entry.key, logFilter)
141+
}
142+
143+
/**
144+
* Collects examples from the VcsLogProvider based on the provided filter.
145+
*
146+
* @param logProvider The VcsLogProvider used to retrieve commit information.
147+
* @param root The root VirtualFile of the project.
148+
* @param filter The VcsLogFilterCollection used to filter the commits.
149+
* @return A string containing the collected examples, or null if no examples are found.
150+
*/
151+
private fun collectExamples(
152+
logProvider: VcsLogProvider,
153+
root: VirtualFile,
154+
filter: VcsLogFilterCollection,
155+
): String? {
156+
val commits = logProvider.getCommitsMatchingFilter(root, filter, 3)
157+
158+
if (commits.isEmpty()) return null
159+
160+
val builder = StringBuilder("")
161+
val commitIds = commits.map { it.id.asString() }
162+
163+
logProvider.readMetadata(root, commitIds) {
164+
val shortMsg = it.fullMessage.split("\n").firstOrNull() ?: it.fullMessage
165+
builder.append(shortMsg).append("\n")
166+
}
167+
168+
return builder.toString()
169+
}
170+
171+
private fun getDiff(commitWorkflowUi: CommitWorkflowUi): List<Change>? {
172+
val changes = commitWorkflowUi.getIncludedChanges()
173+
val unversionedFiles = commitWorkflowUi.getIncludedUnversionedFiles()
174+
175+
val changeList = unversionedFiles.map {
176+
Change(null, CurrentContentRevision(it))
177+
}
178+
179+
if (changes.isNotEmpty() || changeList.isNotEmpty()) {
180+
return changes + changeList
181+
}
182+
183+
return null
184+
}
185+
186+
}
187+
188+
fun getCommitWorkflowUi(): CommitWorkflowUi? {
189+
VariableActionEventDataHolder.getData()?.dataContext?.let {
190+
val commitWorkflowUi = it.getData(VcsDataKeys.COMMIT_WORKFLOW_UI)
191+
return commitWorkflowUi as CommitWorkflowUi?
192+
}
193+
194+
val dataContext = DataManager.getInstance().dataContextFromFocus.result
195+
val commitWorkflowUi = dataContext?.getData(VcsDataKeys.COMMIT_WORKFLOW_UI)
196+
return commitWorkflowUi
197+
}

exts/ext-git/src/main/resources/cc.unitmesh.git.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,8 @@
4040

4141
<mcpTool implementation="cc.unitmesh.git.mcp.FindCommitByTextTool"/>
4242
<mcpTool implementation="cc.unitmesh.git.mcp.GetVcsStatusTool"/>
43+
44+
<shireActionLocationEditor implementation="cc.unitmesh.git.provider.GitActionLocationEditor"/>
45+
<shireToolchainVariableProvider implementation="cc.unitmesh.git.provider.GitToolchainVariableProvider"/>
4346
</extensions>
4447
</idea-plugin>

0 commit comments

Comments
 (0)