diff --git a/CHANGELOG.md b/CHANGELOG.md index ef56a8d8..7ce67ad6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ com.intellij.modules.json -# pitest-intellij Changelog +# TestCraft Pro Changelog + ## [Unreleased] ### Added @@ -44,3 +45,32 @@ - i18n enhancement - bugfixes for no mutation can provide suggestion +## 1.0.8 - 2025-04-28 +### Project Launch +- Complete rebranding and expansion of functionality +- Migration from MIT to Apache 2.0 License +- New test management features + +### Added +- Enhanced test case management system +- Annotation validation and generation +- Assertion statement verification +- Support for both Gradle and Maven projects +- Configurable test quality rules + +### Carried Forward +- All PITest mutation testing capabilities +- Results visualization and navigation +- Editor integration features + +### Functionality Changed +- Run pitest in the test file's source root as the process working directory +- Dump pitest context into report directory for issue debug +- Enhance output information for issue debug + +## 1.0.9 - 2025-04-30 +- bugfix, backward compatibility to JDK1.8 support to run in older IntelliJ IDEA version +- upload to pitest-gradle also, for pitest-gradle user, please uninstall pitest-gradle plugin and reinstall the new one manged in [testcraft](https://plugins.jetbrains.com/plugin/27221-testcraft-pro). + +## 1.0.10 - 2025-05-06 +- bugfix, git command execution in a separate thread to avoid UI blocking diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 00000000..7326f74c --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,29 @@ +# Migration Guide: pitest-gradle to TestCraft Pro + +## Overview +This document guides users migrating from pitest-gradle to TestCraft Pro. + +## Changes +1. **Package Name Changes** + - Old: `com.github.jaksonlin.pitestintellij` + - New: `com.github.jaksonlin.testcraftpro` + +2. **Configuration Updates** + - Plugin settings are now under "TestCraft Pro" in IDE settings + - Additional configuration options for new features + +3. **Feature Enhancements** + - All existing PITest features remain available + - New features are automatically available after installation + +## Migration Steps +1. Uninstall pitest-gradle plugin +2. Install TestCraft Pro from JetBrains Marketplace +3. Reconfigure any custom settings in the new interface + +## Backwards Compatibility +- All existing PITest configurations will continue to work +- Existing mutation test results remain compatible + +## Support +For migration assistance, please open an issue in the new repository. \ No newline at end of file diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..40fa427c --- /dev/null +++ b/NOTICE @@ -0,0 +1,13 @@ +TestCraft Pro +Copyright 2024 Jakson Lin + +This product includes software developed by: +- The Apache Software Foundation (http://www.apache.org/) +- JetBrains s.r.o. (IntelliJ Platform) +- JavaParser (https://javaparser.org) +- FasterXML, LLC (Jackson Project) +- JUnit Team (JUnit Library) + +This product is a derivative work that evolved from pitest-gradle. +The original pitest-gradle was licensed under MIT and has been rewritten +and relicensed as TestCraft Pro under Apache License 2.0. \ No newline at end of file diff --git a/README.md b/README.md index cbd43f79..0b4ffbb3 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,21 @@ # TestCraft -TestCraft is a comprehensive testing assistant plugin for IntelliJ IDEA that enhances your testing workflow with mutation testing, test case management, and AI-powered test analysis. Originally focused on PITest integration, TestCraft has evolved to provide a complete suite of testing tools. +TestCraft is the evolution of the [pitest-gradle](https://plugins.jetbrains.com/plugin/25546-pitest-gradle) plugin, expanding its capabilities beyond Gradle-based PITest mutation testing. While the original plugin focused on running PITest mutation testing for Java Gradle projects, TestCraft now offers a comprehensive testing suite that includes: + +- PITest mutation testing support for both Gradle and Maven projects +- Advanced test case management features +- AI-powered test analysis capabilities + +The plugin has been renamed to TestCraft to better represent its expanded functionality and modern testing features. Current version: 1.0.8 (continuing from the last pitest-gradle version). + +## Migration from pitest-gradle + +- uninstall pitest-gradle +- install TestCraft plugin + ## Features ### Mutation Testing diff --git a/build.gradle.kts b/build.gradle.kts index f5202934..f1695862 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,14 +1,19 @@ import org.jetbrains.changelog.Changelog import org.jetbrains.changelog.markdownToHTML +import org.jetbrains.intellij.platform.gradle.TestFrameworkType plugins { id("java") // Java support alias(libs.plugins.kotlin) // Kotlin support - alias(libs.plugins.gradleIntelliJPlugin) // IntelliJ Platform Gradle Plugin + alias(libs.plugins.intelliJPlatform) // IntelliJ Platform Gradle Plugin alias(libs.plugins.changelog) // Gradle Changelog Plugin alias(libs.plugins.qodana) // Gradle Qodana Plugin alias(libs.plugins.kover) // Gradle Kover Plugin - alias(libs.plugins.serialize) // Gradle Serializer Plugin +} + +// utf-8 +tasks.withType { + options.encoding = "UTF-8" } group = providers.gradleProperty("pluginGroup").get() @@ -16,30 +21,32 @@ version = providers.gradleProperty("pluginVersion").get() // Set the JVM language level used to build the project. kotlin { - jvmToolchain(17) + jvmToolchain(21) +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) // Use JDK 21 + } + sourceCompatibility = JavaVersion.VERSION_1_8 // Generate bytecode for Java 8 + targetCompatibility = JavaVersion.VERSION_1_8 } // Configure project's dependencies repositories { mavenCentral() - maven { url = uri("https://www.jetbrains.com/intellij-repository/releases") } - maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") } -} -intellij { - pluginName.set(providers.gradleProperty("pluginName")) - version.set(providers.gradleProperty("platformVersion")) - type.set(providers.gradleProperty("platformType")) - - // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file. - plugins.set(providers.gradleProperty("platformBundledPlugins") - .map { it.split(',').map(String::trim).filter(String::isNotEmpty) } - ) + // IntelliJ Platform Gradle Plugin Repositories Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-repositories-extension.html + intellijPlatform { + defaultRepositories() + } } // Dependencies are managed with Gradle version catalog - read more: https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog dependencies { testImplementation(libs.junit) + testImplementation(libs.opentest4j) + // Add Gradle Tooling API dependency implementation(fileTree("lib") { include("*.jar") }) implementation("com.github.javaparser:javaparser-core:3.24.2") // Add JavaParser dependency @@ -48,43 +55,33 @@ dependencies { implementation("com.fasterxml.jackson.core:jackson-databind:2.18.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") // Use latest version implementation("org.commonmark:commonmark:0.21.0") -} + implementation("org.apache.httpcomponents:httpclient:4.5.13") -// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin -changelog { - groups.empty() - repositoryUrl = providers.gradleProperty("pluginRepositoryUrl") -} + // IntelliJ Platform Gradle Plugin Dependencies Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html + intellijPlatform { + create(providers.gradleProperty("platformType"), providers.gradleProperty("platformVersion")) -// Configure Gradle Qodana Plugin - read more: https://github.com/JetBrains/gradle-qodana-plugin -qodana { - cachePath = provider { file(".qodana").canonicalPath } - reportPath = provider { file("build/reports/inspections").canonicalPath } - saveReport = true - showReport = System.getenv("QODANA_SHOW_REPORT")?.toBoolean() ?: false -} + // Plugin Dependencies. Uses `platformBundledPlugins` property from the gradle.properties file for bundled IntelliJ Platform plugins. + bundledPlugins(providers.gradleProperty("platformBundledPlugins").map { it.split(',') }) -// Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration -koverReport { - defaults { - xml { - onCheck = true - } - } -} + // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file for plugin from JetBrains Marketplace. + plugins(providers.gradleProperty("platformPlugins").map { it.split(',') }) + + testFramework(TestFrameworkType.Platform) + + bundledPlugin("com.intellij.modules.json") -tasks { - wrapper { - gradleVersion = providers.gradleProperty("gradleVersion").get() } +} - patchPluginXml { - version.set(providers.gradleProperty("pluginVersion")) - sinceBuild.set(providers.gradleProperty("pluginSinceBuild")) - untilBuild.set(providers.gradleProperty("pluginUntilBuild")) +// Configure IntelliJ Platform Gradle Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html +intellijPlatform { + pluginConfiguration { + name = providers.gradleProperty("pluginName") + version = providers.gradleProperty("pluginVersion") // Extract the section from README.md and provide for the plugin's manifest - pluginDescription = providers.fileContents(layout.projectDirectory.file("README.md")).asText.map { + description = providers.fileContents(layout.projectDirectory.file("README.md")).asText.map { val start = "" val end = "" @@ -108,34 +105,78 @@ tasks { ) } } - } - // Configure UI tests plugin - // Read more: https://github.com/JetBrains/intellij-ui-test-robot - runIdeForUiTests { - systemProperty("robot-server.port", "8082") - systemProperty("ide.mac.message.dialogs.as.sheets", "false") - systemProperty("jb.privacy.policy.text", "") - systemProperty("jb.consents.confirmation.enabled", "false") + ideaVersion { + sinceBuild = providers.gradleProperty("pluginSinceBuild") + untilBuild = providers.gradleProperty("pluginUntilBuild") + } } - signPlugin { - certificateChain = System.getenv("CERTIFICATE_CHAIN") - privateKey = System.getenv("PRIVATE_KEY") - password = System.getenv("PRIVATE_KEY_PASSWORD") + signing { + certificateChain = providers.environmentVariable("CERTIFICATE_CHAIN") + privateKey = providers.environmentVariable("PRIVATE_KEY") + password = providers.environmentVariable("PRIVATE_KEY_PASSWORD") } - publishPlugin { - dependsOn("patchChangelog") - token = System.getenv("PUBLISH_TOKEN") + publishing { + token = providers.environmentVariable("PUBLISH_TOKEN") // The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel channels = providers.gradleProperty("pluginVersion").map { listOf(it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) } } - // Force English locale for development and testing + pluginVerification { + ides { + recommended() + } + } +} + +// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin +changelog { + groups.empty() + repositoryUrl = providers.gradleProperty("pluginRepositoryUrl") +} + +// Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration +kover { + reports { + total { + xml { + onCheck = true + } + } + } +} + +tasks { + wrapper { + gradleVersion = providers.gradleProperty("gradleVersion").get() + } + + publishPlugin { + dependsOn(patchChangelog) + } +} + +intellijPlatformTesting { runIde { - jvmArgs("-Duser.language=zh", "-Duser.country=CN") + register("runIdeForUiTests") { + task { + jvmArgumentProviders += CommandLineArgumentProvider { + listOf( + "-Drobot-server.port=8082", + "-Dide.mac.message.dialogs.as.sheets=false", + "-Djb.privacy.policy.text=", + "-Djb.consents.confirmation.enabled=false", + ) + } + } + + plugins { + robotServerPlugin() + } + } } } \ No newline at end of file diff --git a/docs/features/test-case-tracking.md b/docs/features/test-case-tracking.md new file mode 100644 index 00000000..927d7219 --- /dev/null +++ b/docs/features/test-case-tracking.md @@ -0,0 +1,155 @@ +# 测试用例全局追踪功能需求计划 + +## 概述 +TestCraft插件计划增强测试用例管理功能,实现全局追踪能力,帮助团队更好地管理和维护测试用例。 + +## 当前功能 +- ✅ 测试用例注解管理 + - 自定义测试用例注解(@TestCase) + - 可配置注解字段(作者、标题、目标类、目标方法等) + - 测试点(TestPoints)标记 + - 测试状态管理(TODO, DONE, BROKEN, DEPRECATED) + +- ✅ 测试用例验证 + - 检查测试方法注解完整性 + - 验证测试断言有效性 + - 检查测试文档完整性(步骤和断言注释) + +- ✅ 测试用例生成 + - 自动生成测试用例注解 + - 智能代码补全 + +## 计划功能 + +### 1. 测试用例索引功能 +- [ ] 建立测试用例全局索引 + - 实现测试用例的快速索引建立 + - 支持增量更新 + - 提供索引重建机制 + +- [ ] 多维度搜索 + - 按类名搜索 + - 按方法名搜索 + - 按状态搜索 + - 按测试点搜索 + - 按作者搜索 + - 按时间范围搜索 + +- [ ] 统计和分析 + - 测试用例数量统计 + - 测试用例状态分布 + - 测试点覆盖分析 + - 测试用例质量评分 + +### 2. 测试用例关联 +- [ ] 需求关联 + - 支持与需求ID关联 + - 需求覆盖率分析 + - 未覆盖需求识别 + +- [ ] 缺陷关联 + - 支持与缺陷ID关联 + - 缺陷修复验证追踪 + - 回归测试用例识别 + +- [ ] 依赖关系分析 + - 测试用例间依赖关系 + - 测试用例与代码依赖关系 + - 依赖变更影响分析 + +### 3. 测试用例报告 +- [ ] 覆盖率报告 + - 测试用例覆盖率统计 + - 代码覆盖率分析 + - 分支覆盖率分析 + +- [ ] 质量分析报告 + - 测试用例完整性评估 + - 测试用例有效性分析 + - 测试用例维护性评分 + +- [ ] 执行历史追踪 + - 测试用例执行记录 + - 执行结果分析 + - 失败模式识别 + +### 4. 测试用例导航 +- [ ] 快速导航 + - 测试用例跳转 + - 相关测试用例推荐 + - 测试用例分组浏览 + +- [ ] 批量操作 + - 批量状态更新 + - 批量标签管理 + - 批量导出/导入 + +- [ ] 重构支持 + - 测试用例重命名 + - 测试用例移动 + - 测试用例合并/拆分 + +## 优先级规划 + +### 第一阶段(基础功能) +1. 测试用例全局索引 +2. 多维度搜索 +3. 基础统计功能 + +### 第二阶段(增强功能) +1. 需求关联 +2. 缺陷关联 +3. 覆盖率报告 + +### 第三阶段(高级功能) +1. 依赖关系分析 +2. 质量分析报告 +3. 执行历史追踪 + +### 第四阶段(优化功能) +1. 快速导航 +2. 批量操作 +3. 重构支持 + +## 技术实现要点 +1. 索引存储 + - 使用本地文件系统存储索引 + - 考虑使用轻量级数据库 + - 实现增量更新机制 + +2. 性能优化 + - 索引建立优化 + - 搜索性能优化 + - 内存使用优化 + +3. 用户体验 + - 提供进度反馈 + - 支持取消操作 + - 错误处理和恢复 + +## 里程碑计划 +- M1: 完成基础索引功能 +- M2: 实现多维度搜索 +- M3: 完成需求关联功能 +- M4: 实现覆盖率报告 +- M5: 完成高级分析功能 +- M6: 实现优化功能 + +## 风险评估 +1. 性能风险 + - 大型项目索引建立可能耗时 + - 搜索性能可能受影响 + +2. 兼容性风险 + - 不同IDE版本的兼容性 + - 不同项目结构的兼容性 + +3. 维护风险 + - 索引数据一致性维护 + - 功能扩展的复杂性 + +## 后续计划 +1. 用户反馈收集 +2. 性能优化 +3. 功能迭代 +4. 文档完善 \ No newline at end of file diff --git a/docs/images/test-management/1.png b/docs/images/test-management/1.png new file mode 100644 index 00000000..6e538f17 Binary files /dev/null and b/docs/images/test-management/1.png differ diff --git a/docs/images/test-management/10.png b/docs/images/test-management/10.png new file mode 100644 index 00000000..b1781161 Binary files /dev/null and b/docs/images/test-management/10.png differ diff --git a/docs/images/test-management/11.png b/docs/images/test-management/11.png new file mode 100644 index 00000000..8b517dbd Binary files /dev/null and b/docs/images/test-management/11.png differ diff --git a/docs/images/test-management/12.png b/docs/images/test-management/12.png new file mode 100644 index 00000000..0fa1721e Binary files /dev/null and b/docs/images/test-management/12.png differ diff --git a/docs/images/test-management/13.png b/docs/images/test-management/13.png new file mode 100644 index 00000000..ac38637d Binary files /dev/null and b/docs/images/test-management/13.png differ diff --git a/docs/images/test-management/14.png b/docs/images/test-management/14.png new file mode 100644 index 00000000..98a32029 Binary files /dev/null and b/docs/images/test-management/14.png differ diff --git a/docs/images/test-management/15.png b/docs/images/test-management/15.png new file mode 100644 index 00000000..72dcd920 Binary files /dev/null and b/docs/images/test-management/15.png differ diff --git a/docs/images/test-management/16.png b/docs/images/test-management/16.png new file mode 100644 index 00000000..64d2f22e Binary files /dev/null and b/docs/images/test-management/16.png differ diff --git a/docs/images/test-management/17.png b/docs/images/test-management/17.png new file mode 100644 index 00000000..8b653825 Binary files /dev/null and b/docs/images/test-management/17.png differ diff --git a/docs/images/test-management/18.png b/docs/images/test-management/18.png new file mode 100644 index 00000000..63681ddd Binary files /dev/null and b/docs/images/test-management/18.png differ diff --git a/docs/images/test-management/19.png b/docs/images/test-management/19.png new file mode 100644 index 00000000..119ecdf9 Binary files /dev/null and b/docs/images/test-management/19.png differ diff --git a/docs/images/test-management/2.png b/docs/images/test-management/2.png new file mode 100644 index 00000000..a4c8cc10 Binary files /dev/null and b/docs/images/test-management/2.png differ diff --git a/docs/images/test-management/20.png b/docs/images/test-management/20.png new file mode 100644 index 00000000..72c3a100 Binary files /dev/null and b/docs/images/test-management/20.png differ diff --git a/docs/images/test-management/21.png b/docs/images/test-management/21.png new file mode 100644 index 00000000..b7d9c578 Binary files /dev/null and b/docs/images/test-management/21.png differ diff --git a/docs/images/test-management/22.png b/docs/images/test-management/22.png new file mode 100644 index 00000000..ff9e18e2 Binary files /dev/null and b/docs/images/test-management/22.png differ diff --git a/docs/images/test-management/23.png b/docs/images/test-management/23.png new file mode 100644 index 00000000..3ef222b3 Binary files /dev/null and b/docs/images/test-management/23.png differ diff --git a/docs/images/test-management/3.png b/docs/images/test-management/3.png new file mode 100644 index 00000000..ff611930 Binary files /dev/null and b/docs/images/test-management/3.png differ diff --git a/docs/images/test-management/4.png b/docs/images/test-management/4.png new file mode 100644 index 00000000..fc0e8cf3 Binary files /dev/null and b/docs/images/test-management/4.png differ diff --git a/docs/images/test-management/5.png b/docs/images/test-management/5.png new file mode 100644 index 00000000..0058f321 Binary files /dev/null and b/docs/images/test-management/5.png differ diff --git a/docs/images/test-management/6.png b/docs/images/test-management/6.png new file mode 100644 index 00000000..3d8da3d9 Binary files /dev/null and b/docs/images/test-management/6.png differ diff --git a/docs/images/test-management/7.png b/docs/images/test-management/7.png new file mode 100644 index 00000000..6055f844 Binary files /dev/null and b/docs/images/test-management/7.png differ diff --git a/docs/images/test-management/8.png b/docs/images/test-management/8.png new file mode 100644 index 00000000..c1bf505b Binary files /dev/null and b/docs/images/test-management/8.png differ diff --git a/docs/images/test-management/9.png b/docs/images/test-management/9.png new file mode 100644 index 00000000..cdecd8fa Binary files /dev/null and b/docs/images/test-management/9.png differ diff --git a/gradle.properties b/gradle.properties index beef784f..214d6a97 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,29 +1,29 @@ # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html -pluginGroup = com.github.jaksonlin.pitestintellij -pluginName = pitest-gradle -pluginRepositoryUrl = https://github.com/jaksonlin/pitest-gradle +pluginGroup = com.github.jaksonlin.testcraft +pluginName = TestCraft-Pro +pluginRepositoryUrl = https://github.com/jaksonlin/testcraft-pro # SemVer format -> https://semver.org -pluginVersion = 1.0.7 +pluginVersion = 1.0.10 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 201 -pluginUntilBuild = 243.* +pluginUntilBuild = 252.* # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension platformType = IC -platformVersion = 2022.2 +platformVersion = 2025.1 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.jetbrains.php:203.4449.22, org.intellij.scala:2023.3.27@EAP platformPlugins = # Example: platformBundledPlugins = com.intellij.java -platformBundledPlugins = com.intellij.java, com.intellij.gradle,com.intellij.java,Git4Idea +platformBundledPlugins = com.intellij.java, com.intellij.gradle, com.intellij.java, Git4Idea, com.intellij.modules.json # Gradle Releases -> https://github.com/gradle/gradle/releases -gradleVersion = 8.9 +gradleVersion = 8.13 # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib kotlin.stdlib.default.dependency = false @@ -32,4 +32,4 @@ kotlin.stdlib.default.dependency = false org.gradle.configuration-cache = true # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html -org.gradle.caching = true +org.gradle.caching = true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6293ac1f..22f6abe7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,23 +1,22 @@ [versions] # libraries junit = "4.13.2" -annotations = "24.0.1" +opentest4j = "1.3.0" # plugins -kotlin = "1.9.25" -changelog = "2.1.2" -gradleIntelliJPlugin = "1.17.4" -qodana = "0.1.13" -kover = "0.7.3" +changelog = "2.2.1" +intelliJPlatform = "2.5.0" +kotlin = "2.1.20" +kover = "0.9.1" +qodana = "2024.3.4" [libraries] junit = { group = "junit", name = "junit", version.ref = "junit" } -annotations = { group = "org.jetbrains", name = "annotations", version.ref = "annotations" } +opentest4j = { group = "org.opentest4j", name = "opentest4j", version.ref = "opentest4j" } [plugins] changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } -gradleIntelliJPlugin = { id = "org.jetbrains.intellij", version.ref = "gradleIntelliJPlugin" } +intelliJPlatform = { id = "org.jetbrains.intellij.platform", version.ref = "intelliJPlatform" } kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } -qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" } -serialize = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 96c0666a..aa46249a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,4 +2,4 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } -rootProject.name = "pitest-gradle" +rootProject.name = "testcraft-pro" diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/MyBundle.java b/src/main/java/com/github/jaksonlin/pitestintellij/MyBundle.java deleted file mode 100644 index fbc2c0f3..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/MyBundle.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.jaksonlin.pitestintellij; - -import com.intellij.DynamicBundle; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.PropertyKey; - -import java.util.function.Supplier; - -public final class MyBundle extends DynamicBundle { - @NonNls - private static final String BUNDLE = "messages.MyBundle"; - private static final MyBundle INSTANCE = new MyBundle(); - - private MyBundle() { - super(BUNDLE); - } - - public static @NotNull String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object... params) { - return INSTANCE.getMessage(key, params); - } - - public static @NotNull Supplier messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object... params) { - return INSTANCE.getLazyMessage(key, params); - } - - public static MyBundle getInstance() { - return INSTANCE; - } -} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/actions/RunPitestAction.java b/src/main/java/com/github/jaksonlin/pitestintellij/actions/RunPitestAction.java deleted file mode 100644 index 5b170547..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/actions/RunPitestAction.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.jaksonlin.pitestintellij.actions; - -import com.github.jaksonlin.pitestintellij.services.PitestService; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; - -public class RunPitestAction extends AnAction { - - private final PitestService pitestService; - - public RunPitestAction() { - pitestService = ApplicationManager.getApplication().getService(PitestService.class); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - Project targetProject = e.getProject(); - if (targetProject == null) { - return; - } - - VirtualFile testVirtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); - if (testVirtualFile == null) { - return; - } - - pitestService.runPitest(targetProject, testVirtualFile.getPath()); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationSchema.java b/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationSchema.java deleted file mode 100644 index 99ef693b..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationSchema.java +++ /dev/null @@ -1,290 +0,0 @@ -package com.github.jaksonlin.pitestintellij.annotations; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.util.List; - -public class AnnotationSchema { - private String annotationClassName; - private List fields; - public static final String DEFAULT_SCHEMA = """ - { - "annotationClassName": "UnittestCaseInfo", - "fields": [ - { - "name": "author", - "type": "STRING", - "required": true, - "valueProvider": { - "type": "FIRST_CREATOR_AUTHOR" - }, - "validation": { - "allowEmpty": false - }, - "defaultValue": { - "type": "StringValue", - "value": "" - } - }, - { - "name": "title", - "type": "STRING", - "required": true, - "valueProvider": { - "type": "METHOD_NAME_BASED" - }, - "validation": { - "allowEmpty": false - }, - "defaultValue": { - "type": "StringValue", - "value": "" - } - }, - { - "name": "targetClass", - "type": "STRING", - "required": true, - "valueProvider": { - "type": "CLASS_NAME" - }, - "validation": { - "allowEmpty": false - }, - "defaultValue": { - "type": "StringValue", - "value": "" - } - }, - { - "name": "targetMethod", - "type": "STRING", - "required": true, - "valueProvider": { - "type": "METHOD_NAME" - }, - "validation": { - "allowEmpty": false - }, - "defaultValue": { - "type": "StringValue", - "value": "" - } - }, - { - "name": "lastUpdateTime", - "type": "STRING", - "required": true, - "valueProvider": { - "type": "LAST_MODIFIER_TIME", - "format": "yyyy-MM-dd HH:mm:ss" - }, - "validation": { - "allowEmpty": false - }, - "defaultValue": { - "type": "StringValue", - "value": "" - } - }, - { - "name": "lastUpdateAuthor", - "type": "STRING", - "required": true, - "valueProvider": { - "type": "LAST_MODIFIER_AUTHOR" - }, - "validation": { - "allowEmpty": false - }, - "defaultValue": { - "type": "StringValue", - "value": "" - } - }, - { - "name": "methodSignature", - "type": "STRING", - "required": true, - "valueProvider": { - "type": "METHOD_SIGNATURE" - }, - "validation": { - "allowEmpty": false - }, - "defaultValue": { - "type": "StringValue", - "value": "" - } - }, - { - "name": "testPoints", - "type": "STRING_LIST", - "required": false, - "valueProvider": { - "type": "FIXED_VALUE", - "value": ["Functionality"] - }, - "defaultValue": { - "type": "StringListValue", - "value": [] - }, - "validation": { - "validValues": [ - "BoundaryValue", - "NonEmpty", - "ErrorHandling", - "InputValidation", - "PositiveScenario", - "NegativeScenario", - "EdgeCase", - "Functionality", - "BusinessLogicValidation", - "BusinessInputOutput", - "SideEffects", - "StateTransition", - "BusinessCalculation", - "Security", - "Performance" - ], - "allowCustomValues": true, - "mode": "CONTAINS", - "allowEmpty": true - } - }, - { - "name": "status", - "type": "STRING", - "required": false, - "valueProvider": { - "type": "FIXED_VALUE", - "value": "TODO" - }, - "defaultValue": { - "type": "StringValue", - "value": "TODO" - }, - "validation": { - "validValues": [ - "TODO", - "IN_PROGRESS", - "DONE", - "BLOCKED" - ], - "allowCustomValues": false, - "mode": "EXACT", - "allowEmpty": false - } - }, - { - "name": "description", - "type": "STRING", - "required": false, - "valueProvider": { - "type": "METHOD_NAME_BASED" - }, - "validation": { - "allowEmpty": true - }, - "defaultValue": { - "type": "StringValue", - "value": "" - } - }, - { - "name": "tags", - "type": "STRING_LIST", - "required": false, - "defaultValue": { - "type": "StringListValue", - "value": [] - }, - "validation": { - "allowEmpty": true - } - }, - { - "name": "relatedRequirements", - "type": "STRING_LIST", - "required": false, - "defaultValue": { - "type": "StringListValue", - "value": [] - }, - "validation": { - "allowEmpty": true - } - }, - { - "name": "relatedTestcases", - "type": "STRING_LIST", - "required": false, - "defaultValue": { - "type": "StringListValue", - "value": [] - }, - "validation": { - "allowEmpty": true - } - }, - { - "name": "relatedDefects", - "type": "STRING_LIST", - "required": false, - "defaultValue": { - "type": "StringListValue", - "value": [] - }, - "validation": { - "allowEmpty": true - } - } - ] - } - """; - - public AnnotationSchema() { - } - - public AnnotationSchema(String annotationClassName, List fields) { - this.annotationClassName = annotationClassName; - this.fields = fields; - } - - public String getAnnotationClassName() { - return annotationClassName; - } - - public void setAnnotationClassName(String annotationClassName) { - this.annotationClassName = annotationClassName; - } - - public List getFields() { - return fields; - } - - public void setFields(List fields) { - this.fields = fields; - } - - public static class Companion { - private static final ObjectMapper json; - - static { - json = new ObjectMapper(); - } - - @Nullable - public static AnnotationSchema fromJson(@NotNull String jsonString) { - try { - return json.readValue(jsonString, AnnotationSchema.class); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/components/ChatPanel.java b/src/main/java/com/github/jaksonlin/pitestintellij/components/ChatPanel.java deleted file mode 100644 index 3aa271a8..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/components/ChatPanel.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.github.jaksonlin.pitestintellij.components; - -import com.intellij.ui.components.JBScrollPane; -import com.intellij.util.ui.JBUI; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.awt.event.KeyAdapter; -import java.util.ArrayList; -import java.util.List; - -public class ChatPanel extends JPanel { - private final JTextArea inputArea; - private final JButton sendButton; - private final List listeners = new ArrayList<>(); - private final JPanel inputPanel; - - public interface ChatMessageListener { - void onNewMessage(String message); - } - - public ChatPanel() { - setLayout(new BorderLayout()); - - // Input area - inputArea = new JTextArea(3, 40); - inputArea.setLineWrap(true); - inputArea.setWrapStyleWord(true); - JBScrollPane inputScrollPane = new JBScrollPane(inputArea); - - // Send button - sendButton = new JButton("Send"); - sendButton.addActionListener(e -> sendMessage()); - - // Input panel (input area + send button) - inputPanel = new JPanel(new BorderLayout()); - inputPanel.setBorder(JBUI.Borders.empty(5)); - inputPanel.add(inputScrollPane, BorderLayout.CENTER); - inputPanel.add(sendButton, BorderLayout.EAST); - - // Add key listener for Ctrl+Enter to send - inputArea.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER && e.isControlDown()) { - sendMessage(); - e.consume(); - } - } - }); - } - - public void addListener(ChatMessageListener listener) { - listeners.add(listener); - } - - public JPanel getInputPanel() { - return inputPanel; - } - - private void sendMessage() { - String message = inputArea.getText().trim(); - if (!message.isEmpty()) { - listeners.forEach(listener -> listener.onNewMessage(message)); - inputArea.setText(""); - inputArea.requestFocus(); - } - } - - public void clear() { - inputArea.setText(""); - } - - public void setInputEnabled(boolean enabled) { - inputArea.setEnabled(enabled); - sendButton.setEnabled(enabled); - if (enabled) { - inputArea.requestFocus(); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/components/LLMResponsePanel.java b/src/main/java/com/github/jaksonlin/pitestintellij/components/LLMResponsePanel.java deleted file mode 100644 index 3ac127bd..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/components/LLMResponsePanel.java +++ /dev/null @@ -1,413 +0,0 @@ -package com.github.jaksonlin.pitestintellij.components; - -import com.github.jaksonlin.pitestintellij.observers.BasicEventObserver; -import com.intellij.ui.components.JBScrollPane; -import com.intellij.util.ui.JBUI; -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.HtmlRenderer; -import com.intellij.ui.JBColor; -import java.awt.datatransfer.StringSelection; -import java.awt.Toolkit; -import com.intellij.AbstractBundle; -import org.jetbrains.annotations.PropertyKey; - -import javax.swing.*; -import javax.swing.text.html.HTMLEditorKit; -import javax.swing.text.html.StyleSheet; -import java.awt.*; -import java.util.ArrayList; -import java.util.Timer; -import java.util.TimerTask; -import java.util.List; -import java.util.ResourceBundle; -import javax.swing.text.html.HTMLDocument; - -public class LLMResponsePanel extends JPanel implements BasicEventObserver { - private static final String BUNDLE = "messages.MyBundle"; - private static ResourceBundle ourBundle; - - public static String message(@PropertyKey(resourceBundle = BUNDLE) String key, Object... params) { - return AbstractBundle.message(getBundle(), key, params); - } - - private static ResourceBundle getBundle() { - if (ourBundle == null) { - ourBundle = ResourceBundle.getBundle(BUNDLE); - } - return ourBundle; - } - - private final JEditorPane outputArea; - private final ChatPanel chatPanel; - private boolean isLoading = false; - private boolean copyAsMarkdown = false; - private final StringBuilder chatHistory = new StringBuilder(); - private final HTMLEditorKit htmlKit; - private final StyleSheet styleSheet; - private final JPanel loadingPanel; - private final Timer loadingTimer; - private final JLabel loadingLabel; - - private static final String BASE_HTML_TEMPLATE = """ - - - - - - -
- %s -
- - - """; - - private static final String MESSAGE_TEMPLATE = """ -
-
%s
-
%s
-
-
- """; - - public interface ResponseActionListener { - void onClearButtonClick(); - void onCopyButtonClick(); - } - - private List responseActionListeners = new ArrayList<>(); - - public void addResponseActionListener(ResponseActionListener listener) { - responseActionListeners.add(listener); - } - - public void removeResponseActionListener(ResponseActionListener listener) { - responseActionListeners.remove(listener); - } - - public void notifyClearButtonClick() { - for (ResponseActionListener listener : responseActionListeners) { - listener.onClearButtonClick(); - } - } - - public void notifyCopyButtonClick() { - for (ResponseActionListener listener : responseActionListeners) { - listener.onCopyButtonClick(); - } - } - - public LLMResponsePanel(ChatPanel chatPanel) { - this.chatPanel = chatPanel; - setLayout(new BorderLayout()); - setBorder(JBUI.Borders.empty(10)); - - // Setup improved JEditorPane for HTML rendering - outputArea = new JEditorPane(); - outputArea.setEditable(false); - outputArea.setContentType("text/html"); - - // Configure HTML editor kit with custom style sheet - htmlKit = new HTMLEditorKit(); - styleSheet = htmlKit.getStyleSheet(); - styleSheet.addRule(getCodeStyle()); - outputArea.setEditorKit(htmlKit); - - // Initialize with empty document - HTMLDocument doc = (HTMLDocument) htmlKit.createDefaultDocument(); - outputArea.setDocument(doc); - - // Enable proper HTML rendering - outputArea.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); - outputArea.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13)); - - // Create loading panel - loadingPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - loadingPanel.setVisible(false); - loadingLabel = new JLabel(message("llm.thinking")); - loadingPanel.add(loadingLabel); - loadingPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10)); - loadingPanel.setBackground(JBColor.background()); - - // Setup loading animation timer - loadingTimer = new Timer(); - - JBScrollPane outputScrollPane = new JBScrollPane(outputArea); - - // Create toolbar - JToolBar toolbar = new JToolBar(); - toolbar.setFloatable(false); - toolbar.setBorder(JBUI.Borders.empty(2, 2)); - - JButton copyButton = new JButton(message("llm.copy.to.clipboard")); - copyButton.addActionListener(e -> { - notifyCopyButtonClick(); - }); - toolbar.add(copyButton); - - JButton clearButton = new JButton(message("llm.clear")); - clearButton.addActionListener(e -> { - clearOutput(); - notifyClearButtonClick(); - }); - toolbar.add(clearButton); - - // Create input panel at the bottom - JPanel inputPanel = new JPanel(new BorderLayout()); - inputPanel.add(chatPanel.getInputPanel(), BorderLayout.CENTER); - - // Create center panel to hold toolbar, output area, and loading panel - JPanel centerPanel = new JPanel(new BorderLayout()); - centerPanel.add(toolbar, BorderLayout.NORTH); - centerPanel.add(outputScrollPane, BorderLayout.CENTER); - centerPanel.add(loadingPanel, BorderLayout.SOUTH); - - // Add components - add(centerPanel, BorderLayout.CENTER); - add(inputPanel, BorderLayout.SOUTH); - - // add the chatPanel to the responsePanel, and notify update on the responsePanel with user message - this.chatPanel.addListener(message -> { - onEventHappen("CHAT_REQUEST", message); - }); - - // Initialize with empty chat container - updateOutputArea(); - } - - private String getCodeStyle() { - boolean isDarkTheme = !JBColor.isBright(); - String backgroundColor = isDarkTheme ? "#2b2d30" : "#fafafa"; - String textColor = isDarkTheme ? "#bababa" : "#2b2b2b"; - String codeBackground = isDarkTheme ? "#1e1f22" : "#f6f8fa"; - String codeBorder = isDarkTheme ? "#1e1f22" : "#e1e4e8"; - String linkColor = isDarkTheme ? "#589df6" : "#2470B3"; - String separatorColor = isDarkTheme ? "#3c3f41" : "#e0e0e0"; - - return String.format(""" - body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - font-size: 13pt; - margin: 10px; - line-height: 1.4; - background-color: %s; - color: %s; - } - #chat-container { - display: flex; - flex-direction: column; - gap: 0.5em; - } - .message { - border-radius: 8px; - padding: 12px; - } - .message.user { - background-color: %s; - margin-left: 20%%; - } - .message.assistant { - background-color: %s; - margin-right: 20%%; - } - .message.system { - background-color: %s; - text-align: center; - font-style: italic; - } - .message-header { - font-weight: bold; - margin-bottom: 4px; - color: %s; - } - .message-content { - white-space: pre-wrap; - } - .message-separator { - height: 1px; - background-color: %s; - margin: 10px 0; - } - pre { - background-color: %s; - padding: 16px; - border-radius: 6px; - margin: 1em 0; - border: 1px solid %s; - overflow: auto; - } - code { - font-family: "JetBrains Mono", "Fira Code", Consolas, Monaco, "Courier New", monospace; - font-size: 12pt; - color: %s; - } - a { color: %s; } - .keyword { color: %s; } - .string { color: %s; } - .comment { color: %s; font-style: italic; } - .number { color: %s; } - .annotation { color: %s; } - .type { color: %s; } - .method { color: %s; } - .field { color: %s; } - .constant { color: %s; } - .package { color: %s; } - """, - backgroundColor, textColor, - isDarkTheme ? "#2d2d2d" : "#e3f2fd", // user message background - isDarkTheme ? "#1e1e1e" : "#f5f5f5", // assistant message background - isDarkTheme ? "#2d2d2d" : "#e8f5e9", // system message background - textColor, - separatorColor, // separator color - codeBackground, codeBorder, textColor, linkColor, - isDarkTheme ? "#cc7832" : "#d73a49", // keyword - isDarkTheme ? "#6a8759" : "#032f62", // string - isDarkTheme ? "#808080" : "#6a737d", // comment - isDarkTheme ? "#6897bb" : "#005cc5", // number - isDarkTheme ? "#bbb529" : "#e36209", // annotation - isDarkTheme ? "#ffc66d" : "#6f42c1", // type - isDarkTheme ? "#ffc66d" : "#6f42c1", // method - isDarkTheme ? "#9876aa" : "#005cc5", // field - isDarkTheme ? "#9876aa" : "#005cc5", // constant - isDarkTheme ? "#a9b7c6" : "#22863a" // package - ); - } - - private void startLoading() { - isLoading = true; - loadingPanel.setVisible(true); - - // Start the loading animation - loadingTimer.scheduleAtFixedRate(new TimerTask() { - private int dots = 0; - @Override - public void run() { - if (!isLoading) { - cancel(); - return; - } - SwingUtilities.invokeLater(() -> { - dots = (dots + 1) % 4; - loadingLabel.setText(message("llm.thinking") + ".".repeat(dots)); - }); - } - }, 0, 500); - - // Disable input while loading - chatPanel.setInputEnabled(false); - } - - private void stopLoading() { - if (!isLoading) { - return; // Prevent multiple stop calls - } - isLoading = false; - loadingPanel.setVisible(false); - loadingTimer.purge(); - - // Re-enable input after loading - chatPanel.setInputEnabled(true); - } - - private void updateOutputArea() { - String fullHtml = String.format(BASE_HTML_TEMPLATE, getCodeStyle(), chatHistory.toString()); - SwingUtilities.invokeLater(() -> { - try { - // Create a new document each time to avoid state issues - HTMLDocument doc = (HTMLDocument) htmlKit.createDefaultDocument(); - // Set the document first - outputArea.setDocument(doc); - // Then insert the content - htmlKit.insertHTML(doc, 0, fullHtml, 0, 0, null); - outputArea.setCaretPosition(doc.getLength()); - } catch (Exception e) { - // Fallback to setText if something goes wrong - outputArea.setText(fullHtml); - } - }); - } - - private void appendMarkdownToOutput(String markdown) { - String htmlContent = convertMarkdownToHtml(markdown); - chatHistory.append(htmlContent); - updateOutputArea(); - } - - private String convertMarkdownToHtml(String markdown) { - Parser parser = Parser.builder().build(); - Node document = parser.parse(markdown); - HtmlRenderer renderer = HtmlRenderer.builder().build(); - return renderer.render(document); - } - - private void clearOutput() { - chatHistory.setLength(0); - updateOutputArea(); - } - - - - private void copyToClipboard(Object eventObj) { - String currentContentToCopy; - - if (copyAsMarkdown) { - currentContentToCopy = eventObj.toString(); - } else { - currentContentToCopy = outputArea.getText(); - } - StringSelection selection = new StringSelection(currentContentToCopy); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection); - } - - @Override - public void onEventHappen(String eventName, Object eventObj) { - switch (eventName) { - case "START_LOADING": - startLoading(); - break; - case "STOP_LOADING": - stopLoading(); - break; - case "COPY_CHAT_RESPONSE": - copyToClipboard(eventObj); - break; - case "CONFIG_CHANGE:copyAsMarkdown": - copyAsMarkdown = (boolean) eventObj; - break; - case "CHAT_REQUEST": - if (!isLoading) { - appendMarkdownToOutput(String.format(MESSAGE_TEMPLATE, "user", message("llm.user"), eventObj.toString())); - startLoading(); - } - break; - case "DRY_RUN_PROMPT": - String dryRunPrompt = (String) eventObj; - if (dryRunPrompt.isEmpty()){ - JOptionPane.showMessageDialog(this, message("llm.dry.run.prompt.empty"), message("llm.dry.run.prompt"), JOptionPane.INFORMATION_MESSAGE); - } else { - appendMarkdownToOutput(String.format(MESSAGE_TEMPLATE, "system", message("llm.system"), message("llm.dry.run.prompt") + "\n" + eventObj.toString())); - } - break; - default: - String[] responseType = eventName.split(":"); - if (responseType.length > 1 && responseType[0].equals("CHAT_RESPONSE")) { - switch (responseType[1]) { - case "CHAT_MESSAGE": - appendMarkdownToOutput(String.format(MESSAGE_TEMPLATE, "assistant", message("llm.assistant"), eventObj.toString())); - break; - case "ERROR": - JOptionPane.showMessageDialog(this, message("llm.error") + ": " + eventObj.toString(), message("llm.error"), JOptionPane.ERROR_MESSAGE); - break; - case "UNIT_TEST_REQUEST": - clearOutput(); - appendMarkdownToOutput(String.format(MESSAGE_TEMPLATE, "system", message("llm.system"), message("llm.new.unit.test.suggestion") + "\n" + eventObj.toString())); - break; - } - } - break; - } - } -} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/ILLMChatClient.java b/src/main/java/com/github/jaksonlin/pitestintellij/mediators/ILLMChatClient.java deleted file mode 100644 index 26c68ac7..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/ILLMChatClient.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.github.jaksonlin.pitestintellij.mediators; - - -public interface ILLMChatClient { - void updateChatResponse(String responseType,String chatResponse); -} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/IMutationReportUI.java b/src/main/java/com/github/jaksonlin/pitestintellij/mediators/IMutationReportUI.java deleted file mode 100644 index 444e5799..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/IMutationReportUI.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.jaksonlin.pitestintellij.mediators; - -import com.github.jaksonlin.pitestintellij.util.Pair; - -import java.util.Map; - -public interface IMutationReportUI { - void updateMutationResult(String mutationClassFilePath, Map> mutationTestResult); -} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/observers/BasicEventObserver.java b/src/main/java/com/github/jaksonlin/pitestintellij/observers/BasicEventObserver.java deleted file mode 100644 index be8a322e..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/observers/BasicEventObserver.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.github.jaksonlin.pitestintellij.observers; - -public interface BasicEventObserver { - void onEventHappen(String eventName, Object eventObj); -} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/observers/ObserverBase.java b/src/main/java/com/github/jaksonlin/pitestintellij/observers/ObserverBase.java deleted file mode 100644 index afe315b2..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/observers/ObserverBase.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.jaksonlin.pitestintellij.observers; - -import java.util.ArrayList; -import java.util.List; - -public class ObserverBase { - private final List observers = new ArrayList<>(); - - public void addObserver(BasicEventObserver observer) { - observers.add(observer); - } - - public void removeObserver(BasicEventObserver observer) { - observers.remove(observer); - } - - protected void notifyObservers(String eventName, Object eventObj) { - for (BasicEventObserver observer : observers) { - observer.onEventHappen(eventName, eventObj); - } - } -} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/processors/UnittestMethodProcessor.java b/src/main/java/com/github/jaksonlin/pitestintellij/processors/UnittestMethodProcessor.java deleted file mode 100644 index bb189719..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/processors/UnittestMethodProcessor.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.jaksonlin.pitestintellij.processors; - -import com.github.jaksonlin.pitestintellij.context.UnittestMethodContext; -import com.intellij.psi.PsiComment; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.util.PsiTreeUtil; - -import java.util.ArrayList; -import java.util.List; - -public class UnittestMethodProcessor { - public static UnittestMethodContext fromPsiMethod(PsiMethod psiMethod) { - List comments = extractCommentsFromMethodBody(psiMethod); - return new UnittestMethodContext(psiMethod.getName(), comments); - } - - private static List extractCommentsFromMethodBody(PsiMethod psiMethod) { - List comments = new ArrayList<>(); - for (PsiComment comment : PsiTreeUtil.findChildrenOfType(psiMethod, PsiComment.class)) { - comments.add(comment.getText()); - } - return comments; - } -} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/OllamaClient.java b/src/main/java/com/github/jaksonlin/pitestintellij/util/OllamaClient.java deleted file mode 100644 index 629a5177..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/OllamaClient.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.github.jaksonlin.pitestintellij.util; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.intellij.openapi.diagnostic.Logger; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class OllamaClient { - private static final Logger LOG = Logger.getInstance(OllamaClient.class); - private final String baseUrl; - private final ObjectMapper objectMapper; - private final HttpClient httpClient; - private final int timeoutSeconds; - private final String model; - private final int maxTokens; - private final float temperature; - - - public OllamaClient(String host, String model, int maxTokens, float temperature, int port, int timeoutSeconds) { - this.baseUrl = String.format("http://%s:%d", host, port); - this.objectMapper = new ObjectMapper(); - this.timeoutSeconds = timeoutSeconds; - this.model = model; - this.maxTokens = maxTokens; - this.temperature = temperature; - this.httpClient = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(timeoutSeconds)) - .build(); - } - - public boolean testConnection() { - try { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(baseUrl)) - .timeout(Duration.ofSeconds(timeoutSeconds)) - .GET() - .build(); - - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - return response.statusCode() == 200; - } catch (Exception e) { - LOG.warn("Failed to test connection to Ollama server", e); - return false; - } - } - - public String chatCompletion(List messages) throws IOException, InterruptedException { - Map requestBody = new HashMap<>(); - requestBody.put("model", model); - requestBody.put("messages", messages); - requestBody.put("stream", false); - requestBody.put("temperature", temperature); - requestBody.put("num_predict", maxTokens); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/chat")) - .header("Content-Type", "application/json") - .timeout(Duration.ofSeconds(timeoutSeconds)) - .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(requestBody))) - .build(); - - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() != 200) { - LOG.error("Error from Ollama API: " + response.body()); - throw new IOException("Failed to get response from Ollama API: " + response.statusCode()); - } - - Map responseMap = objectMapper.readValue(response.body(), Map.class); - return ((Map) responseMap.get("message")).get("content"); - } - - public static class Message { - private String role; - private String content; - - public Message(String role, String content) { - this.role = role; - this.content = content; - } - - public String getRole() { - return role; - } - - public String getContent() { - return content; - } - } -} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/viewmodels/LLMSuggestionUIComponentViewModel.java b/src/main/java/com/github/jaksonlin/pitestintellij/viewmodels/LLMSuggestionUIComponentViewModel.java deleted file mode 100644 index 11d04b00..00000000 --- a/src/main/java/com/github/jaksonlin/pitestintellij/viewmodels/LLMSuggestionUIComponentViewModel.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.github.jaksonlin.pitestintellij.viewmodels; - -import com.github.jaksonlin.pitestintellij.context.PitestContext; -import com.github.jaksonlin.pitestintellij.observers.BasicEventObserver; -import com.github.jaksonlin.pitestintellij.observers.ObserverBase; -import com.github.jaksonlin.pitestintellij.services.LLMService; - -public class LLMSuggestionUIComponentViewModel extends ObserverBase implements BasicEventObserver { - - private final LLMService llmService; - - - public LLMSuggestionUIComponentViewModel(LLMService llmService) { - this.llmService = llmService; - llmService.addObserver(this); // as hub to forward events - this.addObserver(llmService); - } - - public void propagateConfigChange() { - llmService.propagateConfigChange(); - } - - public void clearChat() { - notifyObservers("CLEAR_CHAT", null); - } - - public void copyChat() { - String chatHistory = llmService.getChatHistory(); - // when chatHistory is empty, use the lastDryRunPrompt - if (chatHistory.isEmpty()) { - chatHistory = lastDryRunPrompt; - } - notifyObservers("COPY_CHAT_RESPONSE", chatHistory); - } - - - - @Override - public void onEventHappen(String eventName, Object eventObj) { - // Handle events from LLMService/UIComponent if needed - switch (eventName) { - default: - // if have chat response, stop loading - if (eventName.contains("CHAT_RESPONSE:")) { - notifyObservers("STOP_LOADING", null); - } - notifyObservers(eventName, eventObj); - break; - } - } - - - public void generateSuggestions(PitestContext context) { - notifyObservers("START_LOADING", null); - llmService.generateUnittestRequest(context.getTestFilePath(), context.getTargetClassFilePath(), context.getMutationResults()); - } - - private String lastDryRunPrompt = ""; - - public void dryRunGetPrompt(PitestContext context) { - lastDryRunPrompt = llmService.dryRunGetPrompt(context.getFullyQualifiedTargetTestClassName(), context.getTargetClassFullyQualifiedName(), context.getMutationResults()); - notifyObservers("DRY_RUN_PROMPT", lastDryRunPrompt); - } - - public void handleChatMessage(String message) { - llmService.handleChatMessage(message); - } -} diff --git a/src/main/java/com/github/jaksonlin/testcraft/application/actions/CheckInvalidTestCasesAction.java b/src/main/java/com/github/jaksonlin/testcraft/application/actions/CheckInvalidTestCasesAction.java new file mode 100644 index 00000000..9d00bbcf --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/application/actions/CheckInvalidTestCasesAction.java @@ -0,0 +1,45 @@ +package com.github.jaksonlin.testcraft.application.actions; + +import com.github.jaksonlin.testcraft.infrastructure.commands.testscan.UnittestFileBatchScanCommand; +import com.github.jaksonlin.testcraft.infrastructure.services.config.InvalidTestCaseConfigService; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import org.jetbrains.annotations.NotNull; + +public class CheckInvalidTestCasesAction extends AnAction { + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + e.getPresentation().setText(I18nService.getInstance().message("action.CheckInvalidTestCasesAction.text")); + } + + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + Project project = e.getProject(); + if (project == null) return; + + InvalidTestCaseConfigService configService = ApplicationManager.getApplication().getService(InvalidTestCaseConfigService.class); + if (!configService.isEnable()) { + Messages.showInfoMessage( + project, + "Test case validation is disabled. Please enable it in Settings → TestCraft → Test Case Validation", + "Test Case Validation Disabled" + ); + return; + } + + UnittestFileBatchScanCommand batchScanCommand = new UnittestFileBatchScanCommand(project, e); + batchScanCommand.execute(); + } + + + + + +} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/actions/GenerateAnnotationCommandAction.java b/src/main/java/com/github/jaksonlin/testcraft/application/actions/GenerateAnnotationCommandAction.java similarity index 58% rename from src/main/java/com/github/jaksonlin/pitestintellij/actions/GenerateAnnotationCommandAction.java rename to src/main/java/com/github/jaksonlin/testcraft/application/actions/GenerateAnnotationCommandAction.java index d28d9b3f..80d849de 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/actions/GenerateAnnotationCommandAction.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/actions/GenerateAnnotationCommandAction.java @@ -1,9 +1,10 @@ -package com.github.jaksonlin.pitestintellij.actions; +package com.github.jaksonlin.testcraft.application.actions; -import com.github.jaksonlin.pitestintellij.commands.unittestannotations.GenerateAnnotationCommand; -import com.github.jaksonlin.pitestintellij.context.CaseCheckContext; -import com.github.jaksonlin.pitestintellij.util.Pair; -import com.github.jaksonlin.pitestintellij.util.PsiUtil; +import com.github.jaksonlin.testcraft.infrastructure.commands.unittestannotations.GenerateAnnotationCommand; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.util.Pair; +import com.github.jaksonlin.testcraft.util.PsiUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.Project; @@ -13,6 +14,12 @@ public class GenerateAnnotationCommandAction extends AnAction { + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + e.getPresentation().setText(I18nService.getInstance().message("action.GenerateAnnotationCommandAction.text")); + } + @Override public void actionPerformed(@NotNull AnActionEvent e) { Pair psiMethodInfo = PsiUtil.findMethodAtCaret(e); // Call the static method diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/actions/RunCaseAnnoationCheckAction.java b/src/main/java/com/github/jaksonlin/testcraft/application/actions/RunCaseAnnoationCheckAction.java similarity index 57% rename from src/main/java/com/github/jaksonlin/pitestintellij/actions/RunCaseAnnoationCheckAction.java rename to src/main/java/com/github/jaksonlin/testcraft/application/actions/RunCaseAnnoationCheckAction.java index 0d89f55b..33c678e9 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/actions/RunCaseAnnoationCheckAction.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/actions/RunCaseAnnoationCheckAction.java @@ -1,9 +1,10 @@ -package com.github.jaksonlin.pitestintellij.actions; +package com.github.jaksonlin.testcraft.application.actions; -import com.github.jaksonlin.pitestintellij.commands.unittestannotations.CheckAnnotationCommand; -import com.github.jaksonlin.pitestintellij.context.CaseCheckContext; -import com.github.jaksonlin.pitestintellij.util.Pair; -import com.github.jaksonlin.pitestintellij.util.PsiUtil; +import com.github.jaksonlin.testcraft.infrastructure.commands.unittestannotations.CheckAnnotationCommand; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.util.Pair; +import com.github.jaksonlin.testcraft.util.PsiUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.Project; @@ -28,4 +29,10 @@ public void actionPerformed(@NotNull AnActionEvent e) { } } + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + e.getPresentation().setText(I18nService.getInstance().message("action.RunCaseAnnoationCheckAction.text")); + } + } \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/services/PitestService.java b/src/main/java/com/github/jaksonlin/testcraft/application/actions/RunPitestAction.java similarity index 56% rename from src/main/java/com/github/jaksonlin/pitestintellij/services/PitestService.java rename to src/main/java/com/github/jaksonlin/testcraft/application/actions/RunPitestAction.java index 99d478c0..995906d2 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/services/PitestService.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/actions/RunPitestAction.java @@ -1,17 +1,22 @@ -package com.github.jaksonlin.pitestintellij.services; +package com.github.jaksonlin.testcraft.application.actions; -import com.github.jaksonlin.pitestintellij.commands.CommandCancellationException; -import com.github.jaksonlin.pitestintellij.commands.pitest.*; -import com.github.jaksonlin.pitestintellij.context.PitestContext; +import com.github.jaksonlin.testcraft.domain.context.PitestContext; +import com.github.jaksonlin.testcraft.infrastructure.commands.CommandCancellationException; +import com.github.jaksonlin.testcraft.infrastructure.commands.pitest.*; +import com.github.jaksonlin.testcraft.infrastructure.services.business.PitestService; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.Service; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.components.JBScrollPane; import org.jetbrains.annotations.NotNull; -import com.github.jaksonlin.pitestintellij.MyBundle; + import javax.swing.*; import java.awt.*; import java.io.PrintWriter; @@ -19,8 +24,37 @@ import java.util.Arrays; import java.util.List; -@Service(Service.Level.APP) -public final class PitestService { +public class RunPitestAction extends AnAction { + + private final PitestService pitestService; + + public RunPitestAction() { + pitestService = ApplicationManager.getApplication().getService(PitestService.class); + } + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + // You can change the text here + e.getPresentation().setText(I18nService.getInstance().message("action.RunPitestAction.text")); + + + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + Project targetProject = e.getProject(); + if (targetProject == null) { + return; + } + + VirtualFile testVirtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); + if (testVirtualFile == null) { + return; + } + + runPitest(targetProject, testVirtualFile.getPath()); + } public void runPitest(Project targetProject, String testFilePath) { PitestContext context = new PitestContext(testFilePath, System.currentTimeMillis()); @@ -40,7 +74,7 @@ public void run(@NotNull ProgressIndicator indicator) { for (PitestCommand command : commands) { if (indicator.isCanceled()) { ApplicationManager.getApplication().invokeLater(() -> - Messages.showInfoMessage(MyBundle.message("pitest.run.canceled"), MyBundle.message("pitest.run.canceled.title")) + Messages.showInfoMessage(I18nService.getInstance().message("pitest.run.canceled"), I18nService.getInstance().message("pitest.run.canceled.title")) ); break; } @@ -49,7 +83,7 @@ public void run(@NotNull ProgressIndicator indicator) { } catch (Exception e) { if (e.getCause() instanceof CommandCancellationException) { ApplicationManager.getApplication().invokeLater(() -> - Messages.showInfoMessage(MyBundle.message("pitest.run.canceled"), MyBundle.message("pitest.run.canceled.title")) + Messages.showInfoMessage(I18nService.getInstance().message("pitest.run.canceled"), I18nService.getInstance().message("pitest.run.canceled.title")) ); } else { showErrorDialog(e, context); @@ -64,7 +98,7 @@ protected void showErrorDialog(Exception e, PitestContext context) { e.printStackTrace(new PrintWriter(sw)); String stackTrace = sw.toString(); String contextInformation = PitestContext.dumpPitestContext(context); - String errorMessage = MyBundle.message("error.pitest.general.title") + ": " + e.getMessage() + "; " + contextInformation + "\n" + stackTrace; + String errorMessage = I18nService.getInstance().message("error.pitest.general.title") + ": " + e.getMessage() + "; " + contextInformation + "\n" + stackTrace; JTextArea textArea = new JTextArea(errorMessage); textArea.setEditable(false); @@ -78,4 +112,4 @@ protected void showErrorDialog(Exception e, PitestContext context) { Messages.showErrorDialog(scrollPane, errorMessage) ); } -} +} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/actions/RunTestFileAnnoationCheckAction.java b/src/main/java/com/github/jaksonlin/testcraft/application/actions/RunTestFileAnnoationCheckAction.java similarity index 89% rename from src/main/java/com/github/jaksonlin/pitestintellij/actions/RunTestFileAnnoationCheckAction.java rename to src/main/java/com/github/jaksonlin/testcraft/application/actions/RunTestFileAnnoationCheckAction.java index 46d00630..d7b3a726 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/actions/RunTestFileAnnoationCheckAction.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/actions/RunTestFileAnnoationCheckAction.java @@ -1,7 +1,7 @@ -package com.github.jaksonlin.pitestintellij.actions; +package com.github.jaksonlin.testcraft.application.actions; -import com.github.jaksonlin.pitestintellij.commands.unittestannotations.CheckAnnotationCommand; -import com.github.jaksonlin.pitestintellij.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.infrastructure.commands.unittestannotations.CheckAnnotationCommand; +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/completions/AnnotationCompletionContributor.java b/src/main/java/com/github/jaksonlin/testcraft/application/completions/AnnotationCompletionContributor.java similarity index 92% rename from src/main/java/com/github/jaksonlin/pitestintellij/completions/AnnotationCompletionContributor.java rename to src/main/java/com/github/jaksonlin/testcraft/application/completions/AnnotationCompletionContributor.java index 035db1ab..7680a2e7 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/completions/AnnotationCompletionContributor.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/completions/AnnotationCompletionContributor.java @@ -1,9 +1,9 @@ -package com.github.jaksonlin.pitestintellij.completions; +package com.github.jaksonlin.testcraft.application.completions; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationFieldConfig; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationSchema; -import com.github.jaksonlin.pitestintellij.annotations.DefaultValue; -import com.github.jaksonlin.pitestintellij.services.AnnotationConfigService; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationFieldConfig; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationSchema; +import com.github.jaksonlin.testcraft.domain.annotations.DefaultValue; +import com.github.jaksonlin.testcraft.infrastructure.services.config.AnnotationConfigService; import com.intellij.codeInsight.completion.*; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.lang.java.JavaLanguage; @@ -27,7 +27,7 @@ public AnnotationCompletionContributor() { PlatformPatterns.psiElement() .inside(PsiAnnotation.class) .withLanguage(JavaLanguage.INSTANCE), - new CompletionProvider<>() { + new CompletionProvider() { @Override protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) { PsiAnnotation annotation = PsiTreeUtil.getParentOfType(parameters.getPosition(), PsiAnnotation.class); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/completions/CustomAnnotationCompletionLookupElement.java b/src/main/java/com/github/jaksonlin/testcraft/application/completions/CustomAnnotationCompletionLookupElement.java similarity index 93% rename from src/main/java/com/github/jaksonlin/pitestintellij/completions/CustomAnnotationCompletionLookupElement.java rename to src/main/java/com/github/jaksonlin/testcraft/application/completions/CustomAnnotationCompletionLookupElement.java index 0c98bb8e..21cc180e 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/completions/CustomAnnotationCompletionLookupElement.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/completions/CustomAnnotationCompletionLookupElement.java @@ -1,6 +1,6 @@ -package com.github.jaksonlin.pitestintellij.completions; +package com.github.jaksonlin.testcraft.application.completions; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationFieldType; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationFieldType; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementPresentation; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/inspectors/UnittestInspector.java b/src/main/java/com/github/jaksonlin/testcraft/application/inspectors/UnittestInspector.java similarity index 84% rename from src/main/java/com/github/jaksonlin/pitestintellij/inspectors/UnittestInspector.java rename to src/main/java/com/github/jaksonlin/testcraft/application/inspectors/UnittestInspector.java index 485901cf..969e6789 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/inspectors/UnittestInspector.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/inspectors/UnittestInspector.java @@ -1,8 +1,8 @@ -package com.github.jaksonlin.pitestintellij.inspectors; +package com.github.jaksonlin.testcraft.application.inspectors; -import com.github.jaksonlin.pitestintellij.MyBundle; -import com.github.jaksonlin.pitestintellij.commands.unittestannotations.UnittestFileInspectorCommand; -import com.github.jaksonlin.pitestintellij.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.infrastructure.commands.testscan.UnittestFileInspectorCommand; +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.project.Project; @@ -18,12 +18,12 @@ public class UnittestInspector extends AbstractBaseJavaLocalInspectionTool { @Override public @NotNull String getGroupDisplayName() { - return MyBundle.message("inspection.group.name"); + return I18nService.getInstance().message("inspection.group.name"); } @Override public @NotNull String getDisplayName() { - return MyBundle.message("inspection.display.name"); + return I18nService.getInstance().message("inspection.display.name"); } @Override diff --git a/src/main/java/com/github/jaksonlin/testcraft/application/processors/LLMProcessor.java b/src/main/java/com/github/jaksonlin/testcraft/application/processors/LLMProcessor.java new file mode 100644 index 00000000..9a700f8c --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/application/processors/LLMProcessor.java @@ -0,0 +1,21 @@ +package com.github.jaksonlin.testcraft.application.processors; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class LLMProcessor { + + private String readFileContent(String filePath) { + try { + return new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Failed to read file: " + filePath, e); + } + } + + private String getSourceCode(String sourceCodeFile) { + return readFileContent(sourceCodeFile); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/settings/AnnotationSettingsConfigurable.java b/src/main/java/com/github/jaksonlin/testcraft/application/settings/AnnotationSettingsConfigurable.java similarity index 90% rename from src/main/java/com/github/jaksonlin/pitestintellij/settings/AnnotationSettingsConfigurable.java rename to src/main/java/com/github/jaksonlin/testcraft/application/settings/AnnotationSettingsConfigurable.java index 52bec431..db88648d 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/settings/AnnotationSettingsConfigurable.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/settings/AnnotationSettingsConfigurable.java @@ -1,9 +1,10 @@ -package com.github.jaksonlin.pitestintellij.settings; +package com.github.jaksonlin.testcraft.application.settings; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationSchema; -import com.github.jaksonlin.pitestintellij.services.AnnotationConfigService; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationSchema; +import com.github.jaksonlin.testcraft.infrastructure.services.config.AnnotationConfigService; +import com.github.jaksonlin.testcraft.presentation.components.configuration.AnnotationSettingsComponent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/settings/InvalidTestCaseSettingsConfigurable.java b/src/main/java/com/github/jaksonlin/testcraft/application/settings/InvalidTestCaseSettingsConfigurable.java similarity index 89% rename from src/main/java/com/github/jaksonlin/pitestintellij/settings/InvalidTestCaseSettingsConfigurable.java rename to src/main/java/com/github/jaksonlin/testcraft/application/settings/InvalidTestCaseSettingsConfigurable.java index cf719b23..d24876c5 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/settings/InvalidTestCaseSettingsConfigurable.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/settings/InvalidTestCaseSettingsConfigurable.java @@ -1,6 +1,7 @@ -package com.github.jaksonlin.pitestintellij.settings; +package com.github.jaksonlin.testcraft.application.settings; -import com.github.jaksonlin.pitestintellij.services.InvalidTestCaseConfigService; +import com.github.jaksonlin.testcraft.infrastructure.services.config.InvalidTestCaseConfigService; +import com.github.jaksonlin.testcraft.presentation.components.configuration.InvalidTestCaseSettingsComponent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.options.Configurable; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/settings/OllamaSettingsConfigurable.java b/src/main/java/com/github/jaksonlin/testcraft/application/settings/OllamaSettingsConfigurable.java similarity index 79% rename from src/main/java/com/github/jaksonlin/pitestintellij/settings/OllamaSettingsConfigurable.java rename to src/main/java/com/github/jaksonlin/testcraft/application/settings/OllamaSettingsConfigurable.java index d8bef671..5e6562b6 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/settings/OllamaSettingsConfigurable.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/settings/OllamaSettingsConfigurable.java @@ -1,7 +1,8 @@ -package com.github.jaksonlin.pitestintellij.settings; +package com.github.jaksonlin.testcraft.application.settings; -import com.github.jaksonlin.pitestintellij.services.LLMService; -import com.github.jaksonlin.pitestintellij.MyBundle; +import com.github.jaksonlin.testcraft.infrastructure.services.config.LLMConfigService; + +import com.github.jaksonlin.testcraft.presentation.components.configuration.OllamaSettingsComponent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.options.Configurable; import org.jetbrains.annotations.Nls; @@ -9,14 +10,16 @@ import javax.swing.*; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; + public class OllamaSettingsConfigurable implements Configurable { private OllamaSettingsComponent settingsComponent = new OllamaSettingsComponent(); - private final LLMService llmService = ApplicationManager.getApplication().getService(LLMService.class); + private final LLMConfigService llmConfigService = ApplicationManager.getApplication().getService(LLMConfigService.class); @Nls(capitalization = Nls.Capitalization.Title) @Override public String getDisplayName() { - return MyBundle.message("settings.testcraft.llm"); + return I18nService.getInstance().message("settings.testcraft.llm"); } @Override @@ -36,7 +39,7 @@ public boolean isModified() { if (settingsComponent == null) { return false; } - LLMService.State state = llmService.getState(); + LLMConfigService.State state = llmConfigService.getState(); boolean modified = !settingsComponent.getHostText().equals(state.ollamaHost); modified |= !settingsComponent.getPortText().equals(String.valueOf(state.ollamaPort)); modified |= !settingsComponent.getModelText().equals(state.ollamaModel); @@ -50,7 +53,7 @@ public boolean isModified() { @Override public void apply() { try { - LLMService.State state = llmService.getState(); + LLMConfigService.State state = llmConfigService.getState(); state.ollamaHost = settingsComponent.getHostText(); state.ollamaPort = Integer.parseInt(settingsComponent.getPortText()); state.maxTokens = Integer.parseInt(settingsComponent.getMaxTokensText()); @@ -59,7 +62,7 @@ public void apply() { state.ollamaModel = settingsComponent.getModelText(); state.copyAsMarkdown = settingsComponent.getCopyAsMarkdown(); - llmService.loadState(state); + llmConfigService.loadState(state); } catch (NumberFormatException e) { // Handle invalid number format throw new IllegalStateException("Invalid number format in settings", e); @@ -69,7 +72,7 @@ public void apply() { @Override public void reset() { // Reset the settings component with the current state of the LLMService - LLMService.State state = llmService.getState(); + LLMConfigService.State state = llmConfigService.getState(); settingsComponent.setHostText(state.ollamaHost); settingsComponent.setPortText(String.valueOf(state.ollamaPort)); settingsComponent.setModelText(state.ollamaModel); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/settings/TestCraftSettingsConfigurable.java b/src/main/java/com/github/jaksonlin/testcraft/application/settings/TestCraftSettingsConfigurable.java similarity index 53% rename from src/main/java/com/github/jaksonlin/pitestintellij/settings/TestCraftSettingsConfigurable.java rename to src/main/java/com/github/jaksonlin/testcraft/application/settings/TestCraftSettingsConfigurable.java index 54666a3b..c9a5534a 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/settings/TestCraftSettingsConfigurable.java +++ b/src/main/java/com/github/jaksonlin/testcraft/application/settings/TestCraftSettingsConfigurable.java @@ -1,23 +1,24 @@ -package com.github.jaksonlin.pitestintellij.settings; +package com.github.jaksonlin.testcraft.application.settings; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.SearchableConfigurable; -import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import com.github.jaksonlin.pitestintellij.MyBundle; + import javax.swing.*; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; + public class TestCraftSettingsConfigurable implements SearchableConfigurable.Parent { @Override public @NotNull String getId() { - return "com.github.jaksonlin.pitestintellij.settings"; + return "com.github.jaksonlin.testcraft.application.settings"; } @Override public String getDisplayName() { - return MyBundle.message("settings.testcraft.title"); + return I18nService.getInstance().message("settings.testcraft.title"); } @Override @@ -25,12 +26,12 @@ public String getDisplayName() { // Create a panel with a brief description of TestCraft settings JPanel panel = new JPanel(); panel.add(new JLabel("" + - "

" + MyBundle.message("settings.testcraft.title") + "

" + - "

" + MyBundle.message("settings.testcraft.description") + "

" + + "

" + I18nService.getInstance().message("settings.testcraft.title") + "

" + + "

" + I18nService.getInstance().message("settings.testcraft.description") + "

" + "
    " + - "
  • " + MyBundle.message("settings.testcraft.annotations") + " - " + MyBundle.message("settings.testcraft.annotations.description") + "
  • " + - "
  • " + MyBundle.message("settings.testcraft.asserts") + " - " + MyBundle.message("settings.testcraft.asserts.description") + "
  • " + - "
  • " + MyBundle.message("settings.testcraft.llm") + " - " + MyBundle.message("settings.testcraft.llm.description") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.testcraft.annotations") + " - " + I18nService.getInstance().message("settings.testcraft.annotations.description") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.testcraft.asserts") + " - " + I18nService.getInstance().message("settings.testcraft.asserts.description") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.testcraft.llm") + " - " + I18nService.getInstance().message("settings.testcraft.llm.description") + "
  • " + "
" + "")); return panel; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationFieldConfig.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationFieldConfig.java similarity index 97% rename from src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationFieldConfig.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationFieldConfig.java index 64bd8d9a..d2646199 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationFieldConfig.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationFieldConfig.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.annotations; +package com.github.jaksonlin.testcraft.domain.annotations; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationFieldType.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationFieldType.java similarity index 52% rename from src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationFieldType.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationFieldType.java index 324e560a..159b78c9 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationFieldType.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationFieldType.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.annotations; +package com.github.jaksonlin.testcraft.domain.annotations; public enum AnnotationFieldType { STRING, diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationParser.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationParser.java similarity index 96% rename from src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationParser.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationParser.java index 274fce33..d019f253 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationParser.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationParser.java @@ -1,6 +1,6 @@ -package com.github.jaksonlin.pitestintellij.annotations; +package com.github.jaksonlin.testcraft.domain.annotations; -import com.github.jaksonlin.pitestintellij.context.UnittestCase; +import com.github.jaksonlin.testcraft.domain.context.UnittestCase; import org.jetbrains.annotations.Nullable; import java.util.HashMap; diff --git a/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationSchema.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationSchema.java new file mode 100644 index 00000000..5a2167bf --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationSchema.java @@ -0,0 +1,288 @@ +package com.github.jaksonlin.testcraft.domain.annotations; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.List; + +public class AnnotationSchema { + private String annotationClassName; + private List fields; + public static final String DEFAULT_SCHEMA = "{\n" + + " \"annotationClassName\": \"UnittestCaseInfo\",\n" + + " \"fields\": [\n" + + " {\n" + + " \"name\": \"author\",\n" + + " \"type\": \"STRING\",\n" + + " \"required\": true,\n" + + " \"valueProvider\": {\n" + + " \"type\": \"FIRST_CREATOR_AUTHOR\"\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": false\n" + + " },\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringValue\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"title\",\n" + + " \"type\": \"STRING\",\n" + + " \"required\": true,\n" + + " \"valueProvider\": {\n" + + " \"type\": \"METHOD_NAME_BASED\"\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": false\n" + + " },\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringValue\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"targetClass\",\n" + + " \"type\": \"STRING\",\n" + + " \"required\": true,\n" + + " \"valueProvider\": {\n" + + " \"type\": \"CLASS_NAME\"\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": false\n" + + " },\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringValue\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"targetMethod\",\n" + + " \"type\": \"STRING\",\n" + + " \"required\": true,\n" + + " \"valueProvider\": {\n" + + " \"type\": \"METHOD_NAME\"\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": false\n" + + " },\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringValue\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"lastUpdateTime\",\n" + + " \"type\": \"STRING\",\n" + + " \"required\": true,\n" + + " \"valueProvider\": {\n" + + " \"type\": \"LAST_MODIFIER_TIME\",\n" + + " \"format\": \"yyyy-MM-dd HH:mm:ss\"\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": false\n" + + " },\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringValue\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"lastUpdateAuthor\",\n" + + " \"type\": \"STRING\",\n" + + " \"required\": true,\n" + + " \"valueProvider\": {\n" + + " \"type\": \"LAST_MODIFIER_AUTHOR\"\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": false\n" + + " },\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringValue\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"methodSignature\",\n" + + " \"type\": \"STRING\",\n" + + " \"required\": true,\n" + + " \"valueProvider\": {\n" + + " \"type\": \"METHOD_SIGNATURE\"\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": false\n" + + " },\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringValue\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"testPoints\",\n" + + " \"type\": \"STRING_LIST\",\n" + + " \"required\": false,\n" + + " \"valueProvider\": {\n" + + " \"type\": \"FIXED_VALUE\",\n" + + " \"value\": [\"Functionality\"]\n" + + " },\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringListValue\",\n" + + " \"value\": []\n" + + " },\n" + + " \"validation\": {\n" + + " \"validValues\": [\n" + + " \"BoundaryValue\",\n" + + " \"NonEmpty\",\n" + + " \"ErrorHandling\",\n" + + " \"InputValidation\",\n" + + " \"PositiveScenario\",\n" + + " \"NegativeScenario\",\n" + + " \"EdgeCase\",\n" + + " \"Functionality\",\n" + + " \"BusinessLogicValidation\",\n" + + " \"BusinessInputOutput\",\n" + + " \"SideEffects\",\n" + + " \"StateTransition\",\n" + + " \"BusinessCalculation\",\n" + + " \"Security\",\n" + + " \"Performance\"\n" + + " ],\n" + + " \"allowCustomValues\": true,\n" + + " \"mode\": \"CONTAINS\",\n" + + " \"allowEmpty\": true\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"status\",\n" + + " \"type\": \"STRING\",\n" + + " \"required\": false,\n" + + " \"valueProvider\": {\n" + + " \"type\": \"FIXED_VALUE\",\n" + + " \"value\": \"TODO\"\n" + + " },\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringValue\",\n" + + " \"value\": \"TODO\"\n" + + " },\n" + + " \"validation\": {\n" + + " \"validValues\": [\n" + + " \"TODO\",\n" + + " \"IN_PROGRESS\",\n" + + " \"DONE\",\n" + + " \"BLOCKED\"\n" + + " ],\n" + + " \"allowCustomValues\": false,\n" + + " \"mode\": \"EXACT\",\n" + + " \"allowEmpty\": false\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"description\",\n" + + " \"type\": \"STRING\",\n" + + " \"required\": false,\n" + + " \"valueProvider\": {\n" + + " \"type\": \"METHOD_NAME_BASED\"\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": true\n" + + " },\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringValue\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"tags\",\n" + + " \"type\": \"STRING_LIST\",\n" + + " \"required\": false,\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringListValue\",\n" + + " \"value\": []\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": true\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"relatedRequirements\",\n" + + " \"type\": \"STRING_LIST\",\n" + + " \"required\": false,\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringListValue\",\n" + + " \"value\": []\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": true\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"relatedTestcases\",\n" + + " \"type\": \"STRING_LIST\",\n" + + " \"required\": false,\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringListValue\",\n" + + " \"value\": []\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": true\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"relatedDefects\",\n" + + " \"type\": \"STRING_LIST\",\n" + + " \"required\": false,\n" + + " \"defaultValue\": {\n" + + " \"type\": \"StringListValue\",\n" + + " \"value\": []\n" + + " },\n" + + " \"validation\": {\n" + + " \"allowEmpty\": true\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"; + + public AnnotationSchema() { + } + + public AnnotationSchema(String annotationClassName, List fields) { + this.annotationClassName = annotationClassName; + this.fields = fields; + } + + public String getAnnotationClassName() { + return annotationClassName; + } + + public void setAnnotationClassName(String annotationClassName) { + this.annotationClassName = annotationClassName; + } + + public List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + + public static class Companion { + private static final ObjectMapper json; + + static { + json = new ObjectMapper(); + } + + @Nullable + public static AnnotationSchema fromJson(@NotNull String jsonString) { + try { + return json.readValue(jsonString, AnnotationSchema.class); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationValidator.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationValidator.java similarity index 99% rename from src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationValidator.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationValidator.java index aa48375d..bf31da30 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/AnnotationValidator.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/AnnotationValidator.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.annotations; +package com.github.jaksonlin.testcraft.domain.annotations; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/DefaultValue.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/DefaultValue.java similarity index 96% rename from src/main/java/com/github/jaksonlin/pitestintellij/annotations/DefaultValue.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/annotations/DefaultValue.java index 5670e170..e5de1c7f 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/DefaultValue.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/DefaultValue.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.annotations; +package com.github.jaksonlin.testcraft.domain.annotations; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/FieldValidation.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/FieldValidation.java similarity index 96% rename from src/main/java/com/github/jaksonlin/pitestintellij/annotations/FieldValidation.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/annotations/FieldValidation.java index 17f534ea..9b9ffee7 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/FieldValidation.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/FieldValidation.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.annotations; +package com.github.jaksonlin.testcraft.domain.annotations; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/UnittestAnnotationConfig.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/UnittestAnnotationConfig.java similarity index 97% rename from src/main/java/com/github/jaksonlin/pitestintellij/annotations/UnittestAnnotationConfig.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/annotations/UnittestAnnotationConfig.java index 51f4e2dd..1fb07e5b 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/UnittestAnnotationConfig.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/UnittestAnnotationConfig.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.annotations; +package com.github.jaksonlin.testcraft.domain.annotations; public class UnittestAnnotationConfig { private String authorField; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/ValidationMode.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/ValidationMode.java similarity index 64% rename from src/main/java/com/github/jaksonlin/pitestintellij/annotations/ValidationMode.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/annotations/ValidationMode.java index 93ca3ea7..206ac1da 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/ValidationMode.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/ValidationMode.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.annotations; +package com.github.jaksonlin.testcraft.domain.annotations; public enum ValidationMode { EXACT, // Exact string match diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/ValueProvider.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/ValueProvider.java similarity index 93% rename from src/main/java/com/github/jaksonlin/pitestintellij/annotations/ValueProvider.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/annotations/ValueProvider.java index beb58c47..2e6788a1 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/ValueProvider.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/ValueProvider.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.annotations; +package com.github.jaksonlin.testcraft.domain.annotations; import com.fasterxml.jackson.databind.JsonNode; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/ValueProviderType.java b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/ValueProviderType.java similarity index 81% rename from src/main/java/com/github/jaksonlin/pitestintellij/annotations/ValueProviderType.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/annotations/ValueProviderType.java index be79eccd..8c2e510c 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/annotations/ValueProviderType.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/annotations/ValueProviderType.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.annotations; +package com.github.jaksonlin.testcraft.domain.annotations; public enum ValueProviderType { GIT_AUTHOR, diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/context/CaseCheckContext.java b/src/main/java/com/github/jaksonlin/testcraft/domain/context/CaseCheckContext.java similarity index 82% rename from src/main/java/com/github/jaksonlin/pitestintellij/context/CaseCheckContext.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/context/CaseCheckContext.java index 27b22c3e..96f45778 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/context/CaseCheckContext.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/context/CaseCheckContext.java @@ -1,9 +1,9 @@ -package com.github.jaksonlin.pitestintellij.context; +package com.github.jaksonlin.testcraft.domain.context; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationParser; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationSchema; -import com.github.jaksonlin.pitestintellij.services.AnnotationConfigService; -import com.github.jaksonlin.pitestintellij.services.InvalidTestCaseConfigService; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationParser; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationSchema; +import com.github.jaksonlin.testcraft.infrastructure.services.config.AnnotationConfigService; +import com.github.jaksonlin.testcraft.infrastructure.services.config.InvalidTestCaseConfigService; import com.intellij.openapi.application.ApplicationManager; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiMethod; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/context/PitestContext.java b/src/main/java/com/github/jaksonlin/testcraft/domain/context/PitestContext.java similarity index 79% rename from src/main/java/com/github/jaksonlin/pitestintellij/context/PitestContext.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/context/PitestContext.java index e3970da3..aede9480 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/context/PitestContext.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/context/PitestContext.java @@ -1,8 +1,8 @@ -package com.github.jaksonlin.pitestintellij.context; +package com.github.jaksonlin.testcraft.domain.context; -import com.github.jaksonlin.pitestintellij.util.Mutation; -import com.github.jaksonlin.pitestintellij.util.MutationReportParser; -import com.github.jaksonlin.pitestintellij.util.ProcessResult; +import com.github.jaksonlin.testcraft.util.Mutation; +import com.github.jaksonlin.testcraft.util.MutationReportParser; +import com.github.jaksonlin.testcraft.util.ProcessResult; import java.io.IOException; import java.nio.file.Paths; @@ -27,6 +27,15 @@ public class PitestContext { private List resourceDirectories; private final long timestamp; private List mutationResults; + private String workingDirectory; + + public String getWorkingDirectory() { + return workingDirectory; + } + + public void setWorkingDirectory(String workingDirectory) { + this.workingDirectory = workingDirectory; + } // this will also init the fielid mutationResults public List collectMutationsResults() throws Exception { @@ -190,30 +199,17 @@ public long getTimestamp() { } public static String dumpPitestContext(PitestContext context) { - return String.format(""" - testVirtualFile: %s - fullyQualifiedTargetTestClassName: %s - javaHome: %s - sourceRoots: %s - fullyQualifiedTargetClassName: %s - targetClassSourceRoot: %s - reportDirectory: %s - classpathFile: %s - command: %s - processResult: %s - pitestDependencies: %s - """, - context.getTestFilePath(), - context.getFullyQualifiedTargetTestClassName(), - context.getJavaHome(), - context.getSourceRoots(), - context.getTargetClassFullyQualifiedName(), - context.getTargetClassSourceRoot(), - context.getReportDirectory(), - context.getClasspathFile(), - context.getCommand(), - context.getProcessResult(), - context.getPitestDependencies() - ); + return "testVirtualFile: " + context.getTestFilePath() + "\n" + + "fullyQualifiedTargetTestClassName: " + context.getFullyQualifiedTargetTestClassName() + "\n" + + "javaHome: " + context.getJavaHome() + "\n" + + "sourceRoots: " + context.getSourceRoots() + "\n" + + "fullyQualifiedTargetClassName: " + context.getTargetClassFullyQualifiedName() + "\n" + + "targetClassSourceRoot: " + context.getTargetClassSourceRoot() + "\n" + + "reportDirectory: " + context.getReportDirectory() + "\n" + + "classpathFile: " + context.getClasspathFile() + "\n" + + "command: " + context.getCommand() + "\n" + + "processResult: " + context.getProcessResult() + "\n" + + "pitestDependencies: " + context.getPitestDependencies() + "\n" + + "workingDirectory: " + context.getWorkingDirectory(); } } \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/context/TestPoints.java b/src/main/java/com/github/jaksonlin/testcraft/domain/context/TestPoints.java similarity index 96% rename from src/main/java/com/github/jaksonlin/pitestintellij/context/TestPoints.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/context/TestPoints.java index e996e948..c909bf44 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/context/TestPoints.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/context/TestPoints.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.context; +package com.github.jaksonlin.testcraft.domain.context; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestCase.java b/src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestCase.java similarity index 86% rename from src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestCase.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestCase.java index 3bcc5897..40a87459 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestCase.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestCase.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.context; +package com.github.jaksonlin.testcraft.domain.context; import java.util.Collections; import java.util.List; @@ -18,7 +18,8 @@ public String getString(String key) { public List getStringList(String key) { Object value = values.get(key); - if (value instanceof List list) { + if (value instanceof List) { + List list = (List) value; if (list.isEmpty() || list.get(0) instanceof String) { return (List) value; } diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestCaseInfoContext.java b/src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestCaseInfoContext.java similarity index 96% rename from src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestCaseInfoContext.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestCaseInfoContext.java index b3485ce5..430ea77b 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestCaseInfoContext.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestCaseInfoContext.java @@ -1,6 +1,6 @@ -package com.github.jaksonlin.pitestintellij.context; +package com.github.jaksonlin.testcraft.domain.context; -import com.github.jaksonlin.pitestintellij.annotations.UnittestAnnotationConfig; +import com.github.jaksonlin.testcraft.domain.annotations.UnittestAnnotationConfig; import java.util.Collections; import java.util.List; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestCaseStatus.java b/src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestCaseStatus.java similarity index 56% rename from src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestCaseStatus.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestCaseStatus.java index 4ab616f7..98411cbb 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestCaseStatus.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestCaseStatus.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.context; +package com.github.jaksonlin.testcraft.domain.context; public enum UnittestCaseStatus { TODO, DONE, BROKEN, DEPRECATED diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestMethodContext.java b/src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestMethodContext.java similarity index 88% rename from src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestMethodContext.java rename to src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestMethodContext.java index dc0964e7..20b30106 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/context/UnittestMethodContext.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/context/UnittestMethodContext.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.context; +package com.github.jaksonlin.testcraft.domain.context; import java.util.List; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/CommandCancellationException.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/CommandCancellationException.java similarity index 70% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/CommandCancellationException.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/CommandCancellationException.java index cefbbcc1..5af4d962 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/CommandCancellationException.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/CommandCancellationException.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.commands; +package com.github.jaksonlin.testcraft.infrastructure.commands; public class CommandCancellationException extends Exception{ diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/BuildPitestCommandCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/BuildPitestCommandCommand.java similarity index 95% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/BuildPitestCommandCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/BuildPitestCommandCommand.java index b4cfeeae..0378ca04 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/BuildPitestCommandCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/BuildPitestCommandCommand.java @@ -1,6 +1,6 @@ -package com.github.jaksonlin.pitestintellij.commands.pitest; +package com.github.jaksonlin.testcraft.infrastructure.commands.pitest; -import com.github.jaksonlin.pitestintellij.context.PitestContext; +import com.github.jaksonlin.testcraft.domain.context.PitestContext; import com.intellij.openapi.project.Project; import java.util.ArrayList; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/HandlePitestResultCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/HandlePitestResultCommand.java similarity index 52% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/HandlePitestResultCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/HandlePitestResultCommand.java index b2691e06..3f06dd49 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/HandlePitestResultCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/HandlePitestResultCommand.java @@ -1,10 +1,11 @@ -package com.github.jaksonlin.pitestintellij.commands.pitest; +package com.github.jaksonlin.testcraft.infrastructure.commands.pitest; -import com.github.jaksonlin.pitestintellij.context.PitestContext; -import com.github.jaksonlin.pitestintellij.components.PitestOutputDialog; -import com.github.jaksonlin.pitestintellij.util.ProcessResult; +import com.github.jaksonlin.testcraft.domain.context.PitestContext; +import com.github.jaksonlin.testcraft.presentation.components.mutation.PitestOutputDialog; +import com.github.jaksonlin.testcraft.util.ProcessResult; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; import java.io.File; @@ -26,15 +27,14 @@ public void execute() { String reportDirectory = getContext().getReportDirectory(); if (reportDirectory != null) { File reportFile = new File(reportDirectory, "index.html"); + StringBuilder combinedOutputSB = getCombinedOutputFromResult(result); if (reportFile.exists()) { - showOutputWithReportButton(result.getOutput(), "Pitest Output", reportFile); + showOutputWithReportButton(combinedOutputSB.toString(), "Pitest Output", reportFile); } else { - showOutput(result.getOutput(), "Pitest Output"); + showOutput(combinedOutputSB.toString(), "Pitest Result"); showError("Report file not found: " + reportFile.getAbsolutePath()); } } - } else if (exitCode == -1) { - showOutput("Error running Pitest:\n\n" + result.getErrorOutput(), "Pitest Error"); } else { StringBuilder errorMessage = new StringBuilder() .append("Pitest exited with code ").append(exitCode).append("\n\n") @@ -42,16 +42,33 @@ public void execute() { .append("Common causes for exit code 2 include:\n") .append("- System.exit() called in test code\n") .append("- Test process configuration issues\n") - .append("- Memory/resource constraints\n\n") - .append("=== Error Output ===\n") - .append(result.getErrorOutput()).append("\n\n") - .append("=== Context Information ===\n") - .append(PitestContext.dumpPitestContext(getContext())); + .append("- Memory/resource constraints\n\n"); + if (result.getOutput() != null) { + errorMessage.append("=== Standard Output ===\n"); + errorMessage.append(result.getOutput()); + } + if (result.getErrorOutput() != null) { + errorMessage.append("\n=== Verbose Output ===\n"); + errorMessage.append(result.getErrorOutput()); + } showOutput(errorMessage.toString(), "Pitest Error"); } } + private static @NotNull StringBuilder getCombinedOutputFromResult(ProcessResult result) { + StringBuilder combinedOutputSB = new StringBuilder(); + if (result.getOutput() != null) { + combinedOutputSB.append("=== Standard Output ===\n"); + combinedOutputSB.append(result.getOutput()); + } + if (result.getErrorOutput() != null) { + combinedOutputSB.append("\n=== Verbose Output ===\n"); + combinedOutputSB.append(result.getErrorOutput()); + } + return combinedOutputSB; + } + private void showOutputWithReportButton(String output, String title, File reportFile) { ApplicationManager.getApplication().invokeLater(() -> { new PitestOutputDialog(getProject(), output, title, reportFile).show(); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/PitestCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PitestCommand.java similarity index 77% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/PitestCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PitestCommand.java index 07d499a6..932bc1f3 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/PitestCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PitestCommand.java @@ -1,19 +1,21 @@ -package com.github.jaksonlin.pitestintellij.commands.pitest; +package com.github.jaksonlin.testcraft.infrastructure.commands.pitest; -import com.github.jaksonlin.pitestintellij.commands.CommandCancellationException; -import com.github.jaksonlin.pitestintellij.context.PitestContext; -import com.github.jaksonlin.pitestintellij.services.RunHistoryManagerService; -import com.github.jaksonlin.pitestintellij.components.PitestOutputDialog; +import com.github.jaksonlin.testcraft.infrastructure.commands.CommandCancellationException; +import com.github.jaksonlin.testcraft.domain.context.PitestContext; +import com.github.jaksonlin.testcraft.infrastructure.services.business.RunHistoryManagerService; +import com.github.jaksonlin.testcraft.presentation.components.mutation.PitestOutputDialog; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; -import com.github.jaksonlin.pitestintellij.MyBundle; + import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; + public abstract class PitestCommand { private final Project project; private final PitestContext context; @@ -22,7 +24,7 @@ public abstract class PitestCommand { public PitestCommand(Project project, PitestContext context) { this.project = project; this.context = context; - this.runHistoryManager = project.getService(RunHistoryManagerService.class); + this.runHistoryManager = RunHistoryManagerService.getInstance(); } public abstract void execute(); @@ -65,7 +67,7 @@ protected void showError(String message) { ApplicationManager.getApplication().invokeLater(() -> { String contextState = PitestContext.dumpPitestContext(context); String messageWithContextState = message + "\n\n" + contextState; - Messages.showErrorDialog(project, messageWithContextState, MyBundle.message("error.pitest.general.title")); + Messages.showErrorDialog(project, messageWithContextState, I18nService.getInstance().message("error.pitest.general.title")); }); } } diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/PrepareEnvironmentCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PrepareEnvironmentCommand.java similarity index 84% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/PrepareEnvironmentCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PrepareEnvironmentCommand.java index dfdeb372..bb69a44d 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/PrepareEnvironmentCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PrepareEnvironmentCommand.java @@ -1,8 +1,8 @@ -package com.github.jaksonlin.pitestintellij.commands.pitest; +package com.github.jaksonlin.testcraft.infrastructure.commands.pitest; -import com.github.jaksonlin.pitestintellij.commands.CommandCancellationException; -import com.github.jaksonlin.pitestintellij.context.PitestContext; -import com.github.jaksonlin.pitestintellij.util.*; +import com.github.jaksonlin.testcraft.infrastructure.commands.CommandCancellationException; +import com.github.jaksonlin.testcraft.domain.context.PitestContext; +import com.github.jaksonlin.testcraft.util.*; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.module.Module; @@ -13,13 +13,16 @@ import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; -import com.github.jaksonlin.pitestintellij.MyBundle; + import java.io.File; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; + public class PrepareEnvironmentCommand extends PitestCommand { private final JavaFileProcessor javaFileProcessor = new JavaFileProcessor(); @@ -40,6 +43,7 @@ public void execute() { collectJavaInfo(testVirtualFile); collectSourceRoots(); collectResourceDirectories(); + setWorkingDirectory(); if (getContext().getSourceRoots() != null) { collectTargetClassThatWeTest(getContext().getSourceRoots()); @@ -111,13 +115,38 @@ private void collectSourceRoots() { getContext().setSourceRoots(sourceRoots); } + private void setWorkingDirectory() { + List sourceRoots = getContext().getSourceRoots(); + + // get the test file path + String testFilePathWithoutSrc = getContext().getTestFilePath().split("src/test/java")[0]; + // normalize the test file path + Path normalizedPath = Paths.get(testFilePathWithoutSrc).normalize(); + // normalized path + testFilePathWithoutSrc = normalizedPath.toString(); + // get the source root that contains the test file + String sourceRoot = null; + for (String root : sourceRoots) { + if (testFilePathWithoutSrc.equals(root)) { + sourceRoot = root; + break; + } + } + if (sourceRoot == null) { + showError("Cannot find source root as working directory to run pitest for test file, expected source root: " + testFilePathWithoutSrc); + throw new IllegalStateException("Cannot find source root for test file"); + } + // set the working directory to the source root + getContext().setWorkingDirectory(sourceRoot); + } + private void collectResourceDirectories() { List resourceDirectories = ReadAction.compute(() -> GradleUtils.getResourceDirectories(getProject())); getContext().setResourceDirectories(resourceDirectories); } private void collectTargetClassThatWeTest(List sourceRoots) { - String targetClass = showInputDialog(MyBundle.message("dialog.target.class.message"), MyBundle.message("dialog.target.class.title")); + String targetClass = showInputDialog(I18nService.getInstance().message("dialog.target.class.message"), I18nService.getInstance().message("dialog.target.class.title")); if (targetClass == null || targetClass.isEmpty()) { try { throw new CommandCancellationException("User cancelled the operation"); @@ -192,15 +221,12 @@ private void collectClassPathFileForPitest(String reportDirectory, String target } private void setupPitestLibDependencies(List resourceDirectories) { - String pluginLibDir = ReadAction.compute(() -> PathManager.getPluginsPath() + "/pitest-gradle/lib"); + String pluginLibDir = ReadAction.compute(() -> PathManager.getPluginsPath() + "/TestCraft-Pro/lib"); List dependencies = new ArrayList<>(); File libDir = new File(pluginLibDir); File[] files = libDir.listFiles(); if (files != null) { for (File file : files) { - if (file.getName().startsWith("pitest-gradle-")) { - continue; - } if (file.getName().endsWith(".jar")) { if (file.getName().startsWith("pitest") || file.getName().startsWith("commons")) { dependencies.add(file.getAbsolutePath()); @@ -210,7 +236,7 @@ private void setupPitestLibDependencies(List resourceDirectories) { } dependencies.addAll(resourceDirectories); if (dependencies.isEmpty()) { - Messages.showErrorDialog(MyBundle.message("error.pitest.dependencies"), MyBundle.message("error.pitest.title")); + Messages.showErrorDialog(I18nService.getInstance().message("error.pitest.dependencies"), I18nService.getInstance().message("error.pitest.title")); throw new IllegalStateException("Cannot find pitest dependencies"); } getContext().setPitestDependencies(String.join(File.pathSeparator, dependencies)); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/RunPitestCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/RunPitestCommand.java similarity index 75% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/RunPitestCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/RunPitestCommand.java index 7a31e269..46cc79a7 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/RunPitestCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/RunPitestCommand.java @@ -1,9 +1,9 @@ -package com.github.jaksonlin.pitestintellij.commands.pitest; +package com.github.jaksonlin.testcraft.infrastructure.commands.pitest; -import com.github.jaksonlin.pitestintellij.context.PitestContext; -import com.github.jaksonlin.pitestintellij.util.Mutation; -import com.github.jaksonlin.pitestintellij.util.ProcessExecutor; -import com.github.jaksonlin.pitestintellij.util.ProcessResult; +import com.github.jaksonlin.testcraft.domain.context.PitestContext; +import com.github.jaksonlin.testcraft.util.Mutation; +import com.github.jaksonlin.testcraft.util.ProcessExecutor; +import com.github.jaksonlin.testcraft.util.ProcessResult; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -30,12 +30,14 @@ public void execute() { try { File commandFile = new File(Paths.get(getContext().getClasspathFileDirectory(), "command.txt").toString()); java.nio.file.Files.write(commandFile.toPath(), String.join(" ", command).getBytes()); + File contextFile = new File(Paths.get(getContext().getClasspathFileDirectory(), "context.txt").toString()); + java.nio.file.Files.write(contextFile.toPath(), PitestContext.dumpPitestContext(getContext()).getBytes()); } catch (IOException e) { log.warn("Error writing command to file: " + e.getMessage()); // Not throwing exception here as it's not critical for the process execution } - ProcessResult processResult = ProcessExecutor.executeProcess(command); + ProcessResult processResult = ProcessExecutor.executeProcess(command, getContext().getWorkingDirectory()); getContext().setProcessResult(processResult); try { List mutations = getContext().collectMutationsResults(); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/StoreHistoryCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/StoreHistoryCommand.java similarity index 69% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/StoreHistoryCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/StoreHistoryCommand.java index 9a24a924..ec664965 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/pitest/StoreHistoryCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/StoreHistoryCommand.java @@ -1,6 +1,6 @@ -package com.github.jaksonlin.pitestintellij.commands.pitest; +package com.github.jaksonlin.testcraft.infrastructure.commands.pitest; -import com.github.jaksonlin.pitestintellij.context.PitestContext; +import com.github.jaksonlin.testcraft.domain.context.PitestContext; import com.intellij.openapi.project.Project; public class StoreHistoryCommand extends PitestCommand { diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/CollectScanCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/CollectScanCommand.java new file mode 100644 index 00000000..a1fc5eaf --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/CollectScanCommand.java @@ -0,0 +1,4 @@ +package com.github.jaksonlin.testcraft.infrastructure.commands.testscan; + +public class CollectScanCommand { +} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/UnittestCaseCheckCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/UnittestCaseCheckCommand.java similarity index 85% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/UnittestCaseCheckCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/UnittestCaseCheckCommand.java index 81ef0416..4c83b36d 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/UnittestCaseCheckCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/UnittestCaseCheckCommand.java @@ -1,9 +1,9 @@ -package com.github.jaksonlin.pitestintellij.commands.unittestannotations; +package com.github.jaksonlin.testcraft.infrastructure.commands.testscan; -import com.github.jaksonlin.pitestintellij.MyBundle; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationSchema; -import com.github.jaksonlin.pitestintellij.context.CaseCheckContext; -import com.github.jaksonlin.pitestintellij.context.UnittestCase; + +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationSchema; +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.domain.context.UnittestCase; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.psi.*; @@ -14,6 +14,8 @@ import java.util.List; import java.util.Map; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; + public abstract class UnittestCaseCheckCommand { private final Project project; private final CaseCheckContext context; @@ -37,7 +39,7 @@ public void showSuccessMessage(Project project, String message) { Messages.showMessageDialog( project, message, - MyBundle.message("test.annotation.details.title"), + I18nService.getInstance().message("test.annotation.details.title"), Messages.getInformationIcon() ); } @@ -46,7 +48,7 @@ public void showErrorMessage(Project project, String message) { Messages.showMessageDialog( project, "Error parsing annotation: " + message, - MyBundle.message("test.file.action.title"), + I18nService.getInstance().message("test.file.action.title"), Messages.getErrorIcon() ); } @@ -55,7 +57,7 @@ public void showNoAnnotationMessage(Project project, String annotationName) { Messages.showMessageDialog( project, "No " + annotationName + " annotation found on this method", - MyBundle.message("test.file.action.title"), + I18nService.getInstance().message("test.file.action.title"), Messages.getWarningIcon() ); } @@ -64,7 +66,7 @@ public void showNotJunitTestMethodMessage(Project project) { Messages.showMessageDialog( project, "This method is not a JUnit test method", - MyBundle.message("test.annotation.generation.title"), + I18nService.getInstance().message("test.annotation.generation.title"), Messages.getWarningIcon() ); } @@ -73,7 +75,7 @@ public void showNoMethodMessage(Project project) { Messages.showMessageDialog( project, "No methods found in this class", - MyBundle.message("test.methods.not.found.title"), + I18nService.getInstance().message("test.methods.not.found.title"), Messages.getWarningIcon() ); } @@ -82,7 +84,7 @@ public void showNoTestMethodCanAddMessage(Project project) { Messages.showMessageDialog( project, "No test methods found in the class that can add annotation.", - MyBundle.message("test.methods.no.annotation.title"), + I18nService.getInstance().message("test.methods.no.annotation.title"), Messages.getInformationIcon() ); } @@ -91,7 +93,7 @@ public void showAnnotationAlreadyExistMessage(Project project, String annotation Messages.showMessageDialog( project, annotationName + " already exist on this method", - MyBundle.message("test.annotation.exists.title"), + I18nService.getInstance().message("test.annotation.exists.title"), Messages.getWarningIcon() ); } diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/UnittestFileBatchScanCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/UnittestFileBatchScanCommand.java new file mode 100644 index 00000000..80c8658b --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/UnittestFileBatchScanCommand.java @@ -0,0 +1,213 @@ +package com.github.jaksonlin.testcraft.infrastructure.commands.testscan; + +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; +import com.intellij.codeInspection.InspectionManager; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +public class UnittestFileBatchScanCommand { + private final Set testAnnotations = new HashSet<>(Arrays.asList( + "org.junit.Test", + "org.junit.jupiter.api.Test", + "Test" + )); + + private final Project project; + private final AnActionEvent e; + + public UnittestFileBatchScanCommand(Project project, AnActionEvent e) { + this.project = project; + this.e = e; + } + + public void execute() { + new Task.Backgroundable(project, "Scanning Test Cases", false) { + private List invalidTests = new ArrayList<>(); + + @Override + public void run(@NotNull ProgressIndicator indicator) { + indicator.setIndeterminate(false); + indicator.setText("Finding test classes..."); + + List testClasses = ReadAction.compute(() -> findTestClasses()); + if (testClasses.isEmpty()) { + return; + } + + indicator.setText("Checking test cases..."); + indicator.setFraction(0.0); + double progressStep = 1.0 / testClasses.size(); + + InspectionManager inspectionManager = InspectionManager.getInstance(project); + + for (PsiClass testClass : testClasses) { + if (indicator.isCanceled()) { + break; + } + + final PsiClass finalTestClass = testClass; + String qualifiedName = ReadAction.compute(() -> finalTestClass.getQualifiedName()); + indicator.setText2("Checking " + qualifiedName); + + ReadAction.run(() -> { + for (PsiMethod method : testClass.getMethods()) { + if (isTestMethod(method)) { + CaseCheckContext caseContext = CaseCheckContext.create(method, testClass); + ProblemsHolder holder = new ProblemsHolder(inspectionManager, method.getContainingFile(), true); + UnittestFileInspectorCommand command = new UnittestFileInspectorCommand(holder, project, caseContext); + command.execute(); + + if (holder.hasResults()) { + String methodName = method.getName(); + invalidTests.add(String.format("%s.%s", qualifiedName, methodName)); + } + } + } + }); + + indicator.setFraction(indicator.getFraction() + progressStep); + } + } + + @Override + public void onSuccess() { + if (invalidTests.isEmpty()) { + Messages.showInfoMessage(project, "No invalid test cases found.", "Test Case Validation Results"); + } else { + StringBuilder message = new StringBuilder(); + message.append(String.format("Found %d invalid test cases:\n\n", invalidTests.size())); + for (String test : invalidTests) { + message.append(String.format("- %s\n", test)); + } + Messages.showWarningDialog(project, message.toString(), "Test Case Validation Results"); + } + } + + @Override + public void onCancel() { + Messages.showInfoMessage(project, "Test case validation was canceled.", "Test Case Validation Canceled"); + } + }.queue(); + } + + private List findTestClasses() { + List testClasses = new ArrayList<>(); + PsiManager psiManager = PsiManager.getInstance(project); + + // Get the context from the action event + PsiElement element = e.getData(CommonDataKeys.PSI_ELEMENT); + VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE); + + if (element != null) { + // If triggered from a file or directory in the project view + if (element instanceof PsiFile) { + PsiFile psiFile = (PsiFile) element; + if (psiFile.getName().endsWith(".java")) { + PsiClass[] classes = PsiTreeUtil.getChildrenOfType(psiFile, PsiClass.class); + if (classes != null) { + for (PsiClass psiClass : classes) { + if (isTestClass(psiClass)) { + testClasses.add(psiClass); + } + } + } + } + } else if (element instanceof PsiDirectory) { + // If triggered from a directory, scan that directory + findTestClassesInDirectory((PsiDirectory) element, testClasses); + } + } else if (file != null) { + // If triggered from a file in the editor + if (file.getName().endsWith(".java")) { + PsiFile psiFile = psiManager.findFile(file); + if (psiFile != null) { + PsiClass[] classes = PsiTreeUtil.getChildrenOfType(psiFile, PsiClass.class); + if (classes != null) { + for (PsiClass psiClass : classes) { + if (isTestClass(psiClass)) { + testClasses.add(psiClass); + } + } + } + } + } + } else { + // If no specific context, scan the entire project + VirtualFile baseDir = LocalFileSystem.getInstance().findFileByPath(Objects.requireNonNull(project.getBasePath())); + if (baseDir != null) { + VirtualFile[] children = baseDir.getChildren(); + for (VirtualFile child : children) { + if (child.isDirectory() && (child.getName().equals("src") || child.getName().equals("test"))) { + findTestClassesInDirectory(psiManager.findDirectory(child), testClasses); + } + } + } + } + + return testClasses; + } + + private boolean isTestClass(PsiClass psiClass) { + // Check if class has test annotations + for (PsiAnnotation annotation : psiClass.getAnnotations()) { + if (annotation.getQualifiedName() != null && + (annotation.getQualifiedName().contains("Test") || + annotation.getQualifiedName().contains("RunWith"))) { + return true; + } + } + + // Check if class has test methods + for (PsiMethod method : psiClass.getMethods()) { + if (isTestMethod(method)) { + return true; + } + } + + return false; + } + + private boolean isTestMethod(PsiMethod method) { + for (PsiAnnotation annotation : method.getAnnotations()) { + if (annotation.getQualifiedName() != null && testAnnotations.contains(annotation.getQualifiedName())) { + return true; + } + } + return false; + } + + private void findTestClassesInDirectory(PsiDirectory directory, List testClasses) { + if (directory == null) return; + + for (PsiElement child : directory.getChildren()) { + if (child instanceof PsiDirectory) { + findTestClassesInDirectory((PsiDirectory) child, testClasses); + } else if (child instanceof PsiFile) { + PsiFile file = (PsiFile) child; + if (file.getName().endsWith(".java")) { + PsiClass[] classes = PsiTreeUtil.getChildrenOfType(file, PsiClass.class); + if (classes != null) { + for (PsiClass psiClass : classes) { + if (isTestClass(psiClass)) { + testClasses.add(psiClass); + } + } + } + } + } + } + } +} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/UnittestFileInspectorCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/UnittestFileInspectorCommand.java similarity index 84% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/UnittestFileInspectorCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/UnittestFileInspectorCommand.java index 3ddf104a..1cff7688 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/UnittestFileInspectorCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/testscan/UnittestFileInspectorCommand.java @@ -1,8 +1,8 @@ -package com.github.jaksonlin.pitestintellij.commands.unittestannotations; +package com.github.jaksonlin.testcraft.infrastructure.commands.testscan; -import com.github.jaksonlin.pitestintellij.context.CaseCheckContext; -import com.github.jaksonlin.pitestintellij.services.AnnotationConfigService; -import com.github.jaksonlin.pitestintellij.services.InvalidTestCaseConfigService; +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.infrastructure.services.config.AnnotationConfigService; +import com.github.jaksonlin.testcraft.infrastructure.services.config.InvalidTestCaseConfigService; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.application.ApplicationManager; @@ -18,6 +18,7 @@ import java.util.Optional; import java.util.Stack; +// this is inspector command for unittest file scan public class UnittestFileInspectorCommand extends UnittestCaseCheckCommand { private final ProblemsHolder holder; private final InvalidTestCaseConfigService invalidTestCaseConfigService = ApplicationManager.getApplication().getService(InvalidTestCaseConfigService.class); @@ -28,6 +29,13 @@ public UnittestFileInspectorCommand(ProblemsHolder holder, Project project, Case this.holder = holder; } + private void reportError(String message, ProblemHighlightType highlightType) { + if (holder == null) { + return; + } + holder.registerProblem(getContext().getPsiMethod(), message, highlightType); + } + @Override public void execute() { if (annotationConfigService.shouldCheckAnnotation()) { @@ -51,12 +59,12 @@ private void checkAnnotationSchema(PsiMethod psiMethod) { try { PsiAnnotation annotation = findTargetAnnotation(psiMethod, getContext().getSchema()); if (annotation == null) { - holder.registerProblem(getContext().getPsiMethod(), "No unittest case management annotation found", ProblemHighlightType.WARNING); + reportError("No unittest case management annotation found", ProblemHighlightType.WARNING); return; } parseUnittestCaseFromAnnotations(annotation); } catch (Exception e) { - holder.registerProblem(getContext().getPsiMethod(), e.getMessage() != null ? e.getMessage() : "Unknown error", ProblemHighlightType.WARNING); + reportError(e.getMessage() != null ? e.getMessage() : "Unknown error", ProblemHighlightType.WARNING); } } @@ -72,7 +80,7 @@ private void checkIfCommentHasStepAndAssert(PsiMethod psiMethod) { } } if (!hasStep || !hasAssert) { - holder.registerProblem(psiMethod, "Method should contains both step and assert comment", ProblemHighlightType.WARNING); + reportError("Method should contains both step and assert comment", ProblemHighlightType.WARNING); } } @@ -88,7 +96,7 @@ private void checkIfthereIsAssertionStatement(PsiMethod psiMethod) { return; } - holder.registerProblem(psiMethod, "Method should contains assert statement", ProblemHighlightType.ERROR); + reportError("Method should contains assert statement", ProblemHighlightType.ERROR); } @@ -159,7 +167,7 @@ private void checkIfValidAssertionStatement(PsiMethod psiMethod) { String methodText = assertionMethod.get().getText(); for (String invalidAssertion : invalidAssertions) { if (methodText.contains(invalidAssertion)) { - holder.registerProblem(psiMethod, "Method should contains valid assert statement", ProblemHighlightType.ERROR); + reportError( "Method should contains valid assert statement", ProblemHighlightType.ERROR); return; } } diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/CheckAnnotationCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/unittestannotations/CheckAnnotationCommand.java similarity index 87% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/CheckAnnotationCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/unittestannotations/CheckAnnotationCommand.java index 59621065..aa58be97 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/CheckAnnotationCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/unittestannotations/CheckAnnotationCommand.java @@ -1,9 +1,10 @@ -package com.github.jaksonlin.pitestintellij.commands.unittestannotations; +package com.github.jaksonlin.testcraft.infrastructure.commands.unittestannotations; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationFieldType; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationSchema; -import com.github.jaksonlin.pitestintellij.context.CaseCheckContext; -import com.github.jaksonlin.pitestintellij.context.UnittestCase; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationFieldType; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationSchema; +import com.github.jaksonlin.testcraft.infrastructure.commands.testscan.UnittestCaseCheckCommand; +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.domain.context.UnittestCase; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiComment; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/CheckMethodDataCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/unittestannotations/CheckMethodDataCommand.java similarity index 84% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/CheckMethodDataCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/unittestannotations/CheckMethodDataCommand.java index a790f90d..b05fbaf8 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/CheckMethodDataCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/unittestannotations/CheckMethodDataCommand.java @@ -1,6 +1,7 @@ -package com.github.jaksonlin.pitestintellij.commands.unittestannotations; +package com.github.jaksonlin.testcraft.infrastructure.commands.unittestannotations; -import com.github.jaksonlin.pitestintellij.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.infrastructure.commands.testscan.UnittestCaseCheckCommand; +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiComment; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/GenerateAnnotationCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/unittestannotations/GenerateAnnotationCommand.java similarity index 92% rename from src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/GenerateAnnotationCommand.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/unittestannotations/GenerateAnnotationCommand.java index bb8a56b6..b84d3b51 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/commands/unittestannotations/GenerateAnnotationCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/unittestannotations/GenerateAnnotationCommand.java @@ -1,13 +1,14 @@ -package com.github.jaksonlin.pitestintellij.commands.unittestannotations; +package com.github.jaksonlin.testcraft.infrastructure.commands.unittestannotations; import com.fasterxml.jackson.databind.node.ArrayNode; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationFieldConfig; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationFieldType; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationSchema; -import com.github.jaksonlin.pitestintellij.annotations.DefaultValue; -import com.github.jaksonlin.pitestintellij.context.CaseCheckContext; -import com.github.jaksonlin.pitestintellij.services.AnnotationConfigService; -import com.github.jaksonlin.pitestintellij.services.AnnotationValueProviderService; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationFieldConfig; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationFieldType; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationSchema; +import com.github.jaksonlin.testcraft.domain.annotations.DefaultValue; +import com.github.jaksonlin.testcraft.infrastructure.commands.testscan.UnittestCaseCheckCommand; +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.infrastructure.services.config.AnnotationConfigService; +import com.github.jaksonlin.testcraft.infrastructure.services.business.AnnotationValueProviderService; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.command.WriteCommandAction; @@ -151,7 +152,6 @@ protected JComponent createCenterPanel() { } } if (selectedMethodCount > 0) { - CountDownLatch latch = new CountDownLatch(selectedMethodCount); ProgressManager.getInstance().run(new Task.Backgroundable(getProject(), "Applying annotations") { @Override public void run(@NotNull ProgressIndicator indicator) { @@ -159,22 +159,16 @@ public void run(@NotNull ProgressIndicator indicator) { for (int i = 0; i < testMethods.size(); i++) { if (selected[i]) { int finalI = i; - ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { generateAnnotation(testMethods.get(finalI), getContext().getSchema()); } catch (Exception e) { LOG.error("Failed to generate annotation", e); - } finally { - latch.countDown(); } - }); + } } - try { - latch.await(); - } catch (InterruptedException e) { - LOG.error("Interrupted while waiting for latch", e); - } + } }); } diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/license/PremiumManager.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/license/PremiumManager.java similarity index 92% rename from src/main/java/com/github/jaksonlin/pitestintellij/license/PremiumManager.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/license/PremiumManager.java index b21ed9cb..784b9f12 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/license/PremiumManager.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/license/PremiumManager.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.license; +package com.github.jaksonlin.testcraft.infrastructure.license; public class PremiumManager { private static PremiumManager instance; diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/BaseEvent.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/BaseEvent.java new file mode 100644 index 00000000..35e456fd --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/BaseEvent.java @@ -0,0 +1,27 @@ +package com.github.jaksonlin.testcraft.infrastructure.messaging.events; + +public abstract class BaseEvent { + private final String eventType; + private final Object payload; + + protected BaseEvent(String eventType, Object payload) { + this.eventType = eventType; + this.payload = payload; + } + + public String getEventType() { + return eventType; + } + + public Object getPayload() { + return payload; + } + + @Override + public String toString() { + return "BaseEvent{" + + "eventType='" + eventType + '\'' + + ", payload=" + payload + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/BasicEventObserver.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/BasicEventObserver.java new file mode 100644 index 00000000..296a8d68 --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/BasicEventObserver.java @@ -0,0 +1,16 @@ +package com.github.jaksonlin.testcraft.infrastructure.messaging.events; + +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.BaseEvent; +import com.google.common.eventbus.Subscribe; + +public abstract class BasicEventObserver { + protected BasicEventObserver() { + } + + @Subscribe + public void onEvent(BaseEvent event) { + onEventHappen(event.getEventType(), event.getPayload()); + } + + public abstract void onEventHappen(String eventName, Object eventObj); +} diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/ChatEvent.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/ChatEvent.java new file mode 100644 index 00000000..2a467d4f --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/ChatEvent.java @@ -0,0 +1,17 @@ +package com.github.jaksonlin.testcraft.infrastructure.messaging.events; + +public class ChatEvent extends BaseEvent { + public static final String CLEAR_CHAT = "CLEAR_CHAT"; + public static final String CHAT_REQUEST = "CHAT_REQUEST"; + public static final String CHAT_RESPONSE = "CHAT_RESPONSE"; + public static final String START_LOADING = "START_LOADING"; + public static final String STOP_LOADING = "STOP_LOADING"; + public static final String REQUEST_COPY_CHAT_RESPONSE = "REQUEST_COPY_CHAT_RESPONSE"; + public static final String COPY_CHAT_RESPONSE = "COPY_CHAT_RESPONSE"; + public static final String DRY_RUN_PROMPT = "DRY_RUN_PROMPT"; + public static final String ERROR = "ERROR"; + + public ChatEvent(String eventType, Object payload) { + super(eventType, payload); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/EventHandler.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/EventHandler.java new file mode 100644 index 00000000..eb3aed6c --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/EventHandler.java @@ -0,0 +1,8 @@ +package com.github.jaksonlin.testcraft.infrastructure.messaging.events; + + +@FunctionalInterface +public interface EventHandler { + void handle(T event); +} + diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/LLMConfigEvent.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/LLMConfigEvent.java new file mode 100644 index 00000000..19408a34 --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/LLMConfigEvent.java @@ -0,0 +1,10 @@ +package com.github.jaksonlin.testcraft.infrastructure.messaging.events; + +public class LLMConfigEvent extends BaseEvent { + public static final String CONFIG_CHANGE = "CONFIG_CHANGE"; + public static final String CONFIG_CHANGE_COPY_AS_MARKDOWN = "CONFIG_CHANGE:copyAsMarkdown"; + + public LLMConfigEvent(String eventType, Object payload) { + super(eventType, payload); + } +} diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/MutationEvent.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/MutationEvent.java new file mode 100644 index 00000000..018bbaea --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/MutationEvent.java @@ -0,0 +1,11 @@ +package com.github.jaksonlin.testcraft.infrastructure.messaging.events; + +public class MutationEvent extends BaseEvent { + public static final String MUTATION_RESULT = "MUTATION_RESULT"; + + public MutationEvent(String eventType, Object payload) { + super(eventType, payload); + } + + +} diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/RunHistoryEvent.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/RunHistoryEvent.java new file mode 100644 index 00000000..cce4d852 --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/RunHistoryEvent.java @@ -0,0 +1,10 @@ +package com.github.jaksonlin.testcraft.infrastructure.messaging.events; + +public class RunHistoryEvent extends BaseEvent { + public static final String RUN_HISTORY = "RUN_HISTORY"; + public static final String RUN_HISTORY_LIST = "RUN_HISTORY_LIST"; + + public RunHistoryEvent(String eventType, Object payload) { + super(eventType, payload); + } +} diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/TypedEventObserver.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/TypedEventObserver.java new file mode 100644 index 00000000..c67884cc --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/events/TypedEventObserver.java @@ -0,0 +1,28 @@ +package com.github.jaksonlin.testcraft.infrastructure.messaging.events; + +import com.github.jaksonlin.testcraft.infrastructure.services.system.EventBusService; +import com.google.common.eventbus.Subscribe; + +public abstract class TypedEventObserver { + private final Class eventType; + private final EventBusService eventBus; + + protected TypedEventObserver(Class eventType) { + this.eventType = eventType; + this.eventBus = EventBusService.getInstance(); + this.eventBus.register(this); + } + + @Subscribe + public void onEvent(BaseEvent event) { + if (eventType.isInstance(event)) { + onTypedEvent(eventType.cast(event)); + } + } + + protected abstract void onTypedEvent(T event); + + public void unregister() { + eventBus.unregister(this); + } +} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/listeners/MyApplicationActivationListener.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/listeners/MyApplicationActivationListener.java similarity index 87% rename from src/main/java/com/github/jaksonlin/pitestintellij/listeners/MyApplicationActivationListener.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/listeners/MyApplicationActivationListener.java index 1970f18f..c2addd07 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/listeners/MyApplicationActivationListener.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/listeners/MyApplicationActivationListener.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.listeners; +package com.github.jaksonlin.testcraft.infrastructure.messaging.listeners; import com.intellij.openapi.application.ApplicationActivationListener; import com.intellij.openapi.diagnostic.Logger; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/ILLMChatMediator.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/ILLMChatMediator.java similarity index 65% rename from src/main/java/com/github/jaksonlin/pitestintellij/mediators/ILLMChatMediator.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/ILLMChatMediator.java index 4d975d65..3d7199db 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/ILLMChatMediator.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/ILLMChatMediator.java @@ -1,14 +1,13 @@ -package com.github.jaksonlin.pitestintellij.mediators; +package com.github.jaksonlin.testcraft.infrastructure.messaging.mediators; -import com.github.jaksonlin.pitestintellij.util.Mutation; -import com.github.jaksonlin.pitestintellij.util.OllamaClient; +import com.github.jaksonlin.testcraft.util.Mutation; +import com.github.jaksonlin.testcraft.util.OllamaClient; import java.util.List; public interface ILLMChatMediator { void setOllamaClient(OllamaClient ollamaClient); void generateUnittestRequest(String testCodeFile, String sourceCodeFile, List mutationList); - void register(ILLMChatClient chatClient); String dryRunGetPrompt(String testClassName, String sourceClassName, List mutations); void handleChatMessage(String message); void clearChat(); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/IMutationMediator.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/IMutationMediator.java similarity index 50% rename from src/main/java/com/github/jaksonlin/pitestintellij/mediators/IMutationMediator.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/IMutationMediator.java index 69fda407..4a7ebcdb 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/IMutationMediator.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/IMutationMediator.java @@ -1,10 +1,9 @@ -package com.github.jaksonlin.pitestintellij.mediators; +package com.github.jaksonlin.testcraft.infrastructure.messaging.mediators; -import com.github.jaksonlin.pitestintellij.util.Mutation; +import com.github.jaksonlin.testcraft.util.Mutation; import java.util.List; public interface IMutationMediator { void processMutationResult(String mutationTargetClassFilePath, List mutationList); - void register(IMutationReportUI clientUI); } diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/LLMChatMediatorImpl.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/LLMChatMediatorImpl.java similarity index 82% rename from src/main/java/com/github/jaksonlin/pitestintellij/mediators/LLMChatMediatorImpl.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/LLMChatMediatorImpl.java index da0a3256..e685282b 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/LLMChatMediatorImpl.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/LLMChatMediatorImpl.java @@ -1,12 +1,15 @@ -package com.github.jaksonlin.pitestintellij.mediators; +package com.github.jaksonlin.testcraft.infrastructure.messaging.mediators; -import com.github.jaksonlin.pitestintellij.MyBundle; -import com.github.jaksonlin.pitestintellij.util.Mutation; -import com.github.jaksonlin.pitestintellij.util.OllamaClient; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.ChatEvent; +import com.github.jaksonlin.testcraft.infrastructure.services.system.EventBusService; +import com.github.jaksonlin.testcraft.util.Mutation; +import com.github.jaksonlin.testcraft.util.OllamaClient; import com.intellij.openapi.diagnostic.Logger; import javax.swing.*; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; @@ -20,7 +23,7 @@ public class LLMChatMediatorImpl implements ILLMChatMediator { private static final Logger LOG = Logger.getInstance(LLMChatMediatorImpl.class); - private ILLMChatClient chatClient; + private final EventBusService eventBusService = EventBusService.getInstance(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(); private OllamaClient ollamaClient; private final List messageHistory = new ArrayList<>(); @@ -38,7 +41,6 @@ public String getChatHistory() { .map(message -> message.getRole() + ": " + message.getContent()) .collect(Collectors.joining("\n")); } - // generate unittest using mutation result, this will clear the chat history @Override public void generateUnittestRequest(String testCodeFile, String sourceCodeFile, List mutations) { @@ -46,31 +48,26 @@ public void generateUnittestRequest(String testCodeFile, String sourceCodeFile, try { // Test connection before attempting to send message if (!ollamaClient.testConnection()) { - if (chatClient != null) { - SwingUtilities.invokeLater(() -> chatClient.updateChatResponse("ERROR", - MyBundle.message("llm.error.connection"))); - } + eventBusService.post(new ChatEvent(ChatEvent.CHAT_RESPONSE, + I18nService.getInstance().message("llm.error.connection"))); return; } + List messages = createPromptOnly(testCodeFile, sourceCodeFile, mutations); if (messages.isEmpty()) { - if (chatClient != null) { - SwingUtilities.invokeLater(() -> chatClient.updateChatResponse("ERROR", - MyBundle.message("llm.error.no.mutations"))); - } + eventBusService.post(new ChatEvent(ChatEvent.CHAT_RESPONSE, + I18nService.getInstance().message("llm.error.no.mutations"))); return; } messageHistory.addAll(messages); String rawResponse = ollamaClient.chatCompletion(messageHistory); messageHistory.add(new OllamaClient.Message("assistant", rawResponse)); String formattedResponse = formatResponse(rawResponse); - SwingUtilities.invokeLater(() -> chatClient.updateChatResponse("UNIT_TEST_REQUEST", formattedResponse)); + eventBusService.post(new ChatEvent(ChatEvent.CHAT_RESPONSE, formattedResponse)); } catch (Exception e) { LOG.error("Failed to generate unit test suggestions", e); - if (chatClient != null) { - SwingUtilities.invokeLater(() -> chatClient.updateChatResponse("ERROR", "Error: " + e.toString())); - } + eventBusService.post(new ChatEvent(ChatEvent.CHAT_RESPONSE, "Error: " + e.toString())); } }); } @@ -82,10 +79,8 @@ public void handleChatMessage(String message) { try { // Test connection before attempting to send message if (!ollamaClient.testConnection()) { - if (chatClient != null) { - SwingUtilities.invokeLater(() -> chatClient.updateChatResponse("ERROR", - MyBundle.message("llm.error.connection"))); - } + eventBusService.post(new ChatEvent(ChatEvent.CHAT_RESPONSE, + I18nService.getInstance().message("llm.error.connection"))); return; } messageHistory.add(new OllamaClient.Message("user", message)); @@ -93,12 +88,10 @@ public void handleChatMessage(String message) { messageHistory.add(new OllamaClient.Message("assistant", rawResponse)); // Format the response String formattedResponse = formatResponse(rawResponse); - chatClient.updateChatResponse("CHAT_MESSAGE", formattedResponse); + eventBusService.post(new ChatEvent(ChatEvent.CHAT_RESPONSE, formattedResponse)); } catch (Exception e) { LOG.error("Failed to respond to chat message", e); - if (chatClient != null) { - SwingUtilities.invokeLater(() -> chatClient.updateChatResponse("ERROR", "Error: " + e.toString())); - } + eventBusService.post(new ChatEvent(ChatEvent.CHAT_RESPONSE, "Error: " + e.toString())); } }); } @@ -196,14 +189,14 @@ private List createPromptOnly(String testCodeFile, String .count(); // System message to set context - promptOnlyMessages.add(new OllamaClient.Message("system", MyBundle.message("llm.prompt.system"))); + promptOnlyMessages.add(new OllamaClient.Message("system", I18nService.getInstance().message("llm.prompt.system"))); // Read source files if needed - String sourceCode = Files.readString(Paths.get(sourceCodeFile)); - String testCode = Files.readString(Paths.get(testCodeFile)); + String sourceCode = readFileContent(sourceCodeFile); + String testCode = readFileContent(testCodeFile); // User message with the structured data - String prompt = String.format(MyBundle.message("llm.prompt.user"), + String prompt = String.format(I18nService.getInstance().message("llm.prompt.user"), sourceCode, testCode, totalMutations, @@ -234,10 +227,10 @@ private List createPromptOnlyWithoutCodeContent(String tes .count(); // System message to set context - promptOnlyMessages.add(new OllamaClient.Message("system", MyBundle.message("llm.prompt.system"))); + promptOnlyMessages.add(new OllamaClient.Message("system", I18nService.getInstance().message("llm.prompt.system"))); // User message with the structured data - String prompt = String.format(MyBundle.message("llm.prompt.user.compact"), + String prompt = String.format(I18nService.getInstance().message("llm.prompt.user.compact"), testClassName, sourceClassName, totalMutations, @@ -252,6 +245,18 @@ private List createPromptOnlyWithoutCodeContent(String tes return promptOnlyMessages; } + + private String readFileContent(String filePath) { + try { + return new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Failed to read file: " + filePath, e); + } + } + + private String getSourceCode(String sourceCodeFile) { + return readFileContent(sourceCodeFile); + } @Override public String dryRunGetPrompt(String testClassName, String sourceClassName, List mutations) { try { @@ -293,12 +298,6 @@ public String dryRunGetPrompt(String testClassName, String sourceClassName, List } } - - - @Override - public void register(ILLMChatClient chatClient) { - this.chatClient = chatClient; - } } diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/MutationMediatorImpl.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/MutationMediatorImpl.java similarity index 79% rename from src/main/java/com/github/jaksonlin/pitestintellij/mediators/MutationMediatorImpl.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/MutationMediatorImpl.java index 62b641dd..620602d0 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/mediators/MutationMediatorImpl.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/messaging/mediators/MutationMediatorImpl.java @@ -1,7 +1,9 @@ -package com.github.jaksonlin.pitestintellij.mediators; +package com.github.jaksonlin.testcraft.infrastructure.messaging.mediators; -import com.github.jaksonlin.pitestintellij.util.Mutation; -import com.github.jaksonlin.pitestintellij.util.Pair; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.MutationEvent; +import com.github.jaksonlin.testcraft.infrastructure.services.system.EventBusService; +import com.github.jaksonlin.testcraft.util.Mutation; +import com.github.jaksonlin.testcraft.util.Pair; import javax.swing.*; import java.util.ArrayList; @@ -12,16 +14,14 @@ import java.util.concurrent.Executors; public class MutationMediatorImpl implements IMutationMediator { - protected IMutationReportUI clientUI; private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final EventBusService eventBusService = EventBusService.getInstance(); @Override public void processMutationResult(String mutationTargetClassFilePath, List mutations) { executorService.submit(() -> { Map> renderedFormat = convertResultToUIRenderFormat(mutations); - if (clientUI != null) { - SwingUtilities.invokeLater(() -> clientUI.updateMutationResult(mutationTargetClassFilePath, renderedFormat)); - } + eventBusService.post(new MutationEvent(MutationEvent.MUTATION_RESULT, new Pair>>(mutationTargetClassFilePath, renderedFormat))); }); } @@ -62,8 +62,5 @@ private String mutationMessageFormat(Mutation mutation, int groupNumber) { return groupNumber + " " + mutation.getDescription() + " -> " + mutation.getStatus(); } - @Override - public void register(IMutationReportUI clientUI) { - this.clientUI = clientUI; - } + } diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/services/AnnotationValueProviderService.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/business/AnnotationValueProviderService.java similarity index 77% rename from src/main/java/com/github/jaksonlin/pitestintellij/services/AnnotationValueProviderService.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/business/AnnotationValueProviderService.java index 592894df..d8afef86 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/services/AnnotationValueProviderService.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/business/AnnotationValueProviderService.java @@ -1,11 +1,13 @@ -package com.github.jaksonlin.pitestintellij.services; +package com.github.jaksonlin.testcraft.infrastructure.services.business; -import com.github.jaksonlin.pitestintellij.annotations.ValueProvider; -import com.github.jaksonlin.pitestintellij.annotations.ValueProviderType; -import com.github.jaksonlin.pitestintellij.context.CaseCheckContext; -import com.github.jaksonlin.pitestintellij.util.GitUtil; +import com.github.jaksonlin.testcraft.domain.annotations.ValueProvider; +import com.github.jaksonlin.testcraft.domain.annotations.ValueProviderType; +import com.github.jaksonlin.testcraft.domain.context.CaseCheckContext; +import com.github.jaksonlin.testcraft.util.GitUtil; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.Service; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.NotNull; @@ -29,37 +31,39 @@ public AnnotationValueProviderService(Project project) { @Nullable public Object provideValue(@NotNull ValueProvider provider, @NotNull CaseCheckContext context) { - ValueProviderType type = provider.getType(); - if (type != null) { - switch (type) { - case GIT_AUTHOR: - return getGitAuthor(); - case LAST_MODIFIER_AUTHOR: - return getLastModifierAuthor(context.getPsiMethod()); - case LAST_MODIFIER_TIME: - return getLastModifierTime(context.getPsiMethod()); - case CURRENT_DATE: - return getCurrentDate(provider.getFormat()); - case METHOD_NAME_BASED: - return generateDescription(context.getPsiMethod()); - case FIXED_VALUE: - return provider.getValue(); - case CLASS_NAME: - return guessClassUnderTestClassName(context.getPsiClass()); - case METHOD_NAME: - return guessMethodUnderTestMethodName(context.getPsiMethod()); - case METHOD_SIGNATURE: - String signature = tryGetMethodUnderTestSignature(context.getPsiClass(), context.getPsiMethod()); - return signature != null ? signature : ""; - case FIRST_CREATOR_AUTHOR: - return getFirstCreatorAuthor(context.getPsiMethod()); - case FIRST_CREATOR_TIME: - return getFirstCreatorTime(context.getPsiMethod()); - default: - return null; + return ApplicationManager.getApplication().runReadAction((Computable)() -> { + ValueProviderType type = provider.getType(); + if (type != null) { + switch (type) { + case GIT_AUTHOR: + return getGitAuthor(); + case LAST_MODIFIER_AUTHOR: + return getLastModifierAuthor(context.getPsiMethod()); + case LAST_MODIFIER_TIME: + return getLastModifierTime(context.getPsiMethod()); + case CURRENT_DATE: + return getCurrentDate(provider.getFormat()); + case METHOD_NAME_BASED: + return generateDescription(context.getPsiMethod()); + case FIXED_VALUE: + return provider.getValue(); + case CLASS_NAME: + return guessClassUnderTestClassName(context.getPsiClass()); + case METHOD_NAME: + return guessMethodUnderTestMethodName(context.getPsiMethod()); + case METHOD_SIGNATURE: + String signature = tryGetMethodUnderTestSignature(context.getPsiClass(), context.getPsiMethod()); + return signature != null ? signature : ""; + case FIRST_CREATOR_AUTHOR: + return getFirstCreatorAuthor(context.getPsiMethod()); + case FIRST_CREATOR_TIME: + return getFirstCreatorTime(context.getPsiMethod()); + default: + return null; + } } - } - return null; + return null; + }); } private String getGitAuthor() { diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/business/PitestService.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/business/PitestService.java new file mode 100644 index 00000000..72ad64d3 --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/business/PitestService.java @@ -0,0 +1,9 @@ +package com.github.jaksonlin.testcraft.infrastructure.services.business; + +import com.intellij.openapi.components.Service; + +@Service(Service.Level.APP) +public final class PitestService { + + +} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/services/RunHistoryManagerService.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/business/RunHistoryManagerService.java similarity index 67% rename from src/main/java/com/github/jaksonlin/pitestintellij/services/RunHistoryManagerService.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/business/RunHistoryManagerService.java index 5bec62c5..ac9c1369 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/services/RunHistoryManagerService.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/business/RunHistoryManagerService.java @@ -1,15 +1,15 @@ -package com.github.jaksonlin.pitestintellij.services; +package com.github.jaksonlin.testcraft.infrastructure.services.business; -import com.github.jaksonlin.pitestintellij.context.PitestContext; -import com.github.jaksonlin.pitestintellij.observers.ObserverBase; -import com.github.jaksonlin.pitestintellij.observers.BasicEventObserver; -import com.github.jaksonlin.pitestintellij.util.Pair; +import com.github.jaksonlin.testcraft.domain.context.PitestContext; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.RunHistoryEvent; +import com.github.jaksonlin.testcraft.infrastructure.services.system.EventBusService; +import com.github.jaksonlin.testcraft.util.Pair; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.components.Service; -import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -24,30 +24,32 @@ import java.util.Map; import java.util.stream.Collectors; -@Service(Service.Level.PROJECT) -public final class RunHistoryManagerService extends ObserverBase { +@Service(Service.Level.APP) +public final class RunHistoryManagerService { private static final Logger log = LoggerFactory.getLogger(RunHistoryManagerService.class); - private final Project project; + private final EventBusService eventBusService = EventBusService.getInstance(); private final Gson gson = new Gson(); private final File historyFile; private final Map history; - public RunHistoryManagerService(@NotNull Project project) { - this.project = project; - this.historyFile = new File(PathManager.getConfigPath(), "run-" + project.getName() + "-history.json"); + public static RunHistoryManagerService getInstance() { + return ApplicationManager.getApplication().getService(RunHistoryManagerService.class); + } + + + public RunHistoryManagerService() { + this.historyFile = new File(PathManager.getConfigPath(), "run-history.json"); this.history = loadRunHistory(); } - @Override - public void addObserver(BasicEventObserver observer) { - super.addObserver(observer); + public void addObserver(Object observer) { + eventBusService.register(observer); // when observer is added, pass current value of history to observer, force it to update - // pass current value of history to observer, List> List> mappedHistory = history.entrySet().stream() .map(entry -> new Pair<>(entry.getValue().getTargetClassPackageName(), entry.getValue().getTargetClassName())) .collect(Collectors.toList()); - observer.onEventHappen("RUN_HISTORY", mappedHistory); - observer.onEventHappen("RUN_HISTORY_LIST", getRunHistory()); + eventBusService.post(new RunHistoryEvent(RunHistoryEvent.RUN_HISTORY, mappedHistory)); + eventBusService.post(new RunHistoryEvent(RunHistoryEvent.RUN_HISTORY_LIST, getRunHistory())); } @Nullable @@ -70,8 +72,8 @@ public void clearRunHistory() { if (historyFile.exists()) { historyFile.delete(); } - notifyObservers("RUN_HISTORY", null); - notifyObservers("RUN_HISTORY_LIST", null); + eventBusService.post(new RunHistoryEvent(RunHistoryEvent.RUN_HISTORY, null)); + eventBusService.post(new RunHistoryEvent(RunHistoryEvent.RUN_HISTORY_LIST, null)); } @NotNull @@ -85,8 +87,8 @@ public void saveRunHistory(@NotNull PitestContext entry) { String json = gson.toJson(history); Files.write(historyFile.toPath(), json.getBytes()); // this should be a Pair> - notifyObservers("RUN_HISTORY", new Pair(entry.getTargetClassPackageName(), entry.getTargetClassName())); - notifyObservers("RUN_HISTORY_LIST", getRunHistory()); + eventBusService.post(new RunHistoryEvent(RunHistoryEvent.RUN_HISTORY, new Pair(entry.getTargetClassPackageName(), entry.getTargetClassName()))); + eventBusService.post(new RunHistoryEvent(RunHistoryEvent.RUN_HISTORY_LIST, getRunHistory())); } catch (IOException e) { // Handle the exception appropriately, e.g., log an error log.error("Error saving run history", e); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/services/AnnotationConfigService.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/config/AnnotationConfigService.java similarity index 96% rename from src/main/java/com/github/jaksonlin/pitestintellij/services/AnnotationConfigService.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/config/AnnotationConfigService.java index 492982c3..dc40bdad 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/services/AnnotationConfigService.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/config/AnnotationConfigService.java @@ -1,8 +1,8 @@ -package com.github.jaksonlin.pitestintellij.services; +package com.github.jaksonlin.testcraft.infrastructure.services.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationSchema; +import com.github.jaksonlin.testcraft.domain.annotations.AnnotationSchema; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.Service; import com.intellij.openapi.components.State; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/services/InvalidTestCaseConfigService.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/config/InvalidTestCaseConfigService.java similarity index 96% rename from src/main/java/com/github/jaksonlin/pitestintellij/services/InvalidTestCaseConfigService.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/config/InvalidTestCaseConfigService.java index cf96a51e..b3a1bda1 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/services/InvalidTestCaseConfigService.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/config/InvalidTestCaseConfigService.java @@ -1,6 +1,5 @@ -package com.github.jaksonlin.pitestintellij.services; +package com.github.jaksonlin.testcraft.infrastructure.services.config; -import com.github.jaksonlin.pitestintellij.annotations.AnnotationSchema; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.Service; import com.intellij.openapi.components.State; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/services/LLMService.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/config/LLMConfigService.java similarity index 68% rename from src/main/java/com/github/jaksonlin/pitestintellij/services/LLMService.java rename to src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/config/LLMConfigService.java index 7917bb0f..7530e2f7 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/services/LLMService.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/config/LLMConfigService.java @@ -1,11 +1,10 @@ -package com.github.jaksonlin.pitestintellij.services; - -import com.github.jaksonlin.pitestintellij.mediators.ILLMChatMediator; -import com.github.jaksonlin.pitestintellij.mediators.LLMChatMediatorImpl; -import com.github.jaksonlin.pitestintellij.observers.BasicEventObserver; -import com.github.jaksonlin.pitestintellij.observers.ObserverBase; -import com.github.jaksonlin.pitestintellij.util.Mutation; -import com.github.jaksonlin.pitestintellij.util.OllamaClient; +package com.github.jaksonlin.testcraft.infrastructure.services.config; + +import com.github.jaksonlin.testcraft.infrastructure.messaging.mediators.ILLMChatMediator; +import com.github.jaksonlin.testcraft.infrastructure.messaging.mediators.LLMChatMediatorImpl; +import com.github.jaksonlin.testcraft.infrastructure.services.system.EventBusService; +import com.github.jaksonlin.testcraft.util.Mutation; +import com.github.jaksonlin.testcraft.util.OllamaClient; import com.intellij.openapi.components.Service; import com.intellij.openapi.diagnostic.Logger; @@ -13,25 +12,32 @@ import java.util.Objects; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import com.github.jaksonlin.pitestintellij.mediators.ILLMChatClient; + +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.BasicEventObserver; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.ChatEvent; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.LLMConfigEvent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; @Service(Service.Level.APP) @State( - name = "com.github.jaksonlin.pitestintellij.services.LLMService", + name = "com.github.jaksonlin.testcraft.infrastructure.services.LLMService", storages = @Storage(value = "$APP_CONFIG$/LLMService.xml") ) -public final class LLMService - extends ObserverBase - implements ILLMChatClient, BasicEventObserver, PersistentStateComponent { +public final class LLMConfigService + extends BasicEventObserver + implements PersistentStateComponent { - private static final Logger LOG = Logger.getInstance(LLMService.class); + private static final Logger LOG = Logger.getInstance(LLMConfigService.class); private final ILLMChatMediator llmChatMediator = new LLMChatMediatorImpl(); + public static LLMConfigService getInstance() { + return ApplicationManager.getApplication().getService(LLMConfigService.class); + } - public LLMService() { - llmChatMediator.register(this); + public LLMConfigService() { + EventBusService.getInstance().register(this); } public static class State { @@ -78,7 +84,7 @@ public State getState() { public void loadState(@NotNull State state) { LOG.info("Loading LLMService state: " + state); myState = state; - notifyObservers("CONFIG_CHANGE:copyAsMarkdown", state.copyAsMarkdown); + EventBusService.getInstance().post(new LLMConfigEvent(LLMConfigEvent.CONFIG_CHANGE_COPY_AS_MARKDOWN, state.copyAsMarkdown)); } public void generateUnittestRequest(String testCodeFile, String sourceCodeFile, List mutationList) { @@ -95,15 +101,7 @@ public void generateUnittestRequest(String testCodeFile, String sourceCodeFile, llmChatMediator.generateUnittestRequest(testCodeFile, sourceCodeFile, mutationList); } - // the mediator is dedicated to talk to the LLM, and when the response is ready, the service will propagate the response to the observers - // so that on the UI layer, this is the place to notify all the observers - // the mediator is not responsible for the UI, so it does not know the UI is a Swing UI - @Override - public void updateChatResponse(String responseType, String chatResponse) { - LOG.info("Received chat response: " + chatResponse); - notifyObservers("CHAT_RESPONSE:" + responseType, chatResponse); - } - + public void handleChatMessage(String message) { LOG.info("Received chat message: " + message); llmChatMediator.setOllamaClient(new OllamaClient( @@ -121,10 +119,14 @@ public String dryRunGetPrompt(String testClassName, String sourceClassName, List return llmChatMediator.dryRunGetPrompt(testClassName, sourceClassName, mutations); } + @Override public void onEventHappen(String eventName, Object eventObj) { // Handle events from LLMChatMediator if needed switch (eventName) { - case "CLEAR_CHAT": + case ChatEvent.CHAT_REQUEST: + handleChatMessage(eventObj.toString()); + break; + case ChatEvent.CLEAR_CHAT: this.llmChatMediator.clearChat(); break; default: @@ -137,6 +139,6 @@ public String getChatHistory() { } public void propagateConfigChange() { - notifyObservers("CONFIG_CHANGE:copyAsMarkdown", myState.copyAsMarkdown); + EventBusService.getInstance().post(new LLMConfigEvent(LLMConfigEvent.CONFIG_CHANGE_COPY_AS_MARKDOWN, myState.copyAsMarkdown)); } } \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/system/EventBusService.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/system/EventBusService.java new file mode 100644 index 00000000..f68ffebc --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/system/EventBusService.java @@ -0,0 +1,51 @@ +package com.github.jaksonlin.testcraft.infrastructure.services.system; + +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.BaseEvent; +import com.google.common.eventbus.EventBus; +import com.intellij.openapi.components.Service; +import com.intellij.openapi.application.ApplicationManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Service(Service.Level.APP) +public final class EventBusService { + private final EventBus eventBus; + private final Logger logger = LoggerFactory.getLogger(EventBusService.class); + + public EventBusService() { + this.eventBus = new EventBus("TestCraftEventBus"); + } + + public void register(Object subscriber) { + try { + eventBus.register(subscriber); + logger.debug("Registered subscriber: {}", subscriber.getClass().getSimpleName()); + } catch (Exception e) { + logger.error("Failed to register subscriber: " + subscriber, e); + } + } + + public void unregister(Object subscriber) { + try { + eventBus.unregister(subscriber); + logger.debug("Unregistered subscriber: {}", subscriber.getClass().getSimpleName()); + } catch (Exception e) { + logger.error("Failed to unregister subscriber: " + subscriber, e); + } + } + + public void post(Object event) { + try { + if (event instanceof BaseEvent) { + logger.debug("Posting event: {}", ((BaseEvent) event).getEventType()); + } + eventBus.post(event); + } catch (Exception e) { + logger.error("Failed to post event: " + event, e); + } + } + + public static EventBusService getInstance() { + return ApplicationManager.getApplication().getService(EventBusService.class); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/system/I18nService.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/system/I18nService.java new file mode 100644 index 00000000..6c9c3032 --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/services/system/I18nService.java @@ -0,0 +1,28 @@ +package com.github.jaksonlin.testcraft.infrastructure.services.system; + +import java.util.ResourceBundle; + +import org.jetbrains.annotations.PropertyKey; + +import com.intellij.AbstractBundle; +import com.intellij.openapi.components.Service; +import com.intellij.openapi.application.ApplicationManager; + +@Service(Service.Level.APP) +public final class I18nService { + private final String BUNDLE = "messages.MyBundle"; + private ResourceBundle ourBundle; + public String message(@PropertyKey(resourceBundle = BUNDLE) String key, Object... params) { + return AbstractBundle.message(getBundle(), key, params); + } + private ResourceBundle getBundle() { + if (ourBundle == null) { + ourBundle = ResourceBundle.getBundle(BUNDLE); + } + return ourBundle; + } + + public static I18nService getInstance() { + return ApplicationManager.getApplication().getService(I18nService.class); + } +} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/settings/AnnotationSettingsComponent.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/AnnotationSettingsComponent.java similarity index 66% rename from src/main/java/com/github/jaksonlin/pitestintellij/settings/AnnotationSettingsComponent.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/AnnotationSettingsComponent.java index b9b2fe01..6c0a7c3c 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/settings/AnnotationSettingsComponent.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/AnnotationSettingsComponent.java @@ -1,8 +1,7 @@ -package com.github.jaksonlin.pitestintellij.settings; +package com.github.jaksonlin.testcraft.presentation.components.configuration; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; import com.intellij.json.JsonFileType; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.EditorSettings; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.ex.EditorEx; @@ -10,10 +9,8 @@ import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBTextField; import org.jetbrains.annotations.NotNull; -import com.github.jaksonlin.pitestintellij.MyBundle; import javax.swing.*; -import javax.swing.event.HyperlinkEvent; import java.awt.*; public class AnnotationSettingsComponent { @@ -23,7 +20,7 @@ public class AnnotationSettingsComponent { private final JCheckBox autoImportCheckBox; private final JCheckBox enableValidationCheckBox; private static final int EDITOR_HEIGHT = 300; - + private static final String EXAMPLE_JSON = "{\n" + " \"fields\": [\n" + " {\n" + @@ -99,29 +96,29 @@ public AnnotationSettingsComponent() { c.gridx = 0; c.gridy = 0; c.weighty = 0.0; - JLabel importSettingsLabel = new JBLabel("" + MyBundle.message("settings.annotation.import.title") + ""); + JLabel importSettingsLabel = new JBLabel("" + I18nService.getInstance().message("settings.annotation.import.title") + ""); contentPanel.add(importSettingsLabel, c); // Package input - addLabelAndField(contentPanel, MyBundle.message("settings.annotation.package.label"), packageTextField, 1, - MyBundle.message("settings.annotation.package.tooltip")); + addLabelAndField(contentPanel, I18nService.getInstance().message("settings.annotation.package.label"), packageTextField, 1, + I18nService.getInstance().message("settings.annotation.package.tooltip")); // Checkboxes c.gridy = 2; c.gridwidth = 2; - autoImportCheckBox = new JCheckBox(MyBundle.message("settings.annotation.autoImport")); - autoImportCheckBox.setToolTipText(MyBundle.message("settings.annotation.autoImport.tooltip")); + autoImportCheckBox = new JCheckBox(I18nService.getInstance().message("settings.annotation.autoImport")); + autoImportCheckBox.setToolTipText(I18nService.getInstance().message("settings.annotation.autoImport.tooltip")); contentPanel.add(autoImportCheckBox, c); c.gridy = 3; - enableValidationCheckBox = new JCheckBox(MyBundle.message("settings.annotation.enableValidation")); - enableValidationCheckBox.setToolTipText(MyBundle.message("settings.annotation.enableValidation.tooltip")); + enableValidationCheckBox = new JCheckBox(I18nService.getInstance().message("settings.annotation.enableValidation")); + enableValidationCheckBox.setToolTipText(I18nService.getInstance().message("settings.annotation.enableValidation.tooltip")); contentPanel.add(enableValidationCheckBox, c); // Schema Configuration section c.gridy = 4; c.insets = new Insets(15, 5, 5, 5); - JLabel schemaLabel = new JBLabel("" + MyBundle.message("settings.annotation.schema.title") + ""); + JLabel schemaLabel = new JBLabel("" + I18nService.getInstance().message("settings.annotation.schema.title") + ""); contentPanel.add(schemaLabel, c); // Schema editor @@ -132,7 +129,7 @@ public AnnotationSettingsComponent() { // Wrap editor in a panel to ensure it expands properly JPanel editorPanel = new JPanel(new BorderLayout()); - editorPanel.add(new JBLabel(MyBundle.message("settings.annotation.schema.label")), BorderLayout.NORTH); + editorPanel.add(new JBLabel(I18nService.getInstance().message("settings.annotation.schema.label")), BorderLayout.NORTH); editorPanel.add(schemaEditor, BorderLayout.CENTER); contentPanel.add(editorPanel, c); @@ -145,55 +142,33 @@ public AnnotationSettingsComponent() { JPanel helpPanel = new JPanel(new BorderLayout()); helpPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - JLabel helpText = new JBLabel("" + - "

" + MyBundle.message("settings.annotation.schema.help.title") + "

" + - "

" + MyBundle.message("settings.annotation.schema.help.intro") + "

" + - - "

" + MyBundle.message("settings.annotation.schema.help.structure.title") + "

" + - "
    " + - "
  • " + MyBundle.message("settings.annotation.schema.help.structure.1") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.structure.2") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.structure.3") + "
  • " + - "
" + + JLabel helpText = new JBLabel("" + + "

" + I18nService.getInstance().message("settings.annotation.schema.help.title") + "

" + + "

" + I18nService.getInstance().message("settings.annotation.schema.help.intro") + "

" + - "

" + MyBundle.message("settings.annotation.schema.help.validation.title") + "

" + + "

" + I18nService.getInstance().message("settings.annotation.schema.help.structure.title") + "

" + "
    " + - "
  • " + MyBundle.message("settings.annotation.schema.help.validation.1") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.validation.2") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.validation.3") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.validation.4") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.validation.5") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.annotation.schema.help.structure.1") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.annotation.schema.help.structure.2") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.annotation.schema.help.structure.3") + "
  • " + "
" + - "

" + MyBundle.message("settings.annotation.schema.help.valueProvider.title") + "

" + + "

" + I18nService.getInstance().message("settings.annotation.schema.help.validation.title") + "

" + "
    " + - "
  • " + MyBundle.message("settings.annotation.schema.help.valueProvider.1") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.valueProvider.2") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.valueProvider.3") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.annotation.schema.help.validation.1") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.annotation.schema.help.validation.2") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.annotation.schema.help.validation.3") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.annotation.schema.help.validation.4") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.annotation.schema.help.validation.5") + "
  • " + "
" + - "

" + MyBundle.message("settings.annotation.schema.help.example.title") + "

" + - "
" + - EXAMPLE_JSON.replace("<", "<").replace(">", ">").replace("\n", "
").replace(" ", " ") + - "
" + - - "

" + MyBundle.message("settings.annotation.schema.help.notes.title") + "

" + + "

" + I18nService.getInstance().message("settings.annotation.schema.help.valueProvider.title") + "

" + "
    " + - "
  • " + MyBundle.message("settings.annotation.schema.help.notes.1") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.notes.2") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.notes.3") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.notes.4") + "
  • " + - "
  • " + MyBundle.message("settings.annotation.schema.help.notes.5") + "
  • " + + "
  • " + I18nService.getInstance().message("settings.annotation.schema.help.valueProvider.1") + "
  • " + "
" + - ""); - - // Create a scroll pane for the help text - JScrollPane scrollPane = new JScrollPane(helpText); - scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - scrollPane.setBorder(BorderFactory.createEmptyBorder()); + ""); - helpPanel.add(scrollPane, BorderLayout.CENTER); + helpPanel.add(helpText, BorderLayout.CENTER); contentPanel.add(helpPanel, c); // Add content panel to main panel diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/settings/InvalidTestCaseSettingsComponent.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/InvalidTestCaseSettingsComponent.java similarity index 74% rename from src/main/java/com/github/jaksonlin/pitestintellij/settings/InvalidTestCaseSettingsComponent.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/InvalidTestCaseSettingsComponent.java index abcf0e7c..9b58ed9e 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/settings/InvalidTestCaseSettingsComponent.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/InvalidTestCaseSettingsComponent.java @@ -1,12 +1,13 @@ -package com.github.jaksonlin.pitestintellij.settings; +package com.github.jaksonlin.testcraft.presentation.components.configuration; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; import com.intellij.openapi.editor.EditorSettings; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.ui.EditorTextField; import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; import org.jetbrains.annotations.NotNull; -import com.github.jaksonlin.pitestintellij.MyBundle; + import javax.swing.*; import java.awt.*; @@ -36,30 +37,30 @@ public InvalidTestCaseSettingsComponent() { c.gridx = 0; c.gridy = 0; c.weighty = 0.0; - JLabel validationLabel = new JBLabel("" + MyBundle.message("settings.invalidTestCase.title") + ""); + JLabel validationLabel = new JBLabel("" + I18nService.getInstance().message("settings.invalidTestCase.title") + ""); contentPanel.add(validationLabel, c); // Checkboxes c.gridy = 1; - enableCheckbox = new JBCheckBox(MyBundle.message("settings.invalidTestCase.enableCheck")); - enableCheckbox.setToolTipText(MyBundle.message("settings.invalidTestCase.enableCheck.tooltip")); + enableCheckbox = new JBCheckBox(I18nService.getInstance().message("settings.invalidTestCase.enableCheck")); + enableCheckbox.setToolTipText(I18nService.getInstance().message("settings.invalidTestCase.enableCheck.tooltip")); contentPanel.add(enableCheckbox, c); c.gridy = 2; - enableCommentCheckbox = new JBCheckBox(MyBundle.message("settings.invalidTestCase.enableCommentCheck")); - enableCommentCheckbox.setToolTipText(MyBundle.message("settings.invalidTestCase.enableCommentCheck.tooltip")); + enableCommentCheckbox = new JBCheckBox(I18nService.getInstance().message("settings.invalidTestCase.enableCommentCheck")); + enableCommentCheckbox.setToolTipText(I18nService.getInstance().message("settings.invalidTestCase.enableCommentCheck.tooltip")); contentPanel.add(enableCommentCheckbox, c); // Invalid Assertions section c.gridy = 3; c.insets = new Insets(15, 5, 5, 5); - JLabel assertionsLabel = new JBLabel("" + MyBundle.message("settings.invalidTestCase.assertions.title") + ""); + JLabel assertionsLabel = new JBLabel("" + I18nService.getInstance().message("settings.invalidTestCase.assertions.title") + ""); contentPanel.add(assertionsLabel, c); // Editor description c.gridy = 4; c.insets = new Insets(5, 5, 5, 5); - JLabel editorDesc = new JBLabel(MyBundle.message("settings.invalidTestCase.assertions.description")); + JLabel editorDesc = new JBLabel(I18nService.getInstance().message("settings.invalidTestCase.assertions.description")); contentPanel.add(editorDesc, c); // Assertion editor @@ -73,11 +74,11 @@ public InvalidTestCaseSettingsComponent() { c.weighty = 0.0; // Reset vertical weight c.insets = new Insets(10, 5, 5, 5); JLabel helpText = new JBLabel("" + - "" + MyBundle.message("settings.invalidTestCase.assertions.examples.title") + "
" + - MyBundle.message("settings.invalidTestCase.assertions.examples.1") + "
" + - MyBundle.message("settings.invalidTestCase.assertions.examples.2") + "
" + - MyBundle.message("settings.invalidTestCase.assertions.examples.3") + "
" + - MyBundle.message("settings.invalidTestCase.assertions.examples.4") + + "" + I18nService.getInstance().message("settings.invalidTestCase.assertions.examples.title") + "
" + + I18nService.getInstance().message("settings.invalidTestCase.assertions.examples.1") + "
" + + I18nService.getInstance().message("settings.invalidTestCase.assertions.examples.2") + "
" + + I18nService.getInstance().message("settings.invalidTestCase.assertions.examples.3") + "
" + + I18nService.getInstance().message("settings.invalidTestCase.assertions.examples.4") + ""); contentPanel.add(helpText, c); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/settings/OllamaSettingsComponent.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/OllamaSettingsComponent.java similarity index 68% rename from src/main/java/com/github/jaksonlin/pitestintellij/settings/OllamaSettingsComponent.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/OllamaSettingsComponent.java index 09f2f507..3fbfb9f3 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/settings/OllamaSettingsComponent.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/OllamaSettingsComponent.java @@ -1,15 +1,17 @@ -package com.github.jaksonlin.pitestintellij.settings; +package com.github.jaksonlin.testcraft.presentation.components.configuration; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBTextField; import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; -import com.github.jaksonlin.pitestintellij.util.OllamaClient; -import com.github.jaksonlin.pitestintellij.MyBundle; +import com.github.jaksonlin.testcraft.util.OllamaClient; + import javax.swing.*; import java.awt.*; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; + public class OllamaSettingsComponent { private final JPanel mainPanel; private final JBTextField hostField = new JBTextField(); @@ -30,49 +32,49 @@ public OllamaSettingsComponent() { contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); // Connection Settings Section - JPanel connectionPanel = createSectionPanel(MyBundle.message("llm.settings.connection.title")); + JPanel connectionPanel = createSectionPanel(I18nService.getInstance().message("llm.settings.connection.title")); GridBagConstraints c = new GridBagConstraints(); c.insets = JBUI.insets(5); c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.LINE_START; // Host field - addLabelAndField(connectionPanel, MyBundle.message("llm.settings.host.label"), hostField, - MyBundle.message("llm.settings.host.tooltip")); + addLabelAndField(connectionPanel, I18nService.getInstance().message("llm.settings.host.label"), hostField, + I18nService.getInstance().message("llm.settings.host.tooltip")); // Port field - addLabelAndField(connectionPanel, MyBundle.message("llm.settings.port.label"), portField, - MyBundle.message("llm.settings.port.tooltip")); + addLabelAndField(connectionPanel, I18nService.getInstance().message("llm.settings.port.label"), portField, + I18nService.getInstance().message("llm.settings.port.tooltip")); contentPanel.add(connectionPanel); contentPanel.add(Box.createVerticalStrut(10)); // Model Settings Section - JPanel modelPanel = createSectionPanel(MyBundle.message("llm.settings.model.title")); + JPanel modelPanel = createSectionPanel(I18nService.getInstance().message("llm.settings.model.title")); // Model field - addLabelAndField(modelPanel, MyBundle.message("llm.settings.model.label"), modelField, - MyBundle.message("llm.settings.model.tooltip")); + addLabelAndField(modelPanel, I18nService.getInstance().message("llm.settings.model.label"), modelField, + I18nService.getInstance().message("llm.settings.model.tooltip")); // Max Tokens field - addLabelAndField(modelPanel, MyBundle.message("llm.settings.maxTokens.label"), maxTokensField, - MyBundle.message("llm.settings.maxTokens.tooltip")); + addLabelAndField(modelPanel, I18nService.getInstance().message("llm.settings.maxTokens.label"), maxTokensField, + I18nService.getInstance().message("llm.settings.maxTokens.tooltip")); // Temperature field - addLabelAndField(modelPanel, MyBundle.message("llm.settings.temperature.label"), temperatureField, - MyBundle.message("llm.settings.temperature.tooltip")); + addLabelAndField(modelPanel, I18nService.getInstance().message("llm.settings.temperature.label"), temperatureField, + I18nService.getInstance().message("llm.settings.temperature.tooltip")); // Timeout field - addLabelAndField(modelPanel, MyBundle.message("llm.settings.timeout.label"), timeoutField, - MyBundle.message("llm.settings.timeout.tooltip")); + addLabelAndField(modelPanel, I18nService.getInstance().message("llm.settings.timeout.label"), timeoutField, + I18nService.getInstance().message("llm.settings.timeout.tooltip")); contentPanel.add(modelPanel); contentPanel.add(Box.createVerticalStrut(10)); // Output Settings Section - JPanel outputPanel = createSectionPanel(MyBundle.message("llm.settings.output.title")); - copyAsMarkdownCheckbox = new JCheckBox(MyBundle.message("llm.settings.copyMarkdown.label")); - copyAsMarkdownCheckbox.setToolTipText(MyBundle.message("llm.settings.copyMarkdown.tooltip")); + JPanel outputPanel = createSectionPanel(I18nService.getInstance().message("llm.settings.output.title")); + copyAsMarkdownCheckbox = new JCheckBox(I18nService.getInstance().message("llm.settings.copyMarkdown.label")); + copyAsMarkdownCheckbox.setToolTipText(I18nService.getInstance().message("llm.settings.copyMarkdown.tooltip")); copyAsMarkdownCheckbox.setAlignmentX(Component.LEFT_ALIGNMENT); outputPanel.add(copyAsMarkdownCheckbox); @@ -80,18 +82,18 @@ public OllamaSettingsComponent() { contentPanel.add(Box.createVerticalStrut(10)); // Test Connection Section - JPanel testPanel = createSectionPanel(MyBundle.message("llm.settings.test.title")); - JButton testConnectionButton = new JButton(MyBundle.message("llm.settings.test.button")); + JPanel testPanel = createSectionPanel(I18nService.getInstance().message("llm.settings.test.title")); + JButton testConnectionButton = new JButton(I18nService.getInstance().message("llm.settings.test.button")); testConnectionButton.setAlignmentX(Component.LEFT_ALIGNMENT); testPanel.add(testConnectionButton); // Add help text JLabel helpText = new JBLabel("" + - "

" + MyBundle.message("llm.settings.help.title") + "

" + + "

" + I18nService.getInstance().message("llm.settings.help.title") + "

" + "
    " + - "
  • " + MyBundle.message("llm.settings.help.running") + "
  • " + - "
  • " + MyBundle.message("llm.settings.help.host") + "
  • " + - "
  • " + MyBundle.message("llm.settings.help.port") + "
  • " + + "
  • " + I18nService.getInstance().message("llm.settings.help.running") + "
  • " + + "
  • " + I18nService.getInstance().message("llm.settings.help.host") + "
  • " + + "
  • " + I18nService.getInstance().message("llm.settings.help.port") + "
  • " + "
" + ""); helpText.setAlignmentX(Component.LEFT_ALIGNMENT); @@ -152,18 +154,18 @@ private void testConnection() { if (success) { JOptionPane.showMessageDialog(mainPanel, "Successfully connected to Ollama server!", - MyBundle.message("llm.settings.test.title"), + I18nService.getInstance().message("llm.settings.test.title"), JOptionPane.INFORMATION_MESSAGE); } else { JOptionPane.showMessageDialog(mainPanel, - MyBundle.message("llm.error.connection"), - MyBundle.message("llm.settings.test.title"), + I18nService.getInstance().message("llm.error.connection"), + I18nService.getInstance().message("llm.settings.test.title"), JOptionPane.ERROR_MESSAGE); } } catch (Exception e) { JOptionPane.showMessageDialog(mainPanel, - MyBundle.message("llm.error.connection") + ": " + e.getMessage(), - MyBundle.message("llm.settings.test.title"), + I18nService.getInstance().message("llm.error.connection") + ": " + e.getMessage(), + I18nService.getInstance().message("llm.settings.test.title"), JOptionPane.ERROR_MESSAGE); } } diff --git a/src/main/java/com/github/jaksonlin/testcraft/presentation/components/llmchat/ChatPanelComponent.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/llmchat/ChatPanelComponent.java new file mode 100644 index 00000000..a0c32568 --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/llmchat/ChatPanelComponent.java @@ -0,0 +1,102 @@ +package com.github.jaksonlin.testcraft.presentation.components.llmchat; + +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.ChatEvent; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.TypedEventObserver; +import com.github.jaksonlin.testcraft.infrastructure.services.system.EventBusService; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.util.ui.JBUI; + +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import java.awt.BorderLayout; +import java.awt.event.KeyEvent; +import java.awt.event.KeyAdapter; + +public class ChatPanelComponent { + + private final TypedEventObserver chatObserver = new TypedEventObserver(ChatEvent.class) { + @Override + protected void onTypedEvent(ChatEvent event) { + // Handle the chat event + switch (event.getEventType()) { + case ChatEvent.START_LOADING: + // disable input area and send button + setInputEnabled(false); + break; + case ChatEvent.STOP_LOADING: + case ChatEvent.CHAT_RESPONSE: + case ChatEvent.ERROR: + // enable input area and send button + setInputEnabled(true); + break; + } + } + }; + private final JTextArea inputArea; + private final JButton sendButton; + private final JPanel masterPanel; + + public ChatPanelComponent() { + + // Input area + inputArea = new JTextArea(3, 40); + inputArea.setLineWrap(true); + inputArea.setWrapStyleWord(true); + JBScrollPane inputScrollPane = new JBScrollPane(inputArea); + + // Send button + sendButton = new JButton(I18nService.getInstance().message("chat.send.button")); + sendButton.addActionListener(e -> sendMessage()); + + // Input panel (input area + send button) + masterPanel = new JPanel(new BorderLayout()); + masterPanel.setBorder(JBUI.Borders.empty(5)); + masterPanel.add(inputScrollPane, BorderLayout.CENTER); + masterPanel.add(sendButton, BorderLayout.EAST); + + // Add key listener for Ctrl+Enter to send + inputArea.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER && e.isControlDown()) { + sendMessage(); + e.consume(); + } + } + }); + + + } + + + public JPanel getInputPanel() { + return masterPanel; + } + + private void sendMessage() { + String message = inputArea.getText().trim(); + if (!message.isEmpty()) { + EventBusService.getInstance().post(new ChatEvent(ChatEvent.CHAT_REQUEST, message)); + inputArea.setText(""); + inputArea.requestFocus(); + } + } + + public void clear() { + inputArea.setText(""); + } + + public void setInputEnabled(boolean enabled) { + inputArea.setEnabled(enabled); + sendButton.setEnabled(enabled); + if (enabled) { + inputArea.requestFocus(); + } + } + + public void dispose() { + chatObserver.unregister(); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/testcraft/presentation/components/llmchat/LLMResponseComponent.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/llmchat/LLMResponseComponent.java new file mode 100644 index 00000000..49451490 --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/llmchat/LLMResponseComponent.java @@ -0,0 +1,341 @@ +package com.github.jaksonlin.testcraft.presentation.components.llmchat; + +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.ChatEvent; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.LLMConfigEvent; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.TypedEventObserver; +import com.github.jaksonlin.testcraft.infrastructure.services.system.EventBusService; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.util.ui.JBUI; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import com.intellij.ui.JBColor; +import java.awt.datatransfer.StringSelection; +import java.awt.Toolkit; + +import javax.swing.*; +import javax.swing.text.html.HTMLEditorKit; +import javax.swing.text.html.StyleSheet; +import java.awt.*; +import java.util.Timer; +import java.util.TimerTask; +import javax.swing.text.html.HTMLDocument; + +public class LLMResponseComponent { + private final TypedEventObserver eventObserver = new TypedEventObserver(ChatEvent.class) { + @Override + public void onTypedEvent(ChatEvent event) { + switch (event.getEventType()) { + case ChatEvent.START_LOADING: + startLoading(); + break; + case ChatEvent.STOP_LOADING: + stopLoading(); + break; + case ChatEvent.CHAT_REQUEST: + if (!isLoading) { + appendMarkdownToOutput(String.format(MESSAGE_TEMPLATE, "user", I18nService.getInstance().message("llm.user"), event.getPayload().toString())); + startLoading(); + } + break; + case ChatEvent.CHAT_RESPONSE: + stopLoading(); + appendMarkdownToOutput(String.format(MESSAGE_TEMPLATE, "assistant", I18nService.getInstance().message("llm.assistant"), event.getPayload().toString())); + break; + case ChatEvent.COPY_CHAT_RESPONSE: + copyToClipboard(event.getPayload()); + break; + case ChatEvent.DRY_RUN_PROMPT: + String dryRunPrompt = (String) event.getPayload(); + if (dryRunPrompt.isEmpty()){ + JOptionPane.showMessageDialog(masterPanel, I18nService.getInstance().message("llm.dry.run.prompt.empty"), I18nService.getInstance().message("llm.dry.run.prompt"), JOptionPane.INFORMATION_MESSAGE); + } else { + appendMarkdownToOutput(String.format(MESSAGE_TEMPLATE, "system", I18nService.getInstance().message("llm.system"), I18nService.getInstance().message("llm.dry.run.prompt") + "\n" + event.getPayload().toString())); + } + break; + case ChatEvent.ERROR: + JOptionPane.showMessageDialog(masterPanel, I18nService.getInstance().message("llm.error") + ": " + event.getPayload().toString(), I18nService.getInstance().message("llm.error"), JOptionPane.ERROR_MESSAGE); + break; + } + } + }; + + private final TypedEventObserver llmConfigObserver = new TypedEventObserver(LLMConfigEvent.class) { + @Override + public void onTypedEvent(LLMConfigEvent event) { + switch (event.getEventType()) { + case LLMConfigEvent.CONFIG_CHANGE_COPY_AS_MARKDOWN: + copyAsMarkdown = (boolean) event.getPayload(); + break; + } + } + }; + private final JEditorPane outputArea; + private boolean isLoading = false; + private boolean copyAsMarkdown = false; + + private final HTMLEditorKit htmlKit; + private final StyleSheet styleSheet; + private final JPanel masterPanel; + private final JPanel loadingPanel; + private final Timer loadingTimer; + private final JLabel loadingLabel; + + + private final StringBuilder chatHistory = new StringBuilder(); + + public JPanel getMasterPanel() { + return masterPanel; + } + + + private static final String BASE_HTML_TEMPLATE = + "\n" + + "\n" + + "\n" + + " \n" + + "\n" + + "\n" + + "
\n" + + " %s\n" + + "
\n" + + "\n" + + ""; + + private static final String MESSAGE_TEMPLATE = + "
\n" + + "
%s
\n" + + "
%s
\n" + + "
\n" + + "
"; + + + public void notifyClearButtonClick() { + EventBusService.getInstance().post(new ChatEvent(ChatEvent.CLEAR_CHAT, null)); + } + + public void notifyCopyButtonClick() { + EventBusService.getInstance().post(new ChatEvent(ChatEvent.REQUEST_COPY_CHAT_RESPONSE, null)); + } + + public LLMResponseComponent(ChatPanelComponent chatPanelComponent) { + masterPanel = new JPanel(new BorderLayout()); + masterPanel.setBorder(JBUI.Borders.empty(10)); + + + // Setup improved JEditorPane for HTML rendering + outputArea = new JEditorPane(); + outputArea.setEditable(false); + outputArea.setContentType("text/html"); + + // Configure HTML editor kit with custom style sheet + htmlKit = new HTMLEditorKit(); + styleSheet = htmlKit.getStyleSheet(); + styleSheet.addRule(getCodeStyle()); + outputArea.setEditorKit(htmlKit); + + // Initialize with empty document + HTMLDocument doc = (HTMLDocument) htmlKit.createDefaultDocument(); + outputArea.setDocument(doc); + + // Enable proper HTML rendering + outputArea.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); + outputArea.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13)); + + // Create loading panel + loadingPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + loadingPanel.setVisible(false); + loadingLabel = new JLabel(I18nService.getInstance().message("llm.thinking")); + loadingPanel.add(loadingLabel); + loadingPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10)); + loadingPanel.setBackground(JBColor.background()); + + // Setup loading animation timer + loadingTimer = new Timer(); + + JBScrollPane outputScrollPane = new JBScrollPane(outputArea); + + // Create toolbar + JToolBar toolbar = new JToolBar(); + toolbar.setFloatable(false); + toolbar.setBorder(JBUI.Borders.empty(2, 2)); + + JButton copyButton = new JButton(I18nService.getInstance().message("llm.copy.to.clipboard")); + copyButton.addActionListener(e -> { + notifyCopyButtonClick(); + }); + toolbar.add(copyButton); + + JButton clearButton = new JButton(I18nService.getInstance().message("llm.clear")); + clearButton.addActionListener(e -> { + clearOutput(); + notifyClearButtonClick(); + }); + toolbar.add(clearButton); + + // Create input panel at the bottom + JPanel inputPanel = new JPanel(new BorderLayout()); + inputPanel.add(chatPanelComponent.getInputPanel(), BorderLayout.CENTER); + + // Create center panel to hold toolbar, output area, and loading panel + JPanel centerPanel = new JPanel(new BorderLayout()); + centerPanel.add(toolbar, BorderLayout.NORTH); + centerPanel.add(outputScrollPane, BorderLayout.CENTER); + centerPanel.add(loadingPanel, BorderLayout.SOUTH); + + // Add components + masterPanel.add(centerPanel, BorderLayout.CENTER); + masterPanel.add(inputPanel, BorderLayout.SOUTH); + + + // Initialize with empty chat container + updateOutputArea(); + } + + private String getCodeStyle() { + boolean isDarkTheme = !JBColor.isBright(); + String backgroundColor = isDarkTheme ? "#2b2d30" : "#fafafa"; + String textColor = isDarkTheme ? "#bababa" : "#2b2b2b"; + String codeBackground = isDarkTheme ? "#1e1f22" : "#f6f8fa"; + String codeBorder = isDarkTheme ? "#1e1f22" : "#e1e4e8"; + String linkColor = isDarkTheme ? "#589df6" : "#2470B3"; + String separatorColor = isDarkTheme ? "#3c3f41" : "#e0e0e0"; + + return "body { " + + "font-family: Arial, sans-serif; " + + "font-size: 13pt; " + + "margin: 10px; " + + "background-color: " + backgroundColor + "; " + + "color: " + textColor + "; " + + "}\n" + + ".message { " + + "padding: 12px; " + + "margin: 5px 0; " + + "}\n" + + ".message.user { " + + "background-color: " + (isDarkTheme ? "#2d2d2d" : "#e3f2fd") + "; " + + "margin-left: 20%; " + + "}\n" + + ".message.assistant { " + + "background-color: " + (isDarkTheme ? "#1e1e1e" : "#f5f5f5") + "; " + + "margin-right: 20%; " + + "}\n" + + ".message.system { " + + "background-color: " + (isDarkTheme ? "#2d2d2d" : "#e8f5e9") + "; " + + "text-align: center; " + + "font-style: italic; " + + "}\n" + + ".message-header { " + + "font-weight: bold; " + + "margin-bottom: 4px; " + + "}\n" + + ".message-content { " + + "white-space: pre-wrap; " + + "}\n" + + ".message-separator { " + + "border-top: 1px solid " + separatorColor + "; " + + "margin: 10px 0; " + + "}\n" + + "pre { " + + "background-color: " + codeBackground + "; " + + "padding: 16px; " + + "margin: 1em 0; " + + "border: 1px solid " + codeBorder + "; " + + "}\n" + + "code { " + + "font-family: monospace; " + + "font-size: 12pt; " + + "}\n" + + "a { color: " + linkColor + "; }"; + } + + private String repeatString(String str, int count) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < count; i++) { + sb.append(str); + } + return sb.toString(); + } + + private void startLoading() { + isLoading = true; + loadingPanel.setVisible(true); + + // Start the loading animation + loadingTimer.scheduleAtFixedRate(new TimerTask() { + private int dots = 0; + @Override + public void run() { + if (!isLoading) { + cancel(); + return; + } + SwingUtilities.invokeLater(() -> { + dots = (dots + 1) % 4; + loadingLabel.setText(I18nService.getInstance().message("llm.thinking") + repeatString(".", dots)); + }); + } + }, 0, 500); + + } + + private void stopLoading() { + if (!isLoading) { + return; // Prevent multiple stop calls + } + isLoading = false; + loadingPanel.setVisible(false); + loadingTimer.purge(); + } + + private void updateOutputArea() { + String fullHtml = String.format(BASE_HTML_TEMPLATE, getCodeStyle(), chatHistory.toString()); + SwingUtilities.invokeLater(() -> { + try { + // Create a new document each time to avoid state issues + HTMLDocument doc = (HTMLDocument) htmlKit.createDefaultDocument(); + // Set the document first + outputArea.setDocument(doc); + // Then insert the content + htmlKit.insertHTML(doc, 0, fullHtml, 0, 0, null); + outputArea.setCaretPosition(doc.getLength()); + } catch (Exception e) { + // Fallback to setText if something goes wrong + outputArea.setText(fullHtml); + } + }); + } + + private void appendMarkdownToOutput(String markdown) { + String htmlContent = convertMarkdownToHtml(markdown); + chatHistory.append(htmlContent); + updateOutputArea(); + } + + private String convertMarkdownToHtml(String markdown) { + Parser parser = Parser.builder().build(); + Node document = parser.parse(markdown); + HtmlRenderer renderer = HtmlRenderer.builder().build(); + return renderer.render(document); + } + + private void clearOutput() { + chatHistory.setLength(0); + updateOutputArea(); + } + + private void copyToClipboard(Object eventObj) { + String currentContentToCopy; + + if (copyAsMarkdown) { + currentContentToCopy = eventObj.toString(); + } else { + currentContentToCopy = outputArea.getText(); + } + StringSelection selection = new StringSelection(currentContentToCopy); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection); + } +} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/components/LLMSuggestionUIComponent.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/llmchat/LLMSuggestionUIComponent.java similarity index 63% rename from src/main/java/com/github/jaksonlin/pitestintellij/components/LLMSuggestionUIComponent.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/components/llmchat/LLMSuggestionUIComponent.java index 9f97bb81..061209d6 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/components/LLMSuggestionUIComponent.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/llmchat/LLMSuggestionUIComponent.java @@ -1,97 +1,82 @@ -package com.github.jaksonlin.pitestintellij.components; - -import com.github.jaksonlin.pitestintellij.context.PitestContext; -import com.github.jaksonlin.pitestintellij.observers.BasicEventObserver; -import com.github.jaksonlin.pitestintellij.viewmodels.LLMSuggestionUIComponentViewModel; +package com.github.jaksonlin.testcraft.presentation.components.llmchat; + +import com.github.jaksonlin.testcraft.domain.context.PitestContext; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.RunHistoryEvent; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.ChatEvent; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.TypedEventObserver; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; +import com.github.jaksonlin.testcraft.presentation.viewmodels.LLMSuggestionUIComponentViewModel; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.ComboBox; import com.intellij.util.ui.JBUI; -import com.intellij.AbstractBundle; -import org.jetbrains.annotations.PropertyKey; -import javax.swing.*; +import javax.swing.JPanel; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JTextField; +import javax.swing.JComboBox; import javax.swing.plaf.basic.BasicComboBoxEditor; -import java.awt.*; +import javax.swing.DefaultComboBoxModel; +import javax.swing.ComboBoxEditor; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; + import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.ResourceBundle; -import com.github.jaksonlin.pitestintellij.services.LLMService; -public class LLMSuggestionUIComponent implements BasicEventObserver { - private static final String BUNDLE = "messages.MyBundle"; - private static ResourceBundle ourBundle; - - public static String message(@PropertyKey(resourceBundle = BUNDLE) String key, Object... params) { - return AbstractBundle.message(getBundle(), key, params); - } - - private static ResourceBundle getBundle() { - if (ourBundle == null) { - ourBundle = ResourceBundle.getBundle(BUNDLE); +public class LLMSuggestionUIComponent { + + private final TypedEventObserver chatEventObserver = new TypedEventObserver(ChatEvent.class) { + @Override + public void onTypedEvent(ChatEvent event) { + switch (event.getEventType()) { + case ChatEvent.START_LOADING: + generateButton.setEnabled(false); + dryRunButton.setEnabled(false); + break; + case ChatEvent.STOP_LOADING: + case ChatEvent.CHAT_RESPONSE: + case ChatEvent.ERROR: + generateButton.setEnabled(true); + dryRunButton.setEnabled(true); + break; + } } - return ourBundle; - } + }; + private final TypedEventObserver runHistoryEventObserver = new TypedEventObserver(RunHistoryEvent.class) { + @Override + public void onTypedEvent(RunHistoryEvent event) { + switch (event.getEventType()) { + case RunHistoryEvent.RUN_HISTORY_LIST: + ApplicationManager.getApplication().invokeLater(() -> loadFileHistory(event.getPayload())); + break; + } + } + };; + - private final LLMSuggestionUIComponentViewModel viewModel; - private final ChatPanel chatPanel = new ChatPanel(); - private final LLMResponsePanel responsePanel = new LLMResponsePanel(chatPanel); + private final ChatPanelComponent chatPanel = new ChatPanelComponent(); + private final LLMResponseComponent responsePanel = new LLMResponseComponent(chatPanel); + + private final LLMSuggestionUIComponentViewModel viewModel = new LLMSuggestionUIComponentViewModel(); + private final JPanel mainPanel = new JPanel(new BorderLayout()); private final DefaultComboBoxModel fileListModel = new DefaultComboBoxModel<>(); private final JComboBox fileSelector = new ComboBox<>(fileListModel); - private final JButton generateButton = new JButton(message("llm.generate.suggestions")); - private final JButton dryRunButton = new JButton(message("llm.check.prompt")); + private final JButton generateButton = new JButton(I18nService.getInstance().message("llm.generate.suggestions")); + private final JButton dryRunButton = new JButton(I18nService.getInstance().message("llm.check.prompt")); private List allFileItems = new ArrayList<>(); - public LLMSuggestionUIComponent(LLMService llmService) { + public LLMSuggestionUIComponent() { setupUI(); - // setup message routing - viewModel = new LLMSuggestionUIComponentViewModel(llmService); - viewModel.addObserver(this); - viewModel.addObserver(responsePanel); - - // add the chatPanel to the mainPanel - chatPanel.addListener(message -> { - viewModel.handleChatMessage(message); - }); - - // add the reponse listener to the responsePanel - responsePanel.addResponseActionListener(new LLMResponsePanel.ResponseActionListener() { - @Override - public void onClearButtonClick() { - viewModel.clearChat(); - } - - @Override - public void onCopyButtonClick() { - viewModel.copyChat(); - } - }); - + // propagate the config change viewModel.propagateConfigChange(); - } - - @Override - public void onEventHappen(String eventName, Object eventObj) { - switch (eventName) { - case "START_LOADING": - generateButton.setEnabled(false); - dryRunButton.setEnabled(false); - break; - case "STOP_LOADING": - generateButton.setEnabled(true); - dryRunButton.setEnabled(true); - break; - case "RUN_HISTORY_LIST": - ApplicationManager.getApplication().invokeLater(() -> loadFileHistory(eventObj)); - break; - default: - break; - } - } public JPanel getPanel() { return this.mainPanel; @@ -145,14 +130,14 @@ public Component getEditorComponent() { // Add components to top panel JPanel selectorPanel = new JPanel(new BorderLayout()); - selectorPanel.add(new JLabel(message("llm.select.file") + ": "), BorderLayout.WEST); + selectorPanel.add(new JLabel(I18nService.getInstance().message("llm.select.file") + ": "), BorderLayout.WEST); selectorPanel.add(fileSelector, BorderLayout.CENTER); topPanel.add(selectorPanel, BorderLayout.CENTER); topPanel.add(buttonPanel, BorderLayout.EAST); // Add panels to main panel mainPanel.add(topPanel, BorderLayout.NORTH); - mainPanel.add(responsePanel, BorderLayout.CENTER); + mainPanel.add(responsePanel.getMasterPanel(), BorderLayout.CENTER); } diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/components/MutationToolWindowUIComponent.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/mutation/MutationToolWindowUIComponent.java similarity index 87% rename from src/main/java/com/github/jaksonlin/pitestintellij/components/MutationToolWindowUIComponent.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/components/mutation/MutationToolWindowUIComponent.java index 38bd9818..dddfc08c 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/components/MutationToolWindowUIComponent.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/mutation/MutationToolWindowUIComponent.java @@ -1,7 +1,7 @@ -package com.github.jaksonlin.pitestintellij.components; +package com.github.jaksonlin.testcraft.presentation.components.mutation; -import com.github.jaksonlin.pitestintellij.MyBundle; -import com.github.jaksonlin.pitestintellij.viewmodels.MutationToolWindowViewModel; + +import com.github.jaksonlin.testcraft.presentation.viewmodels.MutationToolWindowViewModel; import com.intellij.openapi.project.Project; import javax.swing.*; @@ -13,8 +13,10 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; + public class MutationToolWindowUIComponent { - private final JButton clearButton = new JButton(MyBundle.message("clear.button")); + private final JButton clearButton = new JButton(I18nService.getInstance().message("clear.button")); private final JTextField searchInput = new JTextField(20); protected final ObservableTree resultTree = new ObservableTree(); private final MutationToolWindowViewModel vm; @@ -24,7 +26,7 @@ public MutationToolWindowUIComponent(Project project) { this.vm = new MutationToolWindowViewModel(project, resultTree); this.toolWindowPanel = createToolWindowPanel(); registerListeners(); - searchInput.setToolTipText(MyBundle.message("search.placeholder")); + searchInput.setToolTipText(I18nService.getInstance().message("search.placeholder")); } private void registerListeners() { diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/components/ObservableTree.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/mutation/ObservableTree.java similarity index 65% rename from src/main/java/com/github/jaksonlin/pitestintellij/components/ObservableTree.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/components/mutation/ObservableTree.java index 08548811..9c6af23d 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/components/ObservableTree.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/mutation/ObservableTree.java @@ -1,8 +1,9 @@ -package com.github.jaksonlin.pitestintellij.components; +package com.github.jaksonlin.testcraft.presentation.components.mutation; -import com.github.jaksonlin.pitestintellij.MyBundle; -import com.github.jaksonlin.pitestintellij.observers.BasicEventObserver; -import com.github.jaksonlin.pitestintellij.util.Pair; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.RunHistoryEvent; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.TypedEventObserver; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; +import com.github.jaksonlin.testcraft.util.Pair; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -13,30 +14,34 @@ import java.util.List; import java.util.Objects; -public class ObservableTree extends JTree implements BasicEventObserver { +public class ObservableTree extends JTree { - @Override - public void onEventHappen(String eventName,Object eventObj) { - if (!eventName.equals("RUN_HISTORY")) { - return; - } - if (eventObj == null) { - initializeMutationTree(Collections.emptyList()); - } else if (eventObj instanceof Pair) { - Pair pair = (Pair) eventObj; - if (pair.getFirst() instanceof String && pair.getSecond() instanceof String) { - updateMutationTree(new Pair<>((String) pair.getFirst(), (String) pair.getSecond())); + private final TypedEventObserver eventObserver = new TypedEventObserver(RunHistoryEvent.class) { + @Override + public void onTypedEvent(RunHistoryEvent event) { + if (!event.getEventType().equals(RunHistoryEvent.RUN_HISTORY)) { + return; } - } else if (eventObj instanceof List) { - List list = (List) eventObj; - if (list.isEmpty()) { + if (event.getPayload() == null) { initializeMutationTree(Collections.emptyList()); - } else if (list.get(0) instanceof Pair) { - List> nodeList = (List>) list; - initializeMutationTree(nodeList); + } else if (event.getPayload() instanceof Pair) { + Pair pair = (Pair) event.getPayload(); + if (pair.getFirst() instanceof String && pair.getSecond() instanceof String) { + updateMutationTree(new Pair<>((String) pair.getFirst(), (String) pair.getSecond())); + } + } else if (event.getPayload() instanceof List) { + List list = (List) event.getPayload(); + if (list.isEmpty()) { + initializeMutationTree(Collections.emptyList()); + } else if (list.get(0) instanceof Pair) { + List> nodeList = (List>) list; + initializeMutationTree(nodeList); + } } } - } + }; + + private void initializeMutationTree(@NotNull List> nodeNameList) { DefaultTreeModel treeModel = buildTreeModel(nodeNameList); @@ -44,7 +49,7 @@ private void initializeMutationTree(@NotNull List> nodeName } private DefaultTreeModel buildTreeModel(@NotNull List> nodeNameList) { - DefaultMutableTreeNode root = new DefaultMutableTreeNode(MyBundle.message("mutation.tree.root")); + DefaultMutableTreeNode root = new DefaultMutableTreeNode(I18nService.getInstance().message("mutation.tree.root")); for (Pair pair : nodeNameList) { String packageName = pair.getFirst(); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/components/PitestOutputDialog.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/mutation/PitestOutputDialog.java similarity index 87% rename from src/main/java/com/github/jaksonlin/pitestintellij/components/PitestOutputDialog.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/components/mutation/PitestOutputDialog.java index aa696e95..cc4b14a4 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/components/PitestOutputDialog.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/mutation/PitestOutputDialog.java @@ -1,5 +1,6 @@ -package com.github.jaksonlin.pitestintellij.components; +package com.github.jaksonlin.testcraft.presentation.components.mutation; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; @@ -38,7 +39,7 @@ protected JComponent createCenterPanel() { panel.add(scrollPane, BorderLayout.CENTER); if (reportFile != null) { - JButton viewReportButton = new JButton("HTML Report"); + JButton viewReportButton = new JButton(I18nService.getInstance().message("pitest.view.report")); viewReportButton.addActionListener(e -> { try { Desktop.getDesktop().browse(reportFile.toURI()); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/components/PremiumSettingsComponent.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/premium/PremiumSettingsComponent.java similarity index 84% rename from src/main/java/com/github/jaksonlin/pitestintellij/components/PremiumSettingsComponent.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/components/premium/PremiumSettingsComponent.java index 1369504b..6fe8e2b4 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/components/PremiumSettingsComponent.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/premium/PremiumSettingsComponent.java @@ -1,6 +1,6 @@ -package com.github.jaksonlin.pitestintellij.components; +package com.github.jaksonlin.testcraft.presentation.components.premium; -import com.github.jaksonlin.pitestintellij.license.PremiumManager; +import com.github.jaksonlin.testcraft.infrastructure.license.PremiumManager; import javax.swing.*; diff --git a/src/main/java/com/github/jaksonlin/testcraft/presentation/components/testannotation/AnnotationClassSelectionComponent.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/testannotation/AnnotationClassSelectionComponent.java new file mode 100644 index 00000000..fb6da0c3 --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/components/testannotation/AnnotationClassSelectionComponent.java @@ -0,0 +1,4 @@ +package com.github.jaksonlin.testcraft.presentation.components.testannotation; + +public class AnnotationClassSelectionComponent { +} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/toolWindow/LLMSuggestionsToolWindowFactory.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/toolWindow/LLMSuggestionsToolWindowFactory.java similarity index 64% rename from src/main/java/com/github/jaksonlin/pitestintellij/toolWindow/LLMSuggestionsToolWindowFactory.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/toolWindow/LLMSuggestionsToolWindowFactory.java index 36d6a437..7a8d0d62 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/toolWindow/LLMSuggestionsToolWindowFactory.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/toolWindow/LLMSuggestionsToolWindowFactory.java @@ -1,9 +1,7 @@ -package com.github.jaksonlin.pitestintellij.toolWindow; +package com.github.jaksonlin.testcraft.presentation.toolWindow; -import com.github.jaksonlin.pitestintellij.components.LLMSuggestionUIComponent; -import com.github.jaksonlin.pitestintellij.services.LLMService; -import com.github.jaksonlin.pitestintellij.services.RunHistoryManagerService; -import com.intellij.openapi.application.ApplicationManager; +import com.github.jaksonlin.testcraft.presentation.components.llmchat.LLMSuggestionUIComponent; +import com.github.jaksonlin.testcraft.infrastructure.services.business.RunHistoryManagerService; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowFactory; @@ -18,16 +16,14 @@ public class LLMSuggestionsToolWindowFactory implements ToolWindowFactory { @Override public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { // create a new LLMSuggestionUIComponent - LLMService llmService = ApplicationManager.getApplication().getService(LLMService.class); - LLMSuggestionUIComponent uiComponent = new LLMSuggestionUIComponent(llmService); + LLMSuggestionUIComponent uiComponent = new LLMSuggestionUIComponent(); // add the uiComponent to the toolWindow through the content manager JPanel toolWindowPanel = uiComponent.getPanel(); ContentManager contentManager = toolWindow.getContentManager(); Content content = new ContentImpl(toolWindowPanel, "TestCraft - LLM Suggestions Tool Window", false); // Directly create ContentImpl contentManager.addContent(content); // register the uiComponent to the runHistoryManagerService to sync the run history - RunHistoryManagerService runHistoryManagerService = project.getService(RunHistoryManagerService.class); - runHistoryManagerService.addObserver(uiComponent); + RunHistoryManagerService.getInstance().addObserver(uiComponent); } } \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/toolWindow/MutationToolWindowFactory.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/toolWindow/MutationToolWindowFactory.java similarity index 85% rename from src/main/java/com/github/jaksonlin/pitestintellij/toolWindow/MutationToolWindowFactory.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/toolWindow/MutationToolWindowFactory.java index 6565f88a..3f45fe69 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/toolWindow/MutationToolWindowFactory.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/toolWindow/MutationToolWindowFactory.java @@ -1,6 +1,6 @@ -package com.github.jaksonlin.pitestintellij.toolWindow; +package com.github.jaksonlin.testcraft.presentation.toolWindow; -import com.github.jaksonlin.pitestintellij.components.MutationToolWindowUIComponent; +import com.github.jaksonlin.testcraft.presentation.components.mutation.MutationToolWindowUIComponent; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowFactory; diff --git a/src/main/java/com/github/jaksonlin/testcraft/presentation/viewmodels/LLMSuggestionUIComponentViewModel.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/viewmodels/LLMSuggestionUIComponentViewModel.java new file mode 100644 index 00000000..52e95e5b --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/viewmodels/LLMSuggestionUIComponentViewModel.java @@ -0,0 +1,52 @@ +package com.github.jaksonlin.testcraft.presentation.viewmodels; + +import com.github.jaksonlin.testcraft.domain.context.PitestContext; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.ChatEvent; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.TypedEventObserver; +import com.github.jaksonlin.testcraft.infrastructure.services.config.LLMConfigService; +import com.github.jaksonlin.testcraft.infrastructure.services.system.EventBusService; + + +public class LLMSuggestionUIComponentViewModel { + private final LLMConfigService llmConfigService = LLMConfigService.getInstance(); + private final EventBusService eventBusService = EventBusService.getInstance(); + + private final TypedEventObserver chatObserver = new TypedEventObserver(ChatEvent.class) { + @Override + public void onTypedEvent(ChatEvent event) { + switch (event.getEventType()) { + case ChatEvent.REQUEST_COPY_CHAT_RESPONSE: + String chatHistory = llmConfigService.getChatHistory(); + // when chatHistory is empty, use the lastDryRunPrompt + if (chatHistory.isEmpty()) { + chatHistory = lastDryRunPrompt; + } + eventBusService.post(new ChatEvent(ChatEvent.COPY_CHAT_RESPONSE, chatHistory)); + break; + default: + break; + } + } + }; + + public void propagateConfigChange() { + llmConfigService.propagateConfigChange(); + } + + + public void generateSuggestions(PitestContext context) { + eventBusService.post(new ChatEvent(ChatEvent.START_LOADING, null)); + llmConfigService.generateUnittestRequest(context.getTestFilePath(), context.getTargetClassFilePath(), context.getMutationResults()); + } + + private String lastDryRunPrompt = ""; + + public void dryRunGetPrompt(PitestContext context) { + lastDryRunPrompt = llmConfigService.dryRunGetPrompt(context.getFullyQualifiedTargetTestClassName(), context.getTargetClassFullyQualifiedName(), context.getMutationResults()); + eventBusService.post(new ChatEvent(ChatEvent.DRY_RUN_PROMPT, lastDryRunPrompt)); + } + + public void handleChatMessage(String message) { + llmConfigService.handleChatMessage(message); + } +} diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/viewmodels/MutationToolWindowViewModel.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/viewmodels/MutationToolWindowViewModel.java similarity index 76% rename from src/main/java/com/github/jaksonlin/pitestintellij/viewmodels/MutationToolWindowViewModel.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/viewmodels/MutationToolWindowViewModel.java index 14fb11c0..7df834e1 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/viewmodels/MutationToolWindowViewModel.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/viewmodels/MutationToolWindowViewModel.java @@ -1,9 +1,9 @@ -package com.github.jaksonlin.pitestintellij.viewmodels; +package com.github.jaksonlin.testcraft.presentation.viewmodels; -import com.github.jaksonlin.pitestintellij.mediators.IMutationMediator; -import com.github.jaksonlin.pitestintellij.mediators.MutationMediatorImpl; -import com.github.jaksonlin.pitestintellij.services.RunHistoryManagerService; -import com.github.jaksonlin.pitestintellij.components.ObservableTree; +import com.github.jaksonlin.testcraft.infrastructure.messaging.mediators.IMutationMediator; +import com.github.jaksonlin.testcraft.infrastructure.messaging.mediators.MutationMediatorImpl; +import com.github.jaksonlin.testcraft.infrastructure.services.business.RunHistoryManagerService; +import com.github.jaksonlin.testcraft.presentation.components.mutation.ObservableTree; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.Nullable; @@ -12,12 +12,11 @@ import java.util.Enumeration; public class MutationToolWindowViewModel { - private final RunHistoryManagerService runHistoryManager; + private final RunHistoryManagerService runHistoryManager = RunHistoryManagerService.getInstance(); private final IMutationMediator mutationReportMediator = new MutationMediatorImpl(); private final MutationTreeMediatorViewModel mutationTreeMediatorVM; public MutationToolWindowViewModel(Project project, ObservableTree mutationTree) { - this.runHistoryManager = project.getService(RunHistoryManagerService.class); this.mutationTreeMediatorVM = new MutationTreeMediatorViewModel(project, mutationReportMediator); runHistoryManager.addObserver(mutationTree); } diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/viewmodels/MutationTreeMediatorViewModel.java b/src/main/java/com/github/jaksonlin/testcraft/presentation/viewmodels/MutationTreeMediatorViewModel.java similarity index 86% rename from src/main/java/com/github/jaksonlin/pitestintellij/viewmodels/MutationTreeMediatorViewModel.java rename to src/main/java/com/github/jaksonlin/testcraft/presentation/viewmodels/MutationTreeMediatorViewModel.java index 685d2dae..a2bbe574 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/viewmodels/MutationTreeMediatorViewModel.java +++ b/src/main/java/com/github/jaksonlin/testcraft/presentation/viewmodels/MutationTreeMediatorViewModel.java @@ -1,11 +1,10 @@ -package com.github.jaksonlin.pitestintellij.viewmodels; - -import com.github.jaksonlin.pitestintellij.context.PitestContext; -import com.github.jaksonlin.pitestintellij.mediators.IMutationMediator; -import com.github.jaksonlin.pitestintellij.mediators.IMutationReportUI; -import com.github.jaksonlin.pitestintellij.services.RunHistoryManagerService; -import com.github.jaksonlin.pitestintellij.util.Mutation; -import com.github.jaksonlin.pitestintellij.util.Pair; +package com.github.jaksonlin.testcraft.presentation.viewmodels; + +import com.github.jaksonlin.testcraft.domain.context.PitestContext; +import com.github.jaksonlin.testcraft.infrastructure.messaging.mediators.IMutationMediator; +import com.github.jaksonlin.testcraft.infrastructure.services.business.RunHistoryManagerService; +import com.github.jaksonlin.testcraft.util.Mutation; +import com.github.jaksonlin.testcraft.util.Pair; import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; @@ -29,24 +28,33 @@ import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; -import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.MutationEvent; +import com.github.jaksonlin.testcraft.infrastructure.messaging.events.TypedEventObserver; -public class MutationTreeMediatorViewModel implements IMutationReportUI { +public class MutationTreeMediatorViewModel { private static final Logger log = LoggerFactory.getLogger(MutationTreeMediatorViewModel.class); private final Project project; private final IMutationMediator mediator; - private final RunHistoryManagerService runHistoryManager; + private final RunHistoryManagerService runHistoryManager = RunHistoryManagerService.getInstance(); protected final HashMap annotatedNodes = new HashMap<>(); + private final TypedEventObserver mutationObserver = new TypedEventObserver(MutationEvent.class) { + @Override + public void onTypedEvent(MutationEvent event) { + if (event.getEventType().equals(MutationEvent.MUTATION_RESULT)) { + Pair>> payload = (Pair>>) event.getPayload(); + SwingUtilities.invokeLater(() -> updateMutationResult(payload.getFirst(), payload.getSecond())); + } + } + }; + public MutationTreeMediatorViewModel(@NotNull Project project, @NotNull IMutationMediator mediator) { this.project = project; this.mediator = mediator; - this.runHistoryManager = project.getService(RunHistoryManagerService.class); - mediator.register(this); registerEditorListener(project); } @@ -63,7 +71,6 @@ public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile f }); } - @Override public void updateMutationResult(String mutationClassFilePath, Map> mutationTestResult) { VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(mutationClassFilePath); if (virtualFile != null) { diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/Blocks.java b/src/main/java/com/github/jaksonlin/testcraft/util/Blocks.java similarity index 90% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/Blocks.java rename to src/main/java/com/github/jaksonlin/testcraft/util/Blocks.java index a81dcfa2..5d9ecdd3 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/Blocks.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/Blocks.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/ClassFileInfo.java b/src/main/java/com/github/jaksonlin/testcraft/util/ClassFileInfo.java similarity index 92% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/ClassFileInfo.java rename to src/main/java/com/github/jaksonlin/testcraft/util/ClassFileInfo.java index 30be685e..80bf9d0c 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/ClassFileInfo.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/ClassFileInfo.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; public class ClassFileInfo { private final String fullyQualifiedName; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/FileUtils.java b/src/main/java/com/github/jaksonlin/testcraft/util/FileUtils.java similarity index 97% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/FileUtils.java rename to src/main/java/com/github/jaksonlin/testcraft/util/FileUtils.java index 63cba221..244efd64 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/FileUtils.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/FileUtils.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import java.io.File; import java.io.IOException; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/GitCommitInfo.java b/src/main/java/com/github/jaksonlin/testcraft/util/GitCommitInfo.java similarity index 92% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/GitCommitInfo.java rename to src/main/java/com/github/jaksonlin/testcraft/util/GitCommitInfo.java index 8535f30e..46df17d5 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/GitCommitInfo.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/GitCommitInfo.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; public class GitCommitInfo { private final String author; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/GitUserInfo.java b/src/main/java/com/github/jaksonlin/testcraft/util/GitUserInfo.java similarity index 92% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/GitUserInfo.java rename to src/main/java/com/github/jaksonlin/testcraft/util/GitUserInfo.java index 92b2a50d..5ce4ec44 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/GitUserInfo.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/GitUserInfo.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; public class GitUserInfo { private final String name; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/GitUtil.java b/src/main/java/com/github/jaksonlin/testcraft/util/GitUtil.java similarity index 52% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/GitUtil.java rename to src/main/java/com/github/jaksonlin/testcraft/util/GitUtil.java index 7fea8114..6dd7c0e9 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/GitUtil.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/GitUtil.java @@ -1,6 +1,7 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; @@ -15,11 +16,44 @@ import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.Executors; public class GitUtil { + private static final Logger logger = Logger.getInstance(GitUtil.class); + private static final ThreadPoolExecutor gitThreadPool = new ThreadPoolExecutor( + 4, // core pool size + 8, // max pool size + 10L, // reduced keep alive time to release threads faster + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(50), + new ThreadFactory() { + private final AtomicInteger counter = new AtomicInteger(1); + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "GitCommandThread-" + counter.getAndIncrement()); + thread.setDaemon(true); + return thread; + } + }, + new ThreadPoolExecutor.AbortPolicy() + ) { + @Override + protected void afterExecute(Runnable r, Throwable t) { + super.afterExecute(r, t); + purge(); + allowCoreThreadTimeOut(true); + } + }; + private static final long GIT_COMMAND_TIMEOUT = 30; // seconds public static GitRepositoryManager getRepositoryManager(Project project) { return GitRepositoryManager.getInstance(project); @@ -35,30 +69,63 @@ public static GitRepository getRepositoryForFile(Project project, VirtualFile fi return GitRepositoryManager.getInstance(project).getRepositoryForFile(file); } - public static GitUserInfo getGitUserInfo(Project project) { - GitRepositoryManager repositoryManager = getRepositoryManager(project); - List repositories = repositoryManager.getRepositories(); - GitRepository repository = repositories.isEmpty() ? null : repositories.get(0); - - if (repository != null) { + private static CompletableFuture> runGitCommandAsync(GitLineHandler handler) { + Git git = Git.getInstance(); + boolean inDispatchThread = ApplicationManager.getApplication().isDispatchThread(); + + Callable> runCommandCallable = () -> { try { - Callable getNameCallable = () -> GitConfigUtil.getValue(project, repository.getRoot(), GitConfigUtil.USER_NAME); - String name = ApplicationManager.getApplication().executeOnPooledThread(getNameCallable).get(); + logger.info("Starting git command execution in thread: " + Thread.currentThread().getName()); + List result = git.runCommand(handler).getOutput(); + logger.info("Completed git command execution in thread: " + Thread.currentThread().getName()); + return result; + } catch (Exception e) { + logger.error("Error executing git command in thread " + Thread.currentThread().getName(), e); + throw e; + } finally { + gitThreadPool.purge(); + } + }; - Callable getEmailCallable = () -> GitConfigUtil.getValue(project, repository.getRoot(), GitConfigUtil.USER_EMAIL); - String email = ApplicationManager.getApplication().executeOnPooledThread(getEmailCallable).get(); + CompletableFuture> future = new CompletableFuture<>(); + + try { + logger.info("Submitting git command to thread pool. Current active threads: " + + gitThreadPool.getActiveCount() + ", Queue size: " + gitThreadPool.getQueue().size()); + + gitThreadPool.submit(() -> { + try { + List result = runCommandCallable.call(); + future.complete(result); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + + // Set a timeout + long timeout = inDispatchThread ? 5 : GIT_COMMAND_TIMEOUT; + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.schedule(() -> { + if (!future.isDone()) { + future.completeExceptionally(new TimeoutException("Git command timed out after " + timeout + " seconds")); + } + scheduler.shutdown(); + }, timeout, TimeUnit.SECONDS); + + } catch (Exception e) { + future.completeExceptionally(e); + } + + return future; + } - return new GitUserInfo( - name != null ? name : "Unknown", - email != null ? email : "unknown@email.com" - ); - } catch (InterruptedException | ExecutionException e) { - // Handle exception appropriately, maybe log it - e.printStackTrace(); - return new GitUserInfo("Unknown", "unknown@email.com"); - } + private static List runGitCommand(GitLineHandler handler) { + try { + return runGitCommandAsync(handler).get(); + } catch (InterruptedException | ExecutionException e) { + logger.error("Failed to execute git command", e); + throw new RuntimeException("Git command execution failed: " + e.getMessage(), e); } - return new GitUserInfo("Unknown", "unknown@email.com"); } public static GitCommitInfo getLastCommitInfo(Project project, PsiFile file) { @@ -67,20 +134,24 @@ public static GitCommitInfo getLastCommitInfo(Project project, PsiFile file) { if (repository == null) { return null; } - Git git = Git.getInstance(); GitLineHandler handler = new GitLineHandler(project, repository.getRoot(), GitCommand.LOG); - handler.addParameters( - "--max-count=1", - "--pretty=format:%an|%ae|%ad|%s", - "--date=format:%Y-%m-%d %H:%M:%S", - "--", - virtualFile.getPath() - ); + + List gitCommandArgs = new ArrayList<>(); + gitCommandArgs.add("--max-count=1"); + gitCommandArgs.add("--pretty=format:%an|%ae|%ad|%s"); + gitCommandArgs.add("--date=format:%Y-%m-%d %H:%M:%S"); + gitCommandArgs.add("--"); + gitCommandArgs.add(virtualFile.getPath()); + handler.addParameters(gitCommandArgs); + StringBuilder command = new StringBuilder(); + + for (String arg : gitCommandArgs) { + command.append(arg).append(" "); + } + logger.info("Running git command: " + command.toString()); - try { - Callable> runCommandCallable = () -> git.runCommand(handler).getOutput(); - List output = ApplicationManager.getApplication().executeOnPooledThread(runCommandCallable).get(); + List output = runGitCommand(handler); String firstLine = output.isEmpty() ? null : output.get(0); if (firstLine != null) { String[] parts = firstLine.split("\\|"); @@ -93,10 +164,7 @@ public static GitCommitInfo getLastCommitInfo(Project project, PsiFile file) { ); } } - } catch (InterruptedException | ExecutionException e) { - // Handle exception appropriately, maybe log it - e.printStackTrace(); - } + return null; } @@ -110,7 +178,6 @@ public static GitUserInfo getFirstCreatorInfo(Project project, PsiMethod psiMeth if (repository == null) { return null; } - Git git = Git.getInstance(); Document document = FileDocumentManager.getInstance().getDocument(file); if (document == null) { @@ -122,15 +189,21 @@ public static GitUserInfo getFirstCreatorInfo(Project project, PsiMethod psiMeth int endLine = document.getLineNumber(endOffset) + 1; GitLineHandler handler = new GitLineHandler(project, repository.getRoot(), GitCommand.BLAME); - handler.addParameters( - "-L", startLine + "," + endLine, - "--porcelain", - file.getPath() - ); + + List gitCommandArgs = new ArrayList<>(); + gitCommandArgs.add("-L"); + gitCommandArgs.add(startLine + "," + endLine); + gitCommandArgs.add("--porcelain"); + gitCommandArgs.add(file.getPath()); + handler.addParameters(gitCommandArgs); + StringBuilder command = new StringBuilder(); + for (String arg : gitCommandArgs) { + command.append(arg).append(" "); + } + logger.info("Running git command: " + command.toString()); - try { - Callable> runCommandCallable = () -> git.runCommand(handler).getOutput(); - List output = ApplicationManager.getApplication().executeOnPooledThread(runCommandCallable).get(); + + List output = runGitCommand(handler); String authorName = null; String email = null; @@ -169,43 +242,55 @@ public static GitUserInfo getFirstCreatorInfo(Project project, PsiMethod psiMeth timestamp != null ? timestamp : System.currentTimeMillis() ); } - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } + return null; } public static GitUserInfo getLastModifyInfo(Project project, PsiMethod psiMethod) { - PsiFile containingFile = psiMethod.getContainingFile(); - VirtualFile file = containingFile != null ? containingFile.getVirtualFile() : null; - if (file == null) { - return null; - } - GitRepository repository = getRepositoryForFile(project, file); - if (repository == null) { - return null; - } - Git git = Git.getInstance(); - - Document document = FileDocumentManager.getInstance().getDocument(file); - if (document == null) { - return null; - } - int startOffset = psiMethod.getTextRange().getStartOffset(); - int endOffset = psiMethod.getTextRange().getEndOffset(); - int startLine = document.getLineNumber(startOffset) + 1; - int endLine = document.getLineNumber(endOffset) + 1; + try { + PsiFile containingFile = psiMethod.getContainingFile(); + VirtualFile file = containingFile != null ? containingFile.getVirtualFile() : null; + if (file == null) { + logger.error("Cannot get virtual file for method: " + psiMethod.getName()); + return getGitUserInfo(project); + } + + GitRepository repository = getRepositoryForFile(project, file); + if (repository == null) { + logger.error("Cannot find git repository for file: " + file.getPath()); + return getGitUserInfo(project); + } - GitLineHandler handler = new GitLineHandler(project, repository.getRoot(), GitCommand.BLAME); - handler.addParameters( - "-L", startLine + "," + endLine, - "--porcelain", - file.getPath() - ); + Document document = FileDocumentManager.getInstance().getDocument(file); + if (document == null) { + logger.error("Cannot get document for file: " + file.getPath()); + return getGitUserInfo(project); + } + + int startOffset = psiMethod.getTextRange().getStartOffset(); + int endOffset = psiMethod.getTextRange().getEndOffset(); + int startLine = document.getLineNumber(startOffset) + 1; + int endLine = document.getLineNumber(endOffset) + 1; + + GitLineHandler handler = new GitLineHandler(project, repository.getRoot(), GitCommand.BLAME); + + List gitCommandArgs = new ArrayList<>(); + gitCommandArgs.add("-L"); + gitCommandArgs.add(startLine + "," + endLine); + gitCommandArgs.add("--porcelain"); + gitCommandArgs.add(file.getPath()); + handler.addParameters(gitCommandArgs); + StringBuilder command = new StringBuilder(); + for (String arg : gitCommandArgs) { + command.append(arg).append(" "); + } + logger.info("Running git command: " + command.toString()); - try { - Callable> runCommandCallable = () -> git.runCommand(handler).getOutput(); - List output = ApplicationManager.getApplication().executeOnPooledThread(runCommandCallable).get(); + List output = runGitCommand(handler); + if (output == null || output.isEmpty()) { + logger.info("No output from git blame command"); + return getGitUserInfo(project); + } BlameInfo latestCommit = null; BlameInfo currentCommit = null; @@ -247,7 +332,7 @@ public static GitUserInfo getLastModifyInfo(Project project, PsiMethod psiMethod latestCommit = currentCommit; } } catch (NumberFormatException e) { - e.printStackTrace(); + logger.error("Error parsing timestamp from git blame output", e); } currentCommit = null; } @@ -260,10 +345,57 @@ public static GitUserInfo getLastModifyInfo(Project project, PsiMethod psiMethod latestCommit.getTimestamp() != null ? latestCommit.getTimestamp() : System.currentTimeMillis() ); } - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); + + // If we couldn't get the last modify info, return current user info + logger.info("Could not determine last modifier, using current user info"); + return getGitUserInfo(project); + + } catch (Exception e) { + logger.error("Error getting last modify info", e); + return getGitUserInfo(project); + } + } + + public static CompletableFuture getGitUserInfoAsync(Project project) { + GitRepositoryManager repositoryManager = getRepositoryManager(project); + List repositories = repositoryManager.getRepositories(); + GitRepository repository = repositories.isEmpty() ? null : repositories.get(0); + + if (repository != null) { + CompletableFuture nameFuture = CompletableFuture.supplyAsync(() -> { + try { + return GitConfigUtil.getValue(project, repository.getRoot(), GitConfigUtil.USER_NAME); + } catch (Exception e) { + logger.error("Error getting git user name", e); + return "Unknown"; + } + }, gitThreadPool); + + CompletableFuture emailFuture = CompletableFuture.supplyAsync(() -> { + try { + return GitConfigUtil.getValue(project, repository.getRoot(), GitConfigUtil.USER_EMAIL); + } catch (Exception e) { + logger.error("Error getting git user email", e); + return "unknown@email.com"; + } + }, gitThreadPool); + + return CompletableFuture.allOf(nameFuture, emailFuture) + .thenApply(v -> new GitUserInfo( + nameFuture.join() != null ? nameFuture.join() : "Unknown", + emailFuture.join() != null ? emailFuture.join() : "unknown@email.com" + )); + } + return CompletableFuture.completedFuture(new GitUserInfo("Unknown", "unknown@email.com")); + } + + public static GitUserInfo getGitUserInfo(Project project) { + try { + return getGitUserInfoAsync(project).get(5, TimeUnit.SECONDS); + } catch (Exception e) { + logger.error("Failed to get git user info", e); + return new GitUserInfo("Unknown", "unknown@email.com"); } - return null; } private static class BlameInfo { diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/GradleUtils.java b/src/main/java/com/github/jaksonlin/testcraft/util/GradleUtils.java similarity index 98% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/GradleUtils.java rename to src/main/java/com/github/jaksonlin/testcraft/util/GradleUtils.java index c274f731..9f671b58 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/GradleUtils.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/GradleUtils.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/Indexes.java b/src/main/java/com/github/jaksonlin/testcraft/util/Indexes.java similarity index 90% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/Indexes.java rename to src/main/java/com/github/jaksonlin/testcraft/util/Indexes.java index 74335cf3..6aa11edd 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/Indexes.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/Indexes.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/JavaFileProcessor.java b/src/main/java/com/github/jaksonlin/testcraft/util/JavaFileProcessor.java similarity index 97% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/JavaFileProcessor.java rename to src/main/java/com/github/jaksonlin/testcraft/util/JavaFileProcessor.java index c0846321..8827356c 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/JavaFileProcessor.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/JavaFileProcessor.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import com.github.javaparser.JavaParser; import com.github.javaparser.ParseResult; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/Mutation.java b/src/main/java/com/github/jaksonlin/testcraft/util/Mutation.java similarity index 98% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/Mutation.java rename to src/main/java/com/github/jaksonlin/testcraft/util/Mutation.java index f29fabda..0c9e25fd 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/Mutation.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/Mutation.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; public class Mutation { private boolean detected; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/MutationReportParser.java b/src/main/java/com/github/jaksonlin/testcraft/util/MutationReportParser.java similarity index 95% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/MutationReportParser.java rename to src/main/java/com/github/jaksonlin/testcraft/util/MutationReportParser.java index 46cc2c2f..58ba1ff2 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/MutationReportParser.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/MutationReportParser.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/Mutations.java b/src/main/java/com/github/jaksonlin/testcraft/util/Mutations.java similarity index 93% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/Mutations.java rename to src/main/java/com/github/jaksonlin/testcraft/util/Mutations.java index 2a83f781..6fe3e074 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/Mutations.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/Mutations.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; diff --git a/src/main/java/com/github/jaksonlin/testcraft/util/OllamaClient.java b/src/main/java/com/github/jaksonlin/testcraft/util/OllamaClient.java new file mode 100644 index 00000000..ee49f253 --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/util/OllamaClient.java @@ -0,0 +1,103 @@ +package com.github.jaksonlin.testcraft.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.intellij.openapi.diagnostic.Logger; +import org.apache.http.HttpEntity; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class OllamaClient { + private static final Logger LOG = Logger.getInstance(OllamaClient.class); + private final String baseUrl; + private final ObjectMapper objectMapper; + private final CloseableHttpClient httpClient; + private final int timeoutSeconds; + private final String model; + private final int maxTokens; + private final float temperature; + + public OllamaClient(String host, String model, int maxTokens, float temperature, int port, int timeoutSeconds) { + this.baseUrl = String.format("http://%s:%d", host, port); + this.objectMapper = new ObjectMapper(); + this.timeoutSeconds = timeoutSeconds; + this.model = model; + this.maxTokens = maxTokens; + this.temperature = temperature; + + RequestConfig config = RequestConfig.custom() + .setConnectTimeout(timeoutSeconds * 1000) + .setConnectionRequestTimeout(timeoutSeconds * 1000) + .setSocketTimeout(timeoutSeconds * 1000) + .build(); + + this.httpClient = HttpClients.custom() + .setDefaultRequestConfig(config) + .build(); + } + + public boolean testConnection() { + try { + HttpGet request = new HttpGet(baseUrl); + try (CloseableHttpResponse response = httpClient.execute(request)) { + return response.getStatusLine().getStatusCode() == 200; + } + } catch (Exception e) { + LOG.warn("Failed to test connection to Ollama server", e); + return false; + } + } + + public String chatCompletion(List messages) throws IOException { + Map requestBody = new HashMap<>(); + requestBody.put("model", model); + requestBody.put("messages", messages); + requestBody.put("stream", false); + requestBody.put("temperature", temperature); + requestBody.put("num_predict", maxTokens); + + HttpPost request = new HttpPost(baseUrl + "/api/chat"); + request.setHeader("Content-Type", "application/json"); + request.setEntity(new StringEntity(objectMapper.writeValueAsString(requestBody), "UTF-8")); + + try (CloseableHttpResponse response = httpClient.execute(request)) { + if (response.getStatusLine().getStatusCode() != 200) { + String errorBody = EntityUtils.toString(response.getEntity()); + LOG.error("Error from Ollama API: " + errorBody); + throw new IOException("Failed to get response from Ollama API: " + response.getStatusLine().getStatusCode()); + } + + String responseBody = EntityUtils.toString(response.getEntity()); + Map responseMap = objectMapper.readValue(responseBody, Map.class); + return ((Map) responseMap.get("message")).get("content"); + } + } + + public static class Message { + private String role; + private String content; + + public Message(String role, String content) { + this.role = role; + this.content = content; + } + + public String getRole() { + return role; + } + + public String getContent() { + return content; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/Pair.java b/src/main/java/com/github/jaksonlin/testcraft/util/Pair.java similarity index 88% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/Pair.java rename to src/main/java/com/github/jaksonlin/testcraft/util/Pair.java index 30958f3d..cae8258f 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/Pair.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/Pair.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; public class Pair { private final K key; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/ProcessExecutor.java b/src/main/java/com/github/jaksonlin/testcraft/util/ProcessExecutor.java similarity index 94% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/ProcessExecutor.java rename to src/main/java/com/github/jaksonlin/testcraft/util/ProcessExecutor.java index 571c50e6..d4df33c5 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/ProcessExecutor.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/ProcessExecutor.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import java.io.BufferedReader; import java.io.IOException; @@ -8,10 +8,11 @@ import java.util.List; public class ProcessExecutor { - public static ProcessResult executeProcess(List command) { + public static ProcessResult executeProcess(List command, String workingDirectory) { try { ProcessBuilder builder = new ProcessBuilder(command); builder.environment().put("GRADLE_OPTS", "-Dorg.gradle.daemon=false -Dorg.gradle.debug=true"); + builder.directory(workingDirectory != null ? new java.io.File(workingDirectory) : null); Process process = builder.start(); StringBuilder output = new StringBuilder(); StringBuilder errorOutput = new StringBuilder(); diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/ProcessResult.java b/src/main/java/com/github/jaksonlin/testcraft/util/ProcessResult.java similarity index 91% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/ProcessResult.java rename to src/main/java/com/github/jaksonlin/testcraft/util/ProcessResult.java index 1bbd4e33..b19f1073 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/ProcessResult.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/ProcessResult.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; public class ProcessResult { private final int exitCode; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/PsiUtil.java b/src/main/java/com/github/jaksonlin/testcraft/util/PsiUtil.java similarity index 96% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/PsiUtil.java rename to src/main/java/com/github/jaksonlin/testcraft/util/PsiUtil.java index 2b40b819..cc23ecc9 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/PsiUtil.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/PsiUtil.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; diff --git a/src/main/java/com/github/jaksonlin/pitestintellij/util/TargetClassInfo.java b/src/main/java/com/github/jaksonlin/testcraft/util/TargetClassInfo.java similarity index 88% rename from src/main/java/com/github/jaksonlin/pitestintellij/util/TargetClassInfo.java rename to src/main/java/com/github/jaksonlin/testcraft/util/TargetClassInfo.java index 38c7f377..00e3a62c 100644 --- a/src/main/java/com/github/jaksonlin/pitestintellij/util/TargetClassInfo.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/TargetClassInfo.java @@ -1,4 +1,4 @@ -package com.github.jaksonlin.pitestintellij.util; +package com.github.jaksonlin.testcraft.util; import java.nio.file.Path; diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8f16c3d4..6c80e353 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,83 +1,143 @@ - Bundled with PIT 1.16.1 -

- Adds a 'Unittest Helpers' menu to the right-click context menu in the editor, and the `Run Mutation Test` action to the submenu. -
- Adds a 'Mutation Test History' tool window to display the history of mutation tests for each of the `mutated` classes. -
- You can navigate to the source code of the mutated class by searching on the class name with enter key. -
- Adds rendering of the mutation test result on the code editor. -
- Automatic detection of the classpath dependencies for running the mutation test, making use of the Gradle API from the IntelliJ IDEA. -
- Support long classpath for running the mutation test, especially for enterprise projects when there are thousands of classes in the classpath; and even jar files from other locations. -
- Additional classes in resource directory (marked as resource in IDE) are added to the classpath for mutation testing. -

- Usage: Right-click on a JUnit test file and select `Unittest Helpers` -> `Run Mutation Test` to run PIT mutation testing on the test file. -

- ]]>
- - 1.0.1-beta + TestCraft Pro is a comprehensive Java testing toolkit that enhances test quality through multiple features: + +
    +
  • Mutation Testing +
      +
    • Run PITest on both Gradle and Maven projects
    • +
    • Visual results in dedicated tool window
    • +
    • Source code navigation and decoration
    • +
    +
  • +
  • Test Case Management +
      +
    • Automated test case annotation generation
    • +
    • Annotation validation and inspection
    • +
    • Smart code completion for annotations
    • +
    +
  • +
  • Test Quality Assurance +
      +
    • Assertion statement validation
    • +
    • Missing assertion detection
    • +
    • Test documentation verification
    • +
    +
  • +
+ +

Originally evolved from pitest-gradle, TestCraft Pro has expanded to become a complete testing toolkit while maintaining all mutation testing capabilities.

+ ]]> + + Change Notes +

1.0.10 - 2025-05-06

+
    +
  • bugfix, git command execution in a separate thread to avoid UI blocking
  • +
+

1.0.9 - 2025-04-30

+
    +
  • bugfix, backward compatibility to JDK1.8 support to run in older IntelliJ IDEA version
  • +
  • upload to pitest-gradle also, for pitest-gradle user, please uninstall pitest-gradle plugin and reinstall the new one manged in [testcraft](https://plugins.jetbrains.com/plugin/27221-testcraft-pro).
  • +
+

1.0.8 - 2025-04-28

+
    +
  • Run pitest in the test file's source root as the process working directory
  • +
  • Dump pitest context into report directory for issue debug
  • +
  • Enhance output information for issue debug
  • +
+

1.0.7 - 2025-04-16

+
    +
  • enhance the dump prompt to use compact prompt
  • +
  • i18n enhancement
  • +
  • bugfixes for no mutation can provide suggestions
  • +
+

1.0.6 - 2025-04-09

+
    +
  • Add ollama access to evaulate the unit test and create new test
  • +
+ +

1.0.5 - 2025-04-01

+
    +
  • Added assertion statement validation
  • +
  • Enhanced test quality checks
  • +
  • Added compatibility with IDE version 251
  • +
+ +

1.0.4 - 2025-01-16

+
    +
  • Added compatibility to IDE version 251
  • +
+ +

1.0.3 - 2024-11-14

+
    +
  • Added unit test annotation generation
  • +
  • Added unit test annotation inspection
  • +
+ +

Previous Versions

+
    +
  • Initial PITest integration
  • +
  • Bug fixes and stability improvements
  • +
+ ]]>
+ + 1.0.10 Jason Lam - com.github.jaksonlin.pitestintellij - Pitest-Gradle - jaksonlin + com.github.jaksonlin.testcraftpro + TestCraft Pro + Jakson Lin com.intellij.modules.platform com.intellij.modules.java com.intellij.modules.lang - com.intellij.modules.json + com.intellij.modules.json Git4Idea messages.MyBundle - + + factoryClass="com.github.jaksonlin.testcraft.presentation.toolWindow.LLMSuggestionsToolWindowFactory"/> + instance="com.github.jaksonlin.testcraft.application.settings.TestCraftSettingsConfigurable"/> - - - + + + + implementationClass="com.github.jaksonlin.testcraft.application.completions.AnnotationCompletionContributor"/> - + @@ -104,23 +164,29 @@ - + - - - + + + + +
diff --git a/src/main/resources/META-INF/withJson.xml b/src/main/resources/META-INF/withJson.xml deleted file mode 100644 index c4b32869..00000000 --- a/src/main/resources/META-INF/withJson.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/main/resources/messages/MyBundle.properties b/src/main/resources/messages/MyBundle.properties index 4e0dfc19..109ac78c 100644 --- a/src/main/resources/messages/MyBundle.properties +++ b/src/main/resources/messages/MyBundle.properties @@ -82,6 +82,7 @@ toolwindow.llm.title=TestCraft LLM Suggestions action.RunPitestAction.text=Run Mutation Test action.RunCaseAnnoationCheckAction.text=Run Current Method Annotation Check action.GenerateAnnotationCommandAction.text=Generate Testcase Annotation On Method +action.CheckInvalidTestCasesAction.text=Check Invalid Test Cases action.UnittestHelperToolMenu.text=TestCraft Unittest Helpers action.UnittestHelperSubMenu.text=TestCraft Unittest Helper Tools @@ -141,4 +142,8 @@ test.annotation.exists.title=Annotation Already Exists # Pitest Run Messages pitest.run.canceled=Pitest run was canceled pitest.run.canceled.title=Canceled -pitest.run.error=Error executing Pitest command: %s +pitest.run.error=Error executing Pitest command: % +pitest.view.report=View HTML Report + +# Chat +chat.send.button=Send diff --git a/src/main/resources/messages/MyBundle_en_US.properties b/src/main/resources/messages/MyBundle_en_US.properties index 4e0dfc19..ada35522 100644 --- a/src/main/resources/messages/MyBundle_en_US.properties +++ b/src/main/resources/messages/MyBundle_en_US.properties @@ -142,3 +142,6 @@ test.annotation.exists.title=Annotation Already Exists pitest.run.canceled=Pitest run was canceled pitest.run.canceled.title=Canceled pitest.run.error=Error executing Pitest command: %s +pitest.view.report=View HTML Report +# Chat +chat.send.button=Send diff --git a/src/main/resources/messages/MyBundle_zh_CN.properties b/src/main/resources/messages/MyBundle_zh_CN.properties index deb4166c..1fd1b8a4 100644 --- a/src/main/resources/messages/MyBundle_zh_CN.properties +++ b/src/main/resources/messages/MyBundle_zh_CN.properties @@ -75,6 +75,7 @@ toolwindow.llm.title=TestCraft LLM 建议 action.RunPitestAction.text=运行变异测试 action.RunCaseAnnoationCheckAction.text=运行当前方法注解检查 action.GenerateAnnotationCommandAction.text=在方法上生成测试用例注解 +action.CheckInvalidTestCasesAction.text=检查无效测试用例 action.UnittestHelperToolMenu.text=TestCraft 单元测试助手 action.UnittestHelperSubMenu.text=TestCraft 单元测试辅助工具 @@ -149,3 +150,6 @@ test.annotation.exists.title=注解已存在 pitest.run.canceled=Pitest 运行已取消 pitest.run.canceled.title=已取消 pitest.run.error=执行 Pitest 命令时出错:%s +pitest.view.report=查看 HTML 报告 +# Chat +chat.send.button=发送