Skip to content

Commit e765d0b

Browse files
committed
feat: variable {hint} for prompt customization
Move default prompts from AppSettings to DefaultPrompts enum. Run tests in GitHub Actions.
1 parent b9058a4 commit e765d0b

File tree

10 files changed

+241
-95
lines changed

10 files changed

+241
-95
lines changed

.github/workflows/build.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,16 @@ jobs:
6262
./gradlew listProductsReleases # prepare list of IDEs for Plugin Verifier
6363
6464
# Run tests
65-
# - name: Run Tests
66-
# run: ./gradlew test
65+
- name: Run Tests
66+
run: ./gradlew test
6767

6868
# Collect Tests Result of failed tests
69-
# - name: Collect Tests Result
70-
# if: ${{ failure() }}
71-
# uses: actions/upload-artifact@v4
72-
# with:
73-
# name: tests-result
74-
# path: ${{ github.workspace }}/build/reports/tests
69+
- name: Collect Tests Result
70+
if: ${{ failure() }}
71+
uses: actions/upload-artifact@v4
72+
with:
73+
name: tests-result
74+
path: ${{ github.workspace }}/build/reports/tests
7575

7676
# Cache Plugin Verifier IDEs
7777
- name: Setup Plugin Verifier IDEs Cache

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
### Added
6+
7+
- Variable `{hint}' for prompt customization that is filled with content from commit message dialog.
8+
59
### Fixed
610

711
- Selected prompt resets after editing custom prompt.

build.gradle.kts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ tasks.withType<KotlinCompile>().configureEach {
9797
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
9898
}
9999

100+
tasks.test {
101+
useJUnitPlatform()
102+
}
103+
100104
dependencies {
101105
implementation("com.aallam.openai:openai-client:3.7.0") {
102106
exclude(group = "org.slf4j", module = "slf4j-api")
@@ -116,4 +120,9 @@ dependencies {
116120
}
117121

118122
implementation("com.knuddels:jtokkit:1.0.0")
123+
124+
// tests
125+
testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.0")
126+
testImplementation("org.junit.jupiter:junit-jupiter:5.8.2")
127+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
119128
}

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/AICommitAction.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ import com.intellij.openapi.actionSystem.AnActionEvent
1313
import com.intellij.openapi.progress.runBackgroundableTask
1414
import com.intellij.openapi.project.DumbAware
1515
import com.intellij.openapi.vcs.VcsDataKeys
16+
import com.intellij.openapi.vcs.ui.CommitMessage
1617
import com.intellij.vcs.commit.AbstractCommitWorkflowHandler
17-
import com.knuddels.jtokkit.Encodings
18-
import com.knuddels.jtokkit.api.ModelType
1918
import kotlinx.coroutines.Dispatchers
2019
import kotlinx.coroutines.runBlocking
2120

@@ -30,7 +29,7 @@ class AICommitAction : AnAction(), DumbAware {
3029
}
3130

3231
val includedChanges = commitWorkflowHandler.ui.getIncludedChanges()
33-
val commitMessage = VcsDataKeys.COMMIT_MESSAGE_CONTROL.getData(e.dataContext)
32+
val commitMessage = VcsDataKeys.COMMIT_MESSAGE_CONTROL.getData(e.dataContext) as CommitMessage?
3433

3534
runBackgroundableTask(message("action.background"), project) {
3635
val diff = computeDiff(includedChanges, false, project)
@@ -40,7 +39,8 @@ class AICommitAction : AnAction(), DumbAware {
4039
}
4140

4241
val branch = commonBranch(includedChanges, project)
43-
val prompt = constructPrompt(AppSettings.instance.currentPrompt.content, diff, branch)
42+
val hint = commitMessage?.text
43+
val prompt = constructPrompt(AppSettings.instance.currentPrompt.content, diff, branch, hint)
4444
if (isPromptTooLarge(prompt)) {
4545
sendNotification(Notification.promptTooLarge())
4646
return@runBackgroundableTask

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/AICommitsUtils.kt

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import java.nio.file.FileSystems
1717

1818
object AICommitsUtils {
1919

20-
fun isPathExcluded(path: String, project: Project) : Boolean {
20+
fun isPathExcluded(path: String, project: Project): Boolean {
2121
return !AppSettings.instance.isPathExcluded(path) && !project.service<ProjectSettings>().isPathExcluded(path)
2222
}
2323

@@ -32,10 +32,11 @@ object AICommitsUtils {
3232
return false
3333
}
3434

35-
fun constructPrompt(promptContent: String, diff: String, branch: String): String {
35+
fun constructPrompt(promptContent: String, diff: String, branch: String, hint: String?): String {
3636
var content = promptContent
3737
content = content.replace("{locale}", AppSettings.instance.locale.displayLanguage)
3838
content = content.replace("{branch}", branch)
39+
content = replaceHint(content, hint)
3940

4041
return if (content.contains("{diff}")) {
4142
content.replace("{diff}", diff)
@@ -44,6 +45,22 @@ object AICommitsUtils {
4445
}
4546
}
4647

48+
fun replaceHint(promptContent: String, hint: String?): String {
49+
val hintRegex = Regex("\\{[^{}]*(\\\$hint)[^{}]*}")
50+
51+
hintRegex.find(promptContent, 0)?.let {
52+
if (!hint.isNullOrBlank()) {
53+
var hintValue = it.value.replace("\$hint", hint)
54+
hintValue = hintValue.replace("{", "")
55+
hintValue = hintValue.replace("}", "")
56+
return promptContent.replace(it.value, hintValue)
57+
} else {
58+
return promptContent.replace(it.value, "")
59+
}
60+
}
61+
return promptContent.replace("{hint}", hint.orEmpty())
62+
}
63+
4764
fun commonBranch(changes: List<Change>, project: Project): String {
4865
val repositoryManager = GitRepositoryManager.getInstance(project)
4966
var branch = changes.map {
@@ -59,47 +76,47 @@ object AICommitsUtils {
5976
}
6077

6178
fun computeDiff(
62-
includedChanges: List<Change>,
63-
reversePatch: Boolean,
64-
project: Project
79+
includedChanges: List<Change>,
80+
reversePatch: Boolean,
81+
project: Project
6582
): String {
6683

6784
val gitRepositoryManager = GitRepositoryManager.getInstance(project)
6885

6986
// go through included changes, create a map of repository to changes and discard nulls
7087
val changesByRepository = includedChanges
71-
.filter {
72-
it.virtualFile?.path?.let { path ->
73-
AICommitsUtils.isPathExcluded(path, project)
74-
} ?: false
75-
}
76-
.mapNotNull { change ->
77-
change.virtualFile?.let { file ->
78-
gitRepositoryManager.getRepositoryForFileQuick(
79-
file
80-
) to change
81-
}
88+
.filter {
89+
it.virtualFile?.path?.let { path ->
90+
AICommitsUtils.isPathExcluded(path, project)
91+
} ?: false
92+
}
93+
.mapNotNull { change ->
94+
change.virtualFile?.let { file ->
95+
gitRepositoryManager.getRepositoryForFileQuick(
96+
file
97+
) to change
8298
}
83-
.groupBy({ it.first }, { it.second })
99+
}
100+
.groupBy({ it.first }, { it.second })
84101

85102

86103
// compute diff for each repository
87104
return changesByRepository
88-
.map { (repository, changes) ->
89-
repository?.let {
90-
val filePatches = IdeaTextPatchBuilder.buildPatch(
91-
project,
92-
changes,
93-
repository.root.toNioPath(), reversePatch, true
94-
)
95-
96-
val stringWriter = StringWriter()
97-
stringWriter.write("Repository: ${repository.root.path}\n")
98-
UnifiedDiffWriter.write(project, filePatches, stringWriter, "\n", null)
99-
stringWriter.toString()
100-
}
105+
.map { (repository, changes) ->
106+
repository?.let {
107+
val filePatches = IdeaTextPatchBuilder.buildPatch(
108+
project,
109+
changes,
110+
repository.root.toNioPath(), reversePatch, true
111+
)
112+
113+
val stringWriter = StringWriter()
114+
stringWriter.write("Repository: ${repository.root.path}\n")
115+
UnifiedDiffWriter.write(project, filePatches, stringWriter, "\n", null)
116+
stringWriter.toString()
101117
}
102-
.joinToString("\n")
118+
}
119+
.joinToString("\n")
103120
}
104121

105122
fun isPromptTooLarge(prompt: String): Boolean {

src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/AppSettings.kt

Lines changed: 3 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import com.aallam.openai.client.ProxyConfig
77
import com.github.blarc.ai.commits.intellij.plugin.AICommitsUtils
88
import com.github.blarc.ai.commits.intellij.plugin.notifications.Notification
99
import com.github.blarc.ai.commits.intellij.plugin.notifications.sendNotification
10-
import com.github.blarc.ai.commits.intellij.plugin.settings.prompt.Prompt
10+
import com.github.blarc.ai.commits.intellij.plugin.settings.prompt.DefaultPrompts
1111
import com.intellij.credentialStore.CredentialAttributes
1212
import com.intellij.credentialStore.Credentials
1313
import com.intellij.ide.passwordSafe.PasswordSafe
@@ -18,6 +18,7 @@ import com.intellij.openapi.components.Storage
1818
import com.intellij.util.xmlb.Converter
1919
import com.intellij.util.xmlb.XmlSerializerUtil
2020
import com.intellij.util.xmlb.annotations.OptionTag
21+
import org.jetbrains.kotlin.idea.gradleTooling.get
2122
import java.util.*
2223
import kotlin.time.Duration.Companion.seconds
2324

@@ -40,7 +41,7 @@ class AppSettings : PersistentStateComponent<AppSettings> {
4041
var openAISocketTimeout = "30"
4142
var proxyUrl: String? = null
4243

43-
var prompts = initPrompts()
44+
var prompts = DefaultPrompts.toPromptsMap()
4445
var currentPrompt = prompts["basic"]!!
4546

4647
var openAIModelId = "gpt-3.5-turbo"
@@ -105,47 +106,6 @@ class AppSettings : PersistentStateComponent<AppSettings> {
105106
return AICommitsUtils.matchesGlobs(path, appExclusions)
106107
}
107108

108-
private fun initPrompts() = mutableMapOf(
109-
// Generate UUIDs for game objects in Mine.py and call the function in start_game().
110-
"basic" to Prompt(
111-
"Basic",
112-
"Basic prompt that generates a decent commit message.",
113-
"Write an insightful but concise Git commit message in a complete sentence in present tense for the " +
114-
"following diff without prefacing it with anything, the response must be in the language {locale} and must " +
115-
"NOT be longer than 74 characters. The sent text will be the differences between files, where deleted lines" +
116-
" are prefixed with a single minus sign and added lines are prefixed with a single plus sign.\n" +
117-
"{diff}",
118-
false
119-
),
120-
// feat: generate unique UUIDs for game objects on Mine game start
121-
"conventional" to Prompt(
122-
"Conventional",
123-
"Prompt for commit message in the conventional commit convention.",
124-
"Write a commit message in the conventional commit convention. I'll send you an output " +
125-
"of 'git diff --staged' command, and you convert it into a commit message. " +
126-
"Lines must not be longer than 74 characters. Use {locale} language to answer. " +
127-
"End commit title with issue number if you can get it from the branch name: " +
128-
"{branch} in parenthesis.\n" +
129-
"{diff}",
130-
false
131-
),
132-
// ✨ feat(mine): Generate objects UUIDs and start team timers on game start
133-
"emoji" to Prompt(
134-
"Emoji",
135-
"Prompt for commit message in the conventional commit convention with GitMoji convention.",
136-
"Write a clean and comprehensive commit message in the conventional commit convention. " +
137-
"I'll send you an output of 'git diff --staged' command, and you convert " +
138-
"it into a commit message. " +
139-
"Use GitMoji convention to preface the commit. " +
140-
"Do NOT add any descriptions to the commit, only commit message. " +
141-
"Use the present tense. " +
142-
"Lines must not be longer than 74 characters. " +
143-
"Use {locale} language to answer.\n" +
144-
"{diff}",
145-
false
146-
)
147-
)
148-
149109
class LocaleConverter : Converter<Locale>() {
150110
override fun toString(value: Locale): String? {
151111
return value.toLanguageTag()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.github.blarc.ai.commits.intellij.plugin.settings.prompt
2+
3+
enum class DefaultPrompts(val title: String, val description: String, val content: String) {
4+
5+
// Generate UUIDs for game objects in Mine.py and call the function in start_game().
6+
BASIC(
7+
"Basic",
8+
"Basic prompt that generates a decent commit message.",
9+
"Write an insightful but concise Git commit message in a complete sentence in present tense for the " +
10+
"following diff without prefacing it with anything, the response must be in the language {locale} and must " +
11+
"NOT be longer than 74 characters. The sent text will be the differences between files, where deleted lines " +
12+
"are prefixed with a single minus sign and added lines are prefixed with a single plus sign.\n" +
13+
"{Use this hint to improve this commit message: \$hint\n}" +
14+
"{diff}"
15+
),
16+
// feat: generate unique UUIDs for game objects on Mine game start
17+
CONVENTIONAL(
18+
"Conventional",
19+
"Prompt for commit message in the conventional commit convention.",
20+
"Write a commit message in the conventional commit convention. I'll send you an output " +
21+
"of 'git diff --staged' command, and you convert it into a commit message. " +
22+
"Lines must not be longer than 74 characters. Use {locale} language to answer. " +
23+
"End commit title with issue number if you can get it from the branch name: " +
24+
"{branch} in parenthesis.\n" +
25+
"{Use this hint to improve this commit message: \$hint\n}" +
26+
"{diff}",
27+
),
28+
// ✨ feat(mine): Generate objects UUIDs and start team timers on game start
29+
EMOJI(
30+
"Emoji",
31+
"Prompt for commit message in the conventional commit convention with GitMoji convention.",
32+
"Write a clean and comprehensive commit message in the conventional commit convention. " +
33+
"I'll send you an output of 'git diff --staged' command, and you convert " +
34+
"it into a commit message. " +
35+
"Use GitMoji convention to preface the commit. " +
36+
"Do NOT add any descriptions to the commit, only commit message. " +
37+
"Use the present tense. " +
38+
"Lines must not be longer than 74 characters. " +
39+
"{Use this hint to improve this commit message: \$hint\n}" +
40+
"Use {locale} language to answer.\n" +
41+
"{diff}",
42+
);
43+
44+
companion object {
45+
fun toPromptsMap(): MutableMap<String, Prompt> {
46+
return entries.associateBy({ it.name.lowercase() }, DefaultPrompts::toPrompt).toMutableMap()
47+
}
48+
}
49+
50+
fun toPrompt(): Prompt {
51+
return Prompt(
52+
this.title,
53+
this.content,
54+
this.description,
55+
false
56+
)
57+
}
58+
}

0 commit comments

Comments
 (0)