feat(omnibar): implement Omnibar command palette for Compose and IDEA#498
feat(omnibar): implement Omnibar command palette for Compose and IDEA#498
Conversation
## Summary Implement the Xiuper launch animation for Desktop app based on the design system specification (xuiper-ds.md). ## Changes ### New Files - AutoDevAnimation.kt: Animation curves and timing constants - EaseXiu: cubic-bezier(0.16, 1, 0.3, 1) - ultra-fast response - EaseStream: linear - for AI code streaming - SpringTactile: bounce effect for micro-interactions - Duration constants for Launch animation phases - XiuperLaunchScreen.kt: Complete launch animation component - 6-phase animation state machine - Multi-layer glow effects (AI purple + Xiu cyan) - Speed lines for forward momentum visual - Breathing pulse animation - Reduced-motion accessibility support ### Modified Files - Platform.kt (all platforms): Added prefersReducedMotion() function - JVM: macOS/Windows/Linux system settings detection - Android: Settings.Global accessibility check - JS: prefers-reduced-motion media query - iOS: UIAccessibility.isReduceMotionEnabled - WASM: Returns false (no system access) - Main.kt: Integrated launch screen with splash state management - --skip-splash command line argument support - ThemeManager.ThemeMode.DARK for launch screen ## Animation Timeline (2.2s total) - [0-100ms] Initial delay - 'gathering power' - [100-500ms] Glow expansion - energy gathering - [500-1000ms] Logo scale up with ease-xiu curve - [1000-1300ms] Text slide in from below - [1300-1900ms] Brand display + breathing glow - [1900-2200ms] Fade out to main interface ## Design System Alignment - Neon-Noir aesthetic with Void background (#0B0E14) - Dual-color energy system (xiu cyan + ai purple) - Speed lines for 'Forward Momentum' - Breathing glow for 'never static' principle Closes #493
Use kotlin.math.PI for angle conversion to improve consistency.
Refine platform-specific prefersReducedMotion implementations for better accuracy and performance. Also correct "Xuiper" to "Xiuper" in UI text.
Replace deprecated UIAccessibility.isReduceMotionEnabled with UIAccessibilityIsReduceMotionEnabled for prefersReducedMotion detection.
Simplifies reduced motion detection for WASM by always returning false due to media query limitations.
Implements GitHub Issue #495 - Omnibar command palette similar to Linear and Raycast. ## Changes ### mpp-ui (Compose Multiplatform - Shared Layer) - Add OmnibarItem.kt: Data model with OmnibarItemType enum, OmnibarItem data class, OmnibarActionResult sealed class, and OmnibarDataProvider interface - Add OmnibarSearchEngine.kt: Fuzzy search engine with weighted ranking (recency > context > match quality > base weight) - Add Omnibar.kt: Main Compose UI component with 800dp wide floating layout, semi-transparent backdrop, keyboard navigation (Up/Down/Enter/Escape) - Add Search icon to AutoDevComposeIcons - Add Void.overlay color (50% transparent) to AutoDevColors ### mpp-idea (IntelliJ IDEA Plugin) - Add IdeaOmnibarDataProvider.kt: Implementation of OmnibarDataProvider providing built-in and custom commands - Add IdeaOmnibarDialog.kt: DialogWrapper with JewelComposePanel for native IDE integration - Add IdeaOmnibarAction.kt: AnAction for opening Omnibar - Register action with keyboard shortcuts (Ctrl+Shift+P / Cmd+Shift+P) in plugin.xml ### Build Configuration - Add maven-publish plugin to xuiper-ui for mavenLocal publishing - Update publishDepsForIdea task to include xuiper-ui dependency ## Usage In IntelliJ IDEA: Press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows/Linux) Or: Tools menu → Open Omnibar
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a cross-platform Omnibar (command palette) with data providers, search/ranking engine, Compose + IDE UIs, animation system and launch screen, a prefersReducedMotion() API on all targets, UI/theme enhancements, and build updates to publish the xuiper-ui module. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant IDE_Action as IdeaOmnibarAction
participant Dialog as IdeaOmnibarDialogWrapper
participant Provider as IdeaOmnibarDataProvider
participant Search as OmnibarSearchEngine
participant Host as HostEnvironment
User->>IDE_Action: Trigger Omnibar (shortcut/menu)
IDE_Action->>Dialog: open()
Dialog->>Provider: getItems() / getRecentItems()
Provider-->>Dialog: items
User->>Dialog: type query
Dialog->>Search: search(items, query)
Search-->>Dialog: rankedResults
User->>Dialog: select item
Dialog->>Provider: executeAction(item)
Provider-->>Dialog: OmnibarActionResult
alt InsertText
Dialog->>IDE_Action: insertTextToActiveInput(text)
IDE_Action->>Host: inject text / forward to editor
else Navigate
Dialog->>IDE_Action: navigateToPath(path, line)
IDE_Action->>Host: open file & set caret
else LLMQuery
Dialog->>IDE_Action: sendToLLM(query)
IDE_Action->>Host: forward query / insert text
end
Provider->>Provider: recordUsage(item)
Dialog->>Dialog: dismiss()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 2 inconclusive)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (7)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (13)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.kt (2)
65-85: Consider returning early afteronFinished()in normal flow to prevent potential state updates.In the reduced motion path (line 68), the function returns early after
onFinished(). However, in the normal animation sequence (lines 72-84), ifonFinished()triggers recomposition that removes this composable, subsequentphaseassignments could occur on a disposed state. While this is generally safe due toremember, consider adding a check or usingisActivefor robustness.Additionally,
phasestate updates happen outside the animation spec timing, which could cause minor timing mismatches between the phase transitions and theanimateFloatAsStateanimations.
149-170: Continuous animation loops may not terminate reliably.The
while (phase < 5)conditions read thephasestate variable, but sincephaseis aMutableStateand these are coroutine loops, the loops depend on Compose's snapshot system to observe changes. While this typically works, it's fragile.A more robust pattern would be to use
snapshotFlow:LaunchedEffect(phase) { if (phase >= 1 && phase < 5) { - var time = 0f - while (phase < 5) { - time += 0.03f - glowPulse = (sin(time * 2) * 0.25f + 0.75f) - delay(16) - } + snapshotFlow { phase }.collectLatest { currentPhase -> + if (currentPhase >= 1 && currentPhase < 5) { + var time = 0f + while (true) { + time += 0.03f + glowPulse = (sin(time * 2) * 0.25f + 0.75f) + delay(16) + } + } + } } }Alternatively, consider using
AnimatablewithanimateTousinginfiniteRepeatablefor these continuous effects, which integrates better with Compose's animation system.mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt (1)
144-150: Consider logging or documenting the no-op behavior ofDefaultOmnibarDataProvider.The default implementation silently succeeds for
executeActionand does nothing forrecordUsage. This is fine for previews/testing, but consumers might accidentally use this in production. Consider adding a KDoc note clarifying this is for testing/preview purposes only./** - * Default implementation that returns empty results. + * Default implementation that returns empty results. + * Intended for testing, previews, or as a fallback when no platform-specific provider is available. */ object DefaultOmnibarDataProvider : OmnibarDataProvider {mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/Platform.jvm.kt (2)
60-72: Consider caching the macOS reduced motion result.Spawning an external process (
defaults read) on every call could be expensive ifprefersReducedMotion()is called frequently (e.g., during animation setup). Consider caching the result with a reasonable TTL or computing it once at startup.+ private var cachedReducedMotion: Boolean? = null + private var cachedReducedMotionTime: Long = 0 + private const val CACHE_TTL_MS = 60_000L // 1 minute + actual fun prefersReducedMotion(): Boolean { + val now = System.currentTimeMillis() + cachedReducedMotion?.let { cached -> + if (now - cachedReducedMotionTime < CACHE_TTL_MS) return cached + } + return try { val osName = getOSName().lowercase() - when { + val result = when { osName.contains("mac") -> { // ... existing macOS logic } // ... rest of the when } + cachedReducedMotion = result + cachedReducedMotionTime = now + result } catch (e: Exception) { false } }
79-83: Linux implementation could be enhanced to check GNOME accessibility settings.The current implementation only checks
GTK_ENABLE_ANIMATIONS. For broader coverage, consider also checking GNOME's accessibility settings viagsettings:// Additional check for GNOME desktop // gsettings get org.gnome.desktop.interface enable-animationsHowever, the current implementation is a reasonable starting point, and the false default is safe.
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDataProvider.kt (2)
114-134: Consider wrapping index access in a read action.
FilenameIndex.processAllFileNamesaccesses the PSI/index which typically requires a read action. While it may work in some contexts, explicit wrapping ensures thread safety:suspend fun searchFiles(query: String): List<OmnibarItem> = withContext(Dispatchers.Default) { if (query.length < 2) return@withContext emptyList() val scope = GlobalSearchScope.projectScope(project) val files = mutableListOf<OmnibarItem>() try { + com.intellij.openapi.application.runReadAction { FilenameIndex.processAllFileNames({ fileName -> // ... }, scope, null) + } } catch (e: Exception) { // Ignore indexing errors }
93-109: Consider logging exceptions ingetCustomCommandsfor debuggability.Silently swallowing all exceptions makes it difficult to diagnose issues when custom commands fail to load.
} catch (e: Exception) { + // Log for debugging, but don't fail the entire items load + com.intellij.openapi.diagnostic.Logger.getInstance(IdeaOmnibarDataProvider::class.java) + .warn("Failed to load custom commands", e) emptyList() }mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/Omnibar.kt (2)
286-300: Use stable keys inLazyColumnfor better performance.Using
items(items.size)with index-based access loses the benefits of key-based diffing. When items are reordered or filtered, Compose may unnecessarily recompose items.- items(items.size) { index -> - val item = items[index] + items( + items = items, + key = { it.id } + ) { item -> + val index = items.indexOf(item) OmnibarResultItem( item = item, isSelected = index == selectedIndex,Or better, pass
isSelectedbased on item identity rather than index.
48-55: Consider extracting duplicated action execution logic.The action execution pattern (recordUsage → executeAction → onActionResult → onDismiss) is duplicated in both
OmnibarSearchField.onKeyEvent(lines 150-155) andOmnibarResultsList.onItemClick(lines 171-176).Extract a helper function:
private fun CoroutineScope.executeOmnibarAction( item: OmnibarItem, dataProvider: OmnibarDataProvider, onActionResult: (OmnibarActionResult) -> Unit, onDismiss: () -> Unit ) { launch { dataProvider.recordUsage(item) val result = dataProvider.executeAction(item) onActionResult(result) onDismiss() } }Also applies to: 139-160
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarAction.kt (3)
78-89: Editor may not scroll to reveal the target line after caret movement.After moving the caret to the target line, the editor view may not update to show that line. Consider using
scrollToCaretto ensure visibility.if (line > 0) { val editor = fileEditorManager.selectedTextEditor - editor?.caretModel?.moveToLogicalPosition( - com.intellij.openapi.editor.LogicalPosition(line - 1, 0) - ) + editor?.let { + it.caretModel.moveToLogicalPosition( + com.intellij.openapi.editor.LogicalPosition(line - 1, 0) + ) + it.scrollingModel.scrollToCaret(com.intellij.openapi.editor.ScrollType.CENTER) + } }
53-62: Placeholder cases lack implementation or logging.The
Success,Error, andShowSubmenucases are empty placeholders. ForError, users should receive feedback. ForShowSubmenu, the relevant snippet shows it carriesitems: List<OmnibarItem>which could re-open the Omnibar with filtered results.Consider at minimum logging or showing a notification for errors:
is OmnibarActionResult.Error -> { com.intellij.openapi.ui.Messages.showErrorDialog( project, result.message, "Omnibar Error" ) }
91-94:sendToLLMis a placeholder that doesn't integrate with LLM services.The current implementation just inserts text, which doesn't fulfill the LLMQuery intent. Consider adding a TODO comment or opening an issue to track proper LLM integration.
Would you like me to open an issue to track the proper LLM integration for
OmnibarActionResult.LLMQuery?mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt (1)
116-118: Arbitrary delay before focus request may be unreliable.The 100ms delay before
requestFocus()is a timing workaround that may fail on slower systems or succeed too late on faster ones. Consider using a more deterministic approach.- delay(100) - try { focusRequester.requestFocus() } catch (_: Exception) {} + // Request focus after composition is complete + kotlinx.coroutines.yield() + try { focusRequester.requestFocus() } catch (_: Exception) {}Or use
SideEffect/DisposableEffectfor focus management.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
build.gradle.kts(1 hunks)mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/Platform.kt(1 hunks)mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.kt(1 hunks)mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/Platform.ios.kt(2 hunks)mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/Platform.js.kt(1 hunks)mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/Platform.jvm.kt(1 hunks)mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/Platform.wasmJs.kt(1 hunks)mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarAction.kt(1 hunks)mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDataProvider.kt(1 hunks)mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt(1 hunks)mpp-idea/src/main/resources/META-INF/plugin.xml(2 hunks)mpp-idea/src/test/kotlin/cc/unitmesh/devins/idea/renderer/JewelRendererTest.kt(1 hunks)mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt(1 hunks)mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.kt(1 hunks)mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/Omnibar.kt(1 hunks)mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt(1 hunks)mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarSearchEngine.kt(1 hunks)mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevAnimation.kt(1 hunks)mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevColors.kt(5 hunks)mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt(3 hunks)xuiper-ui/build.gradle.kts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.kt
📄 CodeRabbit inference engine (AGENTS.md)
**/*.kt: Useexpect/actualfor platform-specific code in Kotlin Multiplatform projects
For @JsExport in Kotlin, use concrete classes (not interfaces) and Promise (not Flow) for JavaScript interoperability
Avoid emoji and UTF-8 characters in WASM code
Files:
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/Platform.wasmJs.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.ktmpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevAnimation.ktmpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.ktmpp-core/src/iosMain/kotlin/cc/unitmesh/agent/Platform.ios.ktmpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.ktmpp-core/src/jsMain/kotlin/cc/unitmesh/agent/Platform.js.ktmpp-core/src/androidMain/kotlin/cc/unitmesh/agent/Platform.ktmpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarSearchEngine.ktmpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarAction.ktmpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/Platform.jvm.ktmpp-idea/src/test/kotlin/cc/unitmesh/devins/idea/renderer/JewelRendererTest.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDataProvider.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.ktmpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/Omnibar.ktmpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.ktmpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevColors.kt
**/*Compose*.kt
📄 CodeRabbit inference engine (AGENTS.md)
For Compose UI design, use
AutoDevColorsorMaterialTheme.colorScheme
Files:
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
🧠 Learnings (5)
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*Compose*.kt : For Compose UI design, use `AutoDevColors` or `MaterialTheme.colorScheme`
Applied to files:
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.ktmpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevAnimation.ktmpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.ktmpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevColors.kt
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*.kt : Use `expect`/`actual` for platform-specific code in Kotlin Multiplatform projects
Applied to files:
mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/Platform.ios.ktmpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.ktmpp-core/src/androidMain/kotlin/cc/unitmesh/agent/Platform.ktmpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/Platform.jvm.kt
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*Renderer.kt : When modifying `CodingAgentRenderer` in Kotlin, update ALL implementations: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, `JsRendererAdapter`
Applied to files:
mpp-idea/src/test/kotlin/cc/unitmesh/devins/idea/renderer/JewelRendererTest.kt
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Run `./gradlew :mpp-ui:generateI18n4kFiles` after i18n changes
Applied to files:
build.gradle.kts
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to {**/Compose*.kt,mpp-ui/src/jsMain/typescript/**/*.ts} : Never use hardcoded colors - always use design tokens from the design system
Applied to files:
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevColors.kt
🧬 Code graph analysis (4)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt (4)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevTheme.kt (2)
AutoDevTheme(114-141)AutoDevTheme(146-159)mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.kt (1)
XiuperLaunchScreen(56-301)mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/desktop/DesktopWindowLayout.kt (1)
DesktopWindowLayout(24-46)mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt (1)
AutoDevApp(34-68)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/Platform.jvm.kt (6)
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/Platform.wasmJs.kt (1)
getOSName(13-15)mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/Platform.ios.kt (1)
getOSName(19-21)mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/Platform.kt (1)
getOSName(15-17)mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.kt (1)
getOSName(14-14)mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/Platform.js.kt (1)
getOSName(13-15)mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/PlatformExports.kt (1)
getOSName(21-21)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt (1)
items(108-108)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/Omnibar.kt (2)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt (1)
handleKeyEvent(135-164)mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt (1)
items(108-108)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Agent
- GitHub Check: Build
- GitHub Check: Test mpp-core (KMP)
🔇 Additional comments (34)
mpp-idea/src/test/kotlin/cc/unitmesh/devins/idea/renderer/JewelRendererTest.kt (1)
5-5: TaskStatus import correctly supports task status assertionsThe added
TaskStatusimport matches its usage intestTaskBoundaryToolUpdatesTaskListand keeps the test readable; no further changes needed here.xuiper-ui/build.gradle.kts (1)
4-4: Maven publishing configuration is correct and well-integrated.The publication name "maven" properly derives the task name
publishMavenPublicationToMavenLocalthat the root build references. Thekotlin("jvm")plugin provides thejavacomponent needed for line 13.Also applies to: 10-17
build.gradle.kts (1)
30-30: Task integration is consistent and correct.The new dependency on
:xuiper-ui:publishMavenPublicationToMavenLocalcorrectly references the "maven" publication defined in xuiper-ui/build.gradle.kts. Description and success message were properly updated to reflect the expanded scope.Also applies to: 37-37, 41-41
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt (1)
106-106: Terminal icon addition is correct.The
Icons.Default.Terminalicon is available in the Material Icons library and is already in use elsewhere in the codebase (e.g., Omnibar.kt). The implementation follows the established pattern and is properly placed in the "Tools & Utilities" section.mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevColors.kt (3)
25-28: Well-structured additions for the Omnibar feature.The new Void colors follow the established progression and provide appropriate semantic tokens for the floating palette UI. The
surfaceElevatedsits logically betweensurface1andsurface2, and havingborderas a semantic alias (even with the same value assurface3) allows independent evolution of border styling later.Based on learnings, these additions properly extend the design tokens in
AutoDevColorsfor Compose UI usage.
44-44: Good semantic alias.Exposing
primaryas an alias toxiuimproves API discoverability for developers unfamiliar with the design system's terminology.
86-88: Completes the text hierarchy nicely.The
quaternarytext color provides a fourth level for the most subtle text emphasis, useful for disabled states or de-emphasized metadata. The value aligns withNeutral.c600maintaining consistency across the design system.mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.kt (2)
172-176: Good use ofAutoDevColorsfor theming.The implementation correctly uses
AutoDevColors.Void.bg,AutoDevColors.Energy.*, andAutoDevColors.Text.secondarythroughout, which aligns with the coding guidelines for Compose UI design.
280-298: LGTM!The logo and tagline text rendering is well-structured. Brand text is appropriately hardcoded as it's not subject to localization.
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt (2)
8-23: LGTM!The
OmnibarItemTypeenum provides clear categorization with good documentation for each type. The categories align well with the PR objectives for supporting commands, files, symbols, agents, and settings.
94-112: LGTM!The
OmnibarActionResultsealed class provides a clean, type-safe API for action outcomes. The variants cover the expected use cases (success, text insertion, LLM queries, navigation, submenus, errors).mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/Platform.ios.kt (1)
48-51: LGTM!The iOS implementation correctly uses
UIAccessibilityIsReduceMotionEnabled()which is the standard iOS accessibility API for detecting reduced motion preference. This follows the expect/actual pattern per coding guidelines.mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.kt (1)
30-35: LGTM!The expect declaration for
prefersReducedMotion()is well-documented and follows the expect/actual pattern correctly. The KDoc clearly explains the accessibility purpose, which helps platform implementers understand the expected behavior.mpp-idea/src/main/resources/META-INF/plugin.xml (1)
60-61: LGTM! Service and action registration follows IntelliJ conventions.The project-level service registration for
IdeaOmnibarDataProviderand the action with keyboard shortcuts are correctly configured. TheCmd/Ctrl+Shift+Pshortcut is a familiar pattern for command palettes.Also applies to: 70-81
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/Platform.wasmJs.kt (1)
44-50: LGTM! Reasonable default given WASM limitations.The hardcoded
falsereturn is an acceptable fallback since WASM has limited access to browser APIs. The comment correctly explains thejs()expression restrictions.mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/Platform.kt (1)
48-54: Acceptable workaround, but consider documenting the accessibility impact.The limitation around Context access is accurately documented. Returning
falsemeans animations will always play on Android regardless of user's accessibility settings for reduced motion.For improved accessibility in the future, consider:
- Passing Context from app initialization
- Using
Application.registerActivityLifecycleCallbacksto capture a Context referenceBased on learnings, the
expect/actualpattern is correctly applied here.mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/Platform.js.kt (1)
66-81: LGTM! Properly detects reduced motion preference in browser.The implementation correctly:
- Distinguishes browser from Node.js environments
- Uses the standard
prefers-reduced-motionmedia query- Falls back safely to
falseon errorsmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt (3)
36-39: LGTM! Correctly cachesprefersReducedMotion()result.Using
remember { Platform.prefersReducedMotion() }ensures the system call is made only once during composition, avoiding repeated calls on recomposition.
64-73: Splash screen uses hardcodedDARKtheme.The splash screen forces
ThemeManager.ThemeMode.DARKregardless of user's theme preference. This is likely intentional for visual consistency during launch, but consider documenting this design decision if it differs from the main app theme.
74-139: LGTM! Main UI conditional branch is well-structured.The DesktopWindowLayout integration with title bar tabs, menu bar, and AutoDevApp follows the established patterns from the relevant code snippets. The state management and callbacks are properly wired.
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/Omnibar.kt (1)
407-445: LGTM! Keyboard navigation logic matches IDEA implementation.The
handleKeyEventfunction correctly handles arrow keys for navigation, Enter for selection, and Escape for dismissal. The logic mirrors the implementation inIdeaOmnibarDialog.kt(lines 134-163), ensuring consistent behavior.mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarSearchEngine.kt (5)
101-110: Unsafe cast of metadata tags could cause unexpected behavior.If
metadata["tags"]contains a non-List value, theas? List<*>safely returns null. However, if it's a List with non-String elements,tag is Stringhandles it. The logic is defensive but consider documenting the expected metadata format.
83-96: Recency bonus calculation is well-structured with clear decay tiers.The tiered approach (1h, 6h, 24h, 72h) provides good granularity for recency-based ranking. The early return for
lastUsedTimestamp == 0Lcorrectly handles items never used.
117-127: Fuzzy matching implementation is correct and efficient.The algorithm correctly checks if all query characters appear in order within the string, with case-insensitive comparison. The early return when all characters match is a good optimization.
133-145:fuzzyMatchPositionsreturns empty list on partial match - ensure callers handle this.The function returns an empty list if not all query characters are found, which is correct for highlighting purposes. Callers should be aware that a non-empty return indicates a complete match.
63-77: Consider usingLinkedHashMapto preserve category insertion order.
groupByreturns aLinkedHashMapwhich preserves encounter order, so the current implementation should maintain item order. This is good for predictable UI rendering.mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt (4)
106-118: Loading logic and initial setup are well-structured.The
LaunchedEffect(Unit)properly handles async loading with a try-finally for the loading state. Combining recent items with all items and deduplicating by ID is a good approach.
134-164: Keyboard navigation implementation is comprehensive.The key handler correctly manages Up/Down cycling, Enter for selection, and Escape for dismissal. The wrap-around behavior for navigation is user-friendly.
311-388: Result item rendering with hover and selection states is well-implemented.The use of
hoverablewithMutableInteractionSourceandLaunchedEffectfor hover detection provides smooth interaction. The conditional background colors for selected/hovered states are clear.
150-158: Current implementation is safe; recordUsage and executeAction complete synchronously.Both
recordUsageandexecuteActionare quick operations—list mutations and immediate result returns with no async I/O—so they complete beforeonActionResulttriggers dialog dismissal. The coroutine scope fromrememberCoroutineScope()is properly managed by Compose and won't be cancelled by dialog closure.However, if these methods are extended to include long-running operations in the future, consider ensuring completion with
awaitClose()or similar scope lifecycle management.mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevAnimation.kt (4)
106-116: Launch duration total may not match sum of individual phases.
Duration.Launch.TOTALis 2200ms, but summing the phases: 100 + 400 + 500 + 300 + 600 + 300 = 2200ms. The math checks out; however, if phases overlap or run in parallel, the comment "总时长" (total duration) should clarify whether this is sequential or includes overlaps.The durations are well-documented and the total matches the sum. Consider adding a brief note if any phases are meant to overlap.
22-54: Easing curves are well-defined with clear use cases.The three easing options cover the main interaction patterns: fast UI transitions (EaseXiu), streaming content (EaseStream/LinearEasing), and general-purpose (EaseStandard). The design rationale in Chinese comments is helpful.
66-87: Spring animation presets provide good tactile feedback options.
SpringTactilewith moderate damping (0.6f) and stiffness (180f) should provide snappy feedback.SpringGentleusingSpring.DampingRatioMediumBouncyandSpring.StiffnessLowis appropriate for smoother transitions.
122-139: Tween factory functions provide clean API for animation specs.The generic
<T>typing and default duration parameters make these functions easy to use. Each function correctly maps to its corresponding easing curve.
| private fun insertTextToActiveInput(project: com.intellij.openapi.project.Project, text: String) { | ||
| // Try to find and use AutoInputService or similar | ||
| try { | ||
| val serviceClass = Class.forName("cc.unitmesh.devti.sketch.AutoInputService") | ||
| val getInstance = serviceClass.getMethod("getInstance", com.intellij.openapi.project.Project::class.java) | ||
| val service = getInstance.invoke(null, project) | ||
| val putText = serviceClass.getMethod("putText", String::class.java) | ||
| putText.invoke(service, text) | ||
| } catch (e: Exception) { | ||
| // Fallback: Could show in a notification or editor | ||
| } | ||
| } |
There was a problem hiding this comment.
Reflection-based service lookup silently fails without feedback.
The empty catch block swallows all exceptions, making debugging difficult when integration fails. Consider logging the error or showing a notification to the user.
try {
val serviceClass = Class.forName("cc.unitmesh.devti.sketch.AutoInputService")
val getInstance = serviceClass.getMethod("getInstance", com.intellij.openapi.project.Project::class.java)
val service = getInstance.invoke(null, project)
val putText = serviceClass.getMethod("putText", String::class.java)
putText.invoke(service, text)
} catch (e: Exception) {
- // Fallback: Could show in a notification or editor
+ // Log for debugging, consider notifying user
+ com.intellij.openapi.diagnostic.Logger.getInstance(IdeaOmnibarAction::class.java)
+ .warn("Failed to insert text via AutoInputService", e)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private fun insertTextToActiveInput(project: com.intellij.openapi.project.Project, text: String) { | |
| // Try to find and use AutoInputService or similar | |
| try { | |
| val serviceClass = Class.forName("cc.unitmesh.devti.sketch.AutoInputService") | |
| val getInstance = serviceClass.getMethod("getInstance", com.intellij.openapi.project.Project::class.java) | |
| val service = getInstance.invoke(null, project) | |
| val putText = serviceClass.getMethod("putText", String::class.java) | |
| putText.invoke(service, text) | |
| } catch (e: Exception) { | |
| // Fallback: Could show in a notification or editor | |
| } | |
| } | |
| private fun insertTextToActiveInput(project: com.intellij.openapi.project.Project, text: String) { | |
| // Try to find and use AutoInputService or similar | |
| try { | |
| val serviceClass = Class.forName("cc.unitmesh.devti.sketch.AutoInputService") | |
| val getInstance = serviceClass.getMethod("getInstance", com.intellij.openapi.project.Project::class.java) | |
| val service = getInstance.invoke(null, project) | |
| val putText = serviceClass.getMethod("putText", String::class.java) | |
| putText.invoke(service, text) | |
| } catch (e: Exception) { | |
| // Log for debugging, consider notifying user | |
| com.intellij.openapi.diagnostic.Logger.getInstance(IdeaOmnibarAction::class.java) | |
| .warn("Failed to insert text via AutoInputService", e) | |
| } | |
| } |
| private val recentlyUsedItems = mutableListOf<OmnibarItem>() | ||
| private val maxRecentItems = 10 |
There was a problem hiding this comment.
Thread safety concern: recentlyUsedItems accessed without synchronization.
The recentlyUsedItems list is modified in recordUsage() which runs in coroutine scope, but it's also read in getRecentItems(). This could lead to ConcurrentModificationException or inconsistent reads.
Consider using a thread-safe collection:
- private val recentlyUsedItems = mutableListOf<OmnibarItem>()
+ private val recentlyUsedItems = java.util.Collections.synchronizedList(mutableListOf<OmnibarItem>())Or wrap access with a mutex for more control over concurrent operations.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private val recentlyUsedItems = mutableListOf<OmnibarItem>() | |
| private val maxRecentItems = 10 | |
| private val recentlyUsedItems = java.util.Collections.synchronizedList(mutableListOf<OmnibarItem>()) | |
| private val maxRecentItems = 10 |
| override suspend fun recordUsage(item: OmnibarItem) { | ||
| val updatedItem = item.copy( | ||
| lastUsedTimestamp = System.currentTimeMillis(), | ||
| type = OmnibarItemType.RECENT | ||
| ) | ||
| recentlyUsedItems.removeAll { it.id == item.id } | ||
| recentlyUsedItems.add(0, updatedItem) | ||
| if (recentlyUsedItems.size > maxRecentItems) { | ||
| recentlyUsedItems.removeAt(recentlyUsedItems.lastIndex) | ||
| } | ||
| } |
There was a problem hiding this comment.
Bug: Changing type to RECENT breaks action execution for recent items.
When recordUsage stores the item with type = OmnibarItemType.RECENT, subsequent selection of this item from recent list will hit the else branch in executeAction() (line 59) instead of executing the original command logic.
Preserve the original type and use a separate flag or keep original metadata:
override suspend fun recordUsage(item: OmnibarItem) {
val updatedItem = item.copy(
lastUsedTimestamp = System.currentTimeMillis(),
- type = OmnibarItemType.RECENT
+ metadata = item.metadata + ("originalType" to item.type.name)
)
recentlyUsedItems.removeAll { it.id == item.id }
recentlyUsedItems.add(0, updatedItem)Then in executeAction, handle items by their original type stored in metadata.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDataProvider.kt
around lines 63–73, recordUsage currently overwrites the item's type to
OmnibarItemType.RECENT which breaks action execution; instead preserve the
original type and mark the item as recent by adding or updating a separate
flag/metadata field (e.g., isRecent = true or recentTimestamp in item.metadata)
and set lastUsedTimestamp without mutating type. Update recordUsage to
remove/insert the updated item with the preserved type and new metadata, and
update executeAction to determine the action to run from the original type (or
metadata) rather than the RECENT marker.
| ) { | ||
| Text( | ||
| text = "\uD83D\uDD0D", // 🔍 | ||
| style = TextStyle(fontSize = 16.sp) | ||
| ) |
There was a problem hiding this comment.
Emoji characters may cause issues in WASM targets.
Per coding guidelines, avoid emoji and UTF-8 characters in WASM code. The search icon (🔍) and item type icons (📄, 🤖) use emoji. Consider using icon components or ASCII alternatives for cross-platform compatibility.
Text(
- text = "\uD83D\uDD0D", // 🔍
+ text = "⌕", // Search symbol (or use an Icon composable)
style = TextStyle(fontSize = 16.sp)
)For item icons, consider extracting to a helper or using Jewel icons:
OmnibarItemType.FILE -> "📄" // Consider: "◉" or Icon component
OmnibarItemType.AGENT -> "🤖" // Consider: "◈" or Icon componentAlso applies to: 343-356
🤖 Prompt for AI Agents
In mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt
around lines 222-226 (and similarly 343-356), the code uses emoji characters
(e.g., "🔍", "📄", "🤖") which break WASM targets; replace these emoji usages
with cross-platform alternatives by using an icon component (preferred) or plain
ASCII/symbol fallbacks (e.g., "◉", "◈", or an appropriate Jewel/icon
composable), and extract item-type icon logic into a small helper function that
maps OmnibarItemType to the chosen icon component or ASCII string so all places
use the same platform-safe representation.
| LaunchedEffect(visible) { | ||
| if (visible) { | ||
| isLoading = true | ||
| try { | ||
| val items = dataProvider.getItems() | ||
| val recentItems = dataProvider.getRecentItems() | ||
| allItems = (recentItems + items).distinctBy { it.id } | ||
| filteredItems = searchEngine.search(allItems, "") | ||
| } finally { | ||
| isLoading = false | ||
| } | ||
| delay(100) | ||
| try { focusRequester.requestFocus() } catch (_: Exception) {} | ||
| } else { | ||
| searchQuery = "" | ||
| selectedIndex = 0 | ||
| } | ||
| } |
There was a problem hiding this comment.
State reset logic may not execute on dismiss.
The else block (lines 83-86) that resets searchQuery and selectedIndex is inside LaunchedEffect(visible). When visible becomes false, the composable returns early at line 56, so this LaunchedEffect won't run to reset state.
Consider moving reset logic outside, or handle it when the dialog reopens:
LaunchedEffect(visible) {
if (visible) {
+ // Reset state on open
+ searchQuery = ""
+ selectedIndex = 0
isLoading = true
// ... rest of loading logic
- } else {
- searchQuery = ""
- selectedIndex = 0
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| LaunchedEffect(visible) { | |
| if (visible) { | |
| isLoading = true | |
| try { | |
| val items = dataProvider.getItems() | |
| val recentItems = dataProvider.getRecentItems() | |
| allItems = (recentItems + items).distinctBy { it.id } | |
| filteredItems = searchEngine.search(allItems, "") | |
| } finally { | |
| isLoading = false | |
| } | |
| delay(100) | |
| try { focusRequester.requestFocus() } catch (_: Exception) {} | |
| } else { | |
| searchQuery = "" | |
| selectedIndex = 0 | |
| } | |
| } | |
| LaunchedEffect(visible) { | |
| if (visible) { | |
| // Reset state on open | |
| searchQuery = "" | |
| selectedIndex = 0 | |
| isLoading = true | |
| try { | |
| val items = dataProvider.getItems() | |
| val recentItems = dataProvider.getRecentItems() | |
| allItems = (recentItems + items).distinctBy { it.id } | |
| filteredItems = searchEngine.search(allItems, "") | |
| } finally { | |
| isLoading = false | |
| } | |
| delay(100) | |
| try { focusRequester.requestFocus() } catch (_: Exception) {} | |
| } | |
| } |
There was a problem hiding this comment.
Pull request overview
This PR implements a comprehensive Omnibar command palette feature inspired by Linear and Raycast, adding a keyboard-driven command interface across Compose Multiplatform and IntelliJ IDEA platforms. The implementation introduces a fuzzy search engine with weighted ranking, launch screen animations, accessibility support through reduced motion detection, and platform-specific integrations.
Key Changes
- Omnibar Command Palette: 800dp wide floating interface with fuzzy search, keyboard navigation (Up/Down/Enter/Escape), and weighted result ranking based on recency, context, match quality, and base weight
- Platform Accessibility API: Added
Platform.prefersReducedMotion()across all platforms (JVM, iOS, Android, JS, WASM) to support users with motion sensitivity - Launch Screen Animation: Implemented Xiuper-branded launch animation with accessibility-aware simplified mode
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
xuiper-ui/build.gradle.kts |
Added maven-publish plugin for local publishing to support mpp-idea integration |
build.gradle.kts |
Updated publishDepsForIdea task to include xuiper-ui dependency |
mpp-core/src/.../Platform.kt |
Added prefersReducedMotion() API with platform-specific implementations for accessibility |
mpp-ui/.../omnibar/OmnibarItem.kt |
Core data models: OmnibarItem with type enum, OmnibarActionResult sealed class, OmnibarDataProvider interface |
mpp-ui/.../omnibar/OmnibarSearchEngine.kt |
Fuzzy search engine with weighted ranking algorithm (recency, context, match quality) |
mpp-ui/.../omnibar/Omnibar.kt |
Main Compose UI component with 800dp floating layout, keyboard navigation, loading states |
mpp-ui/.../launch/XiuperLaunchScreen.kt |
Brand launch animation with phase-based timing, light effects, reduced motion support |
mpp-ui/.../theme/AutoDevAnimation.kt |
Animation system defining EaseXiu (fast UI), EaseStream (linear AI), and timing constants |
mpp-ui/.../theme/AutoDevColors.kt |
Added surfaceElevated, surfaceHover, border colors for Omnibar styling |
mpp-ui/.../icons/AutoDevComposeIcons.kt |
Added Terminal icon for Omnibar command type display |
mpp-ui/src/jvmMain/.../Main.kt |
Integrated splash screen with --skip-splash flag and reduced motion detection |
mpp-idea/.../omnibar/IdeaOmnibarDataProvider.kt |
IDEA-specific provider implementing built-in commands, custom commands, file search, recent items tracking |
mpp-idea/.../omnibar/IdeaOmnibarDialog.kt |
DialogWrapper with JewelComposePanel for native IDE integration using Jewel theme |
mpp-idea/.../omnibar/IdeaOmnibarAction.kt |
AnAction for triggering Omnibar with action result handlers (navigation, text insertion, LLM queries) |
mpp-idea/.../plugin.xml |
Registered Omnibar action with Ctrl/Cmd+Shift+P shortcuts and Tools menu entry |
mpp-idea/.../JewelRendererTest.kt |
Added missing TaskStatus import (unrelated import fix) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| onConfigureRemote = { | ||
| uiState.showRemoteConfigDialog = true | ||
| AutoDevLogger.info("AutoDevMain") { "☁️ Configure Remote" } | ||
| // 显示启动动画或主界面 |
There was a problem hiding this comment.
Comments should be in English for consistency with the rest of the codebase.
Suggested translations:
- Line 36: "Splash screen state"
- Line 63: "Show splash screen or main interface"
| // 显示启动动画或主界面 | |
| // Show splash screen or main interface |
| // 已由全局状态管理,无需额外操作 | ||
| }, | ||
| onSidebarVisibilityChanged = { visible -> | ||
| // 已由全局状态管理,无需额外操作 |
There was a problem hiding this comment.
Comments should be in English for consistency with the rest of the codebase.
Suggested translation: "Already managed by global state, no additional action needed"
| // 已由全局状态管理,无需额外操作 | |
| // Already managed by global state, no additional action needed |
| private val recentlyUsedItems = mutableListOf<OmnibarItem>() | ||
| private val maxRecentItems = 10 | ||
|
|
||
| override suspend fun getItems(): List<OmnibarItem> = withContext(Dispatchers.Default) { | ||
| val items = mutableListOf<OmnibarItem>() | ||
|
|
||
| // Add built-in commands | ||
| items.addAll(getBuiltinCommands()) | ||
|
|
||
| // Add custom commands from project | ||
| items.addAll(getCustomCommands()) | ||
|
|
||
| items | ||
| } | ||
|
|
||
| override suspend fun getRecentItems(): List<OmnibarItem> { | ||
| return recentlyUsedItems.toList() | ||
| } | ||
|
|
||
| override suspend fun executeAction(item: OmnibarItem): OmnibarActionResult { | ||
| return when (item.type) { | ||
| OmnibarItemType.COMMAND, OmnibarItemType.CUSTOM_COMMAND -> { | ||
| val commandText = "/${item.metadata["commandName"] ?: item.title}" | ||
| OmnibarActionResult.InsertText(commandText) | ||
| } | ||
| OmnibarItemType.FILE -> { | ||
| val path = item.metadata["path"] as? String ?: return OmnibarActionResult.Error("No file path") | ||
| OmnibarActionResult.Navigate(path) | ||
| } | ||
| OmnibarItemType.SYMBOL -> { | ||
| val path = item.metadata["path"] as? String ?: return OmnibarActionResult.Error("No symbol path") | ||
| val line = item.metadata["line"] as? Int ?: 0 | ||
| OmnibarActionResult.Navigate(path, line) | ||
| } | ||
| else -> OmnibarActionResult.Success("Action executed: ${item.title}") | ||
| } | ||
| } | ||
|
|
||
| override suspend fun recordUsage(item: OmnibarItem) { | ||
| val updatedItem = item.copy( | ||
| lastUsedTimestamp = System.currentTimeMillis(), | ||
| type = OmnibarItemType.RECENT | ||
| ) | ||
| recentlyUsedItems.removeAll { it.id == item.id } | ||
| recentlyUsedItems.add(0, updatedItem) | ||
| if (recentlyUsedItems.size > maxRecentItems) { | ||
| recentlyUsedItems.removeAt(recentlyUsedItems.lastIndex) | ||
| } | ||
| } |
There was a problem hiding this comment.
The recentlyUsedItems list is mutable and accessed without synchronization. Since recordUsage is a suspend function and can be called concurrently, this could lead to race conditions. Consider using a thread-safe collection like CopyOnWriteArrayList or adding synchronization to ensure thread safety.
| * Registered shortcuts: | ||
| * - Cmd+K (macOS) / Ctrl+K (Windows/Linux) |
There was a problem hiding this comment.
The documentation mentions "Cmd+K (macOS) / Ctrl+K (Windows/Linux)" as a registered shortcut, but the plugin.xml only registers "ctrl shift P" / "meta shift P". Either update the documentation to remove the Cmd+K/Ctrl+K reference, or add the keyboard shortcuts in plugin.xml if this was intended.
| * Registered shortcuts: | |
| * - Cmd+K (macOS) / Ctrl+K (Windows/Linux) | |
| * Registered shortcut: |
| package cc.unitmesh.devins.ui.compose.omnibar | ||
|
|
||
| import cc.unitmesh.agent.Platform | ||
|
|
||
| /** | ||
| * Search engine for Omnibar with fuzzy matching and weighted ranking. | ||
| * | ||
| * Ranking factors (in priority order): | ||
| * 1. Recency - recently used items get higher priority | ||
| * 2. Context relevance - items matching current context (open file type, etc.) | ||
| * 3. Match quality - exact > prefix > contains > fuzzy | ||
| * 4. Base weight - configured priority of item type | ||
| */ | ||
| class OmnibarSearchEngine { | ||
|
|
||
| companion object { | ||
| private const val RECENCY_WEIGHT = 100 | ||
| private const val RECENCY_DECAY_HOURS = 24 | ||
| private const val MAX_RESULTS = 50 | ||
| } | ||
|
|
||
| /** | ||
| * Search and rank items based on query and context. | ||
| * | ||
| * @param items All available items | ||
| * @param query Search query (can be empty for default ranking) | ||
| * @param contextTags Optional tags for context-aware ranking (e.g., "kotlin", "test") | ||
| * @return Ranked and filtered list of items | ||
| */ | ||
| fun search( | ||
| items: List<OmnibarItem>, | ||
| query: String, | ||
| contextTags: Set<String> = emptySet() | ||
| ): List<OmnibarItem> { | ||
| if (query.isBlank()) { | ||
| // No query - return items ranked by recency and weight | ||
| return items | ||
| .map { it to calculateRecencyBonus(it) } | ||
| .sortedByDescending { (item, recencyBonus) -> | ||
| item.weight + recencyBonus + contextBonus(item, contextTags) | ||
| } | ||
| .take(MAX_RESULTS) | ||
| .map { it.first } | ||
| } | ||
|
|
||
| // With query - filter and rank by match quality | ||
| return items | ||
| .map { item -> | ||
| val matchScore = item.matchScore(query) | ||
| val recencyBonus = calculateRecencyBonus(item) | ||
| val contextScore = contextBonus(item, contextTags) | ||
| item to (matchScore + recencyBonus + contextScore) | ||
| } | ||
| .filter { it.second > 0 } | ||
| .sortedByDescending { it.second } | ||
| .take(MAX_RESULTS) | ||
| .map { it.first } | ||
| } | ||
|
|
||
| /** | ||
| * Group items by category for display. | ||
| */ | ||
| fun groupByCategory(items: List<OmnibarItem>): Map<String, List<OmnibarItem>> { | ||
| return items.groupBy { item -> | ||
| item.category.ifBlank { | ||
| when (item.type) { | ||
| OmnibarItemType.COMMAND -> "Commands" | ||
| OmnibarItemType.CUSTOM_COMMAND -> "Custom Commands" | ||
| OmnibarItemType.FILE -> "Files" | ||
| OmnibarItemType.SYMBOL -> "Symbols" | ||
| OmnibarItemType.AGENT -> "Agents" | ||
| OmnibarItemType.RECENT -> "Recent" | ||
| OmnibarItemType.SETTING -> "Settings" | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Calculate recency bonus based on last used timestamp. | ||
| * Items used recently get higher scores, with exponential decay. | ||
| */ | ||
| private fun calculateRecencyBonus(item: OmnibarItem): Int { | ||
| if (item.lastUsedTimestamp == 0L) return 0 | ||
|
|
||
| val currentTime = Platform.getCurrentTimestamp() | ||
| val ageHours = (currentTime - item.lastUsedTimestamp) / (1000L * 60 * 60) | ||
|
|
||
| return when { | ||
| ageHours < 1 -> RECENCY_WEIGHT * 4 // Last hour | ||
| ageHours < 6 -> RECENCY_WEIGHT * 2 // Last 6 hours | ||
| ageHours < RECENCY_DECAY_HOURS -> RECENCY_WEIGHT // Last 24 hours | ||
| ageHours < 72 -> RECENCY_WEIGHT / 2 // Last 3 days | ||
| else -> 0 | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Calculate context bonus for items matching current context. | ||
| */ | ||
| private fun contextBonus(item: OmnibarItem, contextTags: Set<String>): Int { | ||
| if (contextTags.isEmpty()) return 0 | ||
|
|
||
| val itemTags = item.metadata["tags"] as? List<*> ?: return 0 | ||
| val matchCount = itemTags.count { tag -> | ||
| tag is String && contextTags.contains(tag.lowercase()) | ||
| } | ||
|
|
||
| return matchCount * 50 | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Extension function for fuzzy matching on strings. | ||
| * Returns true if all characters in query appear in text in order. | ||
| */ | ||
| fun String.fuzzyContains(query: String): Boolean { | ||
| if (query.isEmpty()) return true | ||
| var queryIndex = 0 | ||
| for (char in this) { | ||
| if (char.lowercaseChar() == query[queryIndex].lowercaseChar()) { | ||
| queryIndex++ | ||
| if (queryIndex == query.length) return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| /** | ||
| * Get fuzzy match positions for highlighting. | ||
| * Returns list of character indices that match the query. | ||
| */ | ||
| fun String.fuzzyMatchPositions(query: String): List<Int> { | ||
| if (query.isEmpty()) return emptyList() | ||
| val positions = mutableListOf<Int>() | ||
| var queryIndex = 0 | ||
| for ((index, char) in this.withIndex()) { | ||
| if (queryIndex < query.length && | ||
| char.lowercaseChar() == query[queryIndex].lowercaseChar()) { | ||
| positions.add(index) | ||
| queryIndex++ | ||
| } | ||
| } | ||
| return if (queryIndex == query.length) positions else emptyList() | ||
| } | ||
|
|
There was a problem hiding this comment.
The new Omnibar search engine and fuzzy matching logic (OmnibarSearchEngine, fuzzy matching extensions) lack test coverage. Given that there are existing test directories (e.g., mpp-ui/src/commonTest/kotlin/cc/unitmesh/devins/ui/compose/) and other similar components have tests, consider adding unit tests to verify:
- Fuzzy matching correctness (fuzzyContains, fuzzyMatchPositions)
- Match score calculations
- Recency bonus calculations
- Context bonus calculations
- Search ranking behavior
| override suspend fun recordUsage(item: OmnibarItem) { | ||
| val updatedItem = item.copy( | ||
| lastUsedTimestamp = System.currentTimeMillis(), | ||
| type = OmnibarItemType.RECENT |
There was a problem hiding this comment.
The recordUsage method modifies the item type to RECENT when recording usage (line 66). This causes an issue because when a user uses the same command again from recent items, it will be stored with type RECENT instead of preserving its original type (e.g., COMMAND or CUSTOM_COMMAND). Consider preserving the original type or adding a separate flag to indicate if an item appears in recent history.
| type = OmnibarItemType.RECENT | |
| isRecent = true |
| private fun navigateToPath(project: com.intellij.openapi.project.Project, path: String, line: Int) { | ||
| val file = LocalFileSystem.getInstance().findFileByPath(path) ?: return | ||
| val fileEditorManager = FileEditorManager.getInstance(project) | ||
| fileEditorManager.openFile(file, true) | ||
|
|
||
| if (line > 0) { | ||
| val editor = fileEditorManager.selectedTextEditor | ||
| editor?.caretModel?.moveToLogicalPosition( | ||
| com.intellij.openapi.editor.LogicalPosition(line - 1, 0) | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
The navigateToPath function attempts to open a file without validating whether the file exists or is accessible. If the file doesn't exist, findFileByPath returns null and the function silently returns. Consider adding error handling or notification to inform the user when file navigation fails, especially since this is triggered from user actions in the Omnibar.
| private fun insertTextToActiveInput(project: com.intellij.openapi.project.Project, text: String) { | ||
| // Try to find and use AutoInputService or similar | ||
| try { | ||
| val serviceClass = Class.forName("cc.unitmesh.devti.sketch.AutoInputService") | ||
| val getInstance = serviceClass.getMethod("getInstance", com.intellij.openapi.project.Project::class.java) | ||
| val service = getInstance.invoke(null, project) | ||
| val putText = serviceClass.getMethod("putText", String::class.java) | ||
| putText.invoke(service, text) | ||
| } catch (e: Exception) { | ||
| // Fallback: Could show in a notification or editor | ||
| } | ||
| } |
There was a problem hiding this comment.
The insertTextToActiveInput method uses reflection to dynamically call AutoInputService. While this provides flexibility, the catch block silently swallows all exceptions without logging or user feedback. Consider at minimum logging the exception for debugging purposes, or providing user feedback when text insertion fails.
| /** | ||
| * Group items by category for display. | ||
| */ | ||
| fun groupByCategory(items: List<OmnibarItem>): Map<String, List<OmnibarItem>> { | ||
| return items.groupBy { item -> | ||
| item.category.ifBlank { | ||
| when (item.type) { | ||
| OmnibarItemType.COMMAND -> "Commands" | ||
| OmnibarItemType.CUSTOM_COMMAND -> "Custom Commands" | ||
| OmnibarItemType.FILE -> "Files" | ||
| OmnibarItemType.SYMBOL -> "Symbols" | ||
| OmnibarItemType.AGENT -> "Agents" | ||
| OmnibarItemType.RECENT -> "Recent" | ||
| OmnibarItemType.SETTING -> "Settings" | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
[nitpick] The groupByCategory function is defined but never used in the codebase. If this functionality is intended for future use, consider adding a TODO comment. Otherwise, consider removing it to reduce code maintenance burden, or integrate it into the UI if categorized display was intended.
| AutoDevLogger.info("AutoDevMain") { "📁 Log files location: ${AutoDevLogger.getLogDirectory()}" } | ||
|
|
||
| val mode = args.find { it.startsWith("--mode=") }?.substringAfter("--mode=") ?: "auto" | ||
| // 检查是否跳过启动动画(通过命令行参数) |
There was a problem hiding this comment.
Comments should be in English for consistency with the rest of the codebase.
Suggested translation: "Check if splash screen should be skipped (via command line argument)"
| // 检查是否跳过启动动画(通过命令行参数) | |
| // Check if splash screen should be skipped (via command line argument) |
Integrate ComposeOmnibarDataProvider and enable Cmd/Ctrl+K shortcut to open Omnibar in Compose desktop app. Adds built-in commands and settings actions.
- Add ComposeOmnibarDataProvider for mpp-ui with built-in commands and settings - Integrate Omnibar in AutoDevApp.kt with Cmd+K/Ctrl+K keyboard shortcut - Create Omnibar.tsx and Omnibar.css for VSCode webview - Add keyboard shortcut (Cmd+K/Ctrl+K) to open Omnibar in VSCode - Update useVSCode.ts with Omnibar message types
Refine and expand agent-related information in AGENTS.md.
Summary
Implements GitHub Issue #495 - Omnibar command palette similar to Linear and Raycast.
Features
Changes
mpp-ui (Compose Multiplatform - Shared Layer)
mpp-idea (IntelliJ IDEA Plugin)
Build Configuration
Usage
In IntelliJ IDEA:
Closes #495
Summary by CodeRabbit
New Features
Improvements
Chores
Docs
✏️ Tip: You can customize this high-level summary in your review settings.