Skip to content

Conversation

@phodal
Copy link
Member

@phodal phodal commented Dec 2, 2025

Summary

This PR adds file selection functionality to IdeaTopToolbar and fixes the right-side config button to open MCP configuration instead of model configuration.

Changes

1. IdeaFileSearchPopup (New)

  • Created new Compose/Jewel-based file search dialog
  • Supports searching project files using FilenameIndex
  • Prioritizes recently opened files using EditorHistoryManager
  • Multi-select files with checkbox UI
  • Filters binary and ignored files

2. IdeaTopToolbar

  • Added project parameter for file search
  • Added onFilesSelected callback for selected files
  • Click "Add File" button now opens file search popup
  • Selected files displayed as chips with remove functionality

3. IdeaBottomToolbar

  • Changed right-side settings button from model config to MCP config
  • MCP config dialog now opens directly from the settings button
  • Left side already has model selector, so this avoids duplication

4. IdeaAgentApp

  • Added IdeaTopToolbar to input area
  • Added selected files state management
  • Appends file paths to message on send (format: /file:path)
  • Removed onSettingsClick parameter (MCP config handled internally)

Testing

  • Compilation passes successfully
  • Manual testing recommended for UI interactions

Pull Request opened by Augment Code with guidance from the PR author

Summary by CodeRabbit

  • New Features
    • File search popup to find and add files/folders to your workspace.
    • Unified "Add File" button in the toolbar to open the file picker and manage selected files.
    • Selected files are appended to messages when sending and cleared after send.
    • MCP Settings renamed and exposed as an MCP Configuration dialog.
    • Added a new History icon for toolbar use.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add IdeaFileSearchPopup for adding files to context
  - Support searching project files using FilenameIndex
  - Prioritize recently opened files using EditorHistoryManager
  - Multi-select files with checkbox UI
  - Filter binary and ignored files

- Update IdeaTopToolbar with file selection functionality
  - Add project parameter for file search
  - Add onFilesSelected callback for selected files
  - Show file search popup on Add File button click

- Fix IdeaBottomToolbar right-side config button
  - Change from model config to MCP config dialog
  - MCP config dialog now opens directly from settings button

- Update IdeaDevInInputArea in IdeaAgentApp
  - Add IdeaTopToolbar to input area
  - Manage selected files state
  - Append file paths to message on send (/file:path format)
@coderabbitai
Copy link

coderabbitai bot commented Dec 2, 2025

Walkthrough

Refactors toolbars and input area to centralize file selection: adds a file search popup and presentation models, replaces multiple top-toolbar actions with a single Add File flow, renames MCP Settings to MCP Configuration with an in-place dialog, and integrates selected files into the message-send flow.

Changes

Cohort / File(s) Summary
Bottom toolbar / MCP config
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt
Renamed MCP text to "MCP Configuration"; removed external onSettingsClick parameter; added internal showMcpConfigDialog state and rendering of an MCP Configuration dialog that can be opened/closed
File search popup & models
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
New composable IdeaFileSearchPopup with recent-files loading, project-scoped search, filtering/capping, IdeaFilePresentation and SearchResults data classes, and internal item renderers
Top toolbar / Add File flow
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt
Replaced discrete clipboard/save/cursor actions with single Add File button; added project: Project? param and onFilesSelected: (List<VirtualFile>) -> Unit; introduced internal showFileSearchPopup state and popup gating by project
Agent app integration
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
Removed onSettingsClick from IdeaDevInInputArea API; added IdeaTopToolbar rendering, selectedFiles state, selection/removal handling; appends /file:<path> entries to outgoing messages and clears selected files after send
Icons
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt
Added new public History: ImageVector icon definition

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant TopToolbar as Top Toolbar
  participant FilePopup as File Search Popup
  participant Project as Project (IDE)
  participant DevIn as DevIn Input Area
  participant MessageFlow as Message Composition

  User->>TopToolbar: Click "Add File"
  TopToolbar->>FilePopup: Show (project supplied)
  FilePopup->>Project: Load recent files / search request
  Project-->>FilePopup: Recent files / search results
  User->>FilePopup: Select files & confirm
  FilePopup-->>TopToolbar: onFilesSelected(files)
  TopToolbar->>DevIn: Update selectedFiles state
  User->>DevIn: Compose message and send
  DevIn->>MessageFlow: Append /file:<path> for each selected file
  MessageFlow->>DevIn: Send combined message
  DevIn->>DevIn: Clear selectedFiles
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Files needing extra attention:
    • IdeaFileSearchPopup.kt — I/O, read actions, filtering logic, and Compose state/async patterns.
    • IdeaTopToolbar.kt — public API changes (params removed/added) and popup gating.
    • IdeaAgentApp.kt — wiring selectedFiles into message composition and send behavior.
    • IdeaBottomToolbar.kt — verify dialog lifecycle and accessibility text changes.

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped through toolbars, nibbling at the bits,
Found files to fetch and dialogs to fit,
One Add button now, a tidy little trick—
Search, select, send; the workflow’s quick! 📁✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two main changes: adding a file search popup and fixing the MCP config button behavior.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/idea-file-search-and-mcp-config

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Comment augment review to trigger a new review at any time.

val scope = GlobalSearchScope.projectScope(project)

try {
ApplicationManager.getApplication().runReadAction {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This executes an index search inside a blocking read action triggered from a LaunchedEffect, which likely runs on the EDT and can freeze the UI on large projects; this risks violating the platform’s EDT rules. (Guideline: edt_violations)

🤖 Was this useful? React with 👍 or 👎

isExecuting: Boolean = false,
onStopClick: () -> Unit = {},
onSettingsClick: () -> Unit = {},
onMcpConfigClick: () -> Unit = {},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onMcpConfigClick is declared but never used in this composable, which makes the API misleading for callers. (Guideline: kotlin_code_quality)

🤖 Was this useful? React with 👍 or 👎

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (1)

36-36: Consider removing unused parameter.

The onMcpConfigClick parameter is never invoked since the MCP configuration dialog is now managed internally via the showMcpConfigDialog state. If this parameter was retained for API compatibility, consider documenting that intent; otherwise, it can be safely removed to reduce maintenance burden.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt (2)

38-61: Avoid duplicate loadRecentFiles calls and consider adding basic query debounce

Both LaunchedEffect(Unit) and LaunchedEffect(searchQuery) call loadRecentFiles(project) on initial composition, which does the same work twice, and searches fire on every keystroke once length ≥ 2. Consider collapsing the initial load into the query-driven effect (e.g., seed query state or guard the second effect) and optionally adding a small debounce before calling searchFiles to reduce redundant work on large projects.


231-281: Run file search off the UI thread and add minimal logging for failures

searchFiles wraps FilenameIndex.processFilesByName in runReadAction but runs directly in the LaunchedEffect coroutine, which is typically on the UI dispatcher for Compose. On large projects this can block the IDE. Also, both loadRecentFiles and searchFiles swallow all exceptions silently.

Consider:

  • Switching the search to a background dispatcher and using a non-blocking read action:
 private fun searchFiles(project: Project, query: String): List<IdeaFilePresentation> {
-    val results = mutableListOf<IdeaFilePresentation>()
-    val scope = GlobalSearchScope.projectScope(project)
+    val results = mutableListOf<IdeaFilePresentation>()
+    val scope = GlobalSearchScope.projectScope(project)

-    try {
-        ApplicationManager.getApplication().runReadAction {
-            FilenameIndex.processFilesByName(query, false, scope) { file ->
-                if (canBeAdded(project, file) && results.size < 50) {
-                    results.add(IdeaFilePresentation.from(project, file))
-                }
-                results.size < 50
-            }
-        }
-    } catch (e: Exception) {
-        // Ignore search errors
-    }
+    try {
+        ApplicationManager.getApplication().runReadAction {
+            FilenameIndex.processFilesByName(query, false, scope) { file ->
+                if (canBeAdded(project, file) && results.size < 50) {
+                    results.add(IdeaFilePresentation.from(project, file))
+                }
+                results.size < 50
+            }
+        }
+    } catch (e: Exception) {
+        // TODO: replace with proper IntelliJ logger
+        // thisLogger().warn("Error searching files for query: $query", e)
+    }
  • Applying similar minimal logging in loadRecentFiles instead of fully ignoring failures.

This keeps the UI responsive and makes diagnosing plugin issues easier.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt (1)

56-64: Clarify the role of onAddFileClick now that selection flows via onFilesSelected

The Add File button now:

if (project != null) {
    showFileSearchPopup = true
}
onAddFileClick()

With the new onFilesSelected callback handling actual file selection from IdeaFileSearchPopup, onAddFileClick appears to be an auxiliary hook (e.g., analytics or legacy behavior). If it no longer has a distinct responsibility, consider deprecating or removing it to keep the API focused; else, document its expected use to avoid confusion for future callers.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt (3)

334-357: File selection state handling looks solid; check popup/z-order setup

selectedFiles is managed idiomatically (immutable updates, dedup by path) and the IdeaTopToolbar wiring for add/remove is clean and predictable.

Because this composable also embeds a SwingPanel and IdeaTopToolbar ultimately shows a Compose-based file search popup, please double‑check that:

  • IdeaFileSearchPopup is built on Jewel popup/dialog primitives (e.g., DialogWrapper/Jewel PopupMenu, not androidx.compose.ui.window.Popup), and
  • JewelFlags.useCustomPopupRenderer = true is enabled in IdeaAgentToolWindowFactory,

to avoid popups being rendered behind the Swing content. Based on learnings, this is the recommended setup for **/idea/**/*.kt.


377-391: Appending /file: lines on editor submit is correct but duplicates bottom‑toolbar logic

The new onSubmit path correctly appends one /file:<path> line per selected file and clears selectedFiles afterwards, mirroring the bottom toolbar behavior.

To keep these paths from drifting, consider extracting a small helper (e.g., fun buildFullText(text, selectedFiles)) and reusing it in both onSubmit and the bottom toolbar handler.


420-437: Unify send condition with attachments and reduce duplication in bottom toolbar

The bottom toolbar send path correctly mirrors the editor submit logic for appending /file: lines and clearing selectedFiles. Two optional improvements:

  1. Allow “files‑only” sends (if desired):
    Right now sendEnabled and the send guard both require non‑blank text. If you want users to be able to send just selected files, you could relax this:
-            onSendClick = {
-                val text = devInInput?.text?.trim() ?: inputText.trim()
-                if (text.isNotBlank() && !isProcessing) {
+            onSendClick = {
+                val text = devInInput?.text?.trim() ?: inputText.trim()
+                if ((text.isNotBlank() || selectedFiles.isNotEmpty()) && !isProcessing) {
@@
-            sendEnabled = inputText.isNotBlank() && !isProcessing,
+            sendEnabled = (inputText.isNotBlank() || selectedFiles.isNotEmpty()) && !isProcessing,
  1. Avoid duplicating message construction:
    As with the editor submit path, consider factoring out the filesText/fullText construction into a shared helper to keep behavior in sync.

Also, the comment says “MCP config is handled internally”, but onConfigureClick is still threaded through to IdeaBottomToolbar. If IdeaBottomToolbar no longer uses this callback, you can drop the parameter from IdeaDevInInputArea to simplify the API; if it still does, it may be worth renaming/clarifying its purpose.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a8534f and 0400c8e.

📒 Files selected for processing (4)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (5 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt (4 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use expect/actual for platform-specific code (e.g., file I/O on JVM/JS/Wasm) in Kotlin Multiplatform projects
Check export first if some functions are not working well with CLI (TypeScript)
In Kotlin/JS @JsExport: Avoid Flow, use Promise instead
In Kotlin/JS @JsExport: Use concrete classes as return types and parameter types; avoid interface types
For WASM platform, avoid using emoji and UTF-8 in code

Files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
**/idea/**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/idea/**/*.kt: For SwingPanel z-index issues with Compose popups, enable Jewel's custom popup renderer: JewelFlags.useCustomPopupRenderer = true in IdeaAgentToolWindowFactory
For popup/dropdown menus in IntelliJ plugins, use Jewel's PopupMenu instead of androidx.compose.ui.window.Popup
For dialogs in IntelliJ plugins, use IntelliJ's DialogWrapper with org.jetbrains.jewel.bridge.compose instead of androidx.compose.ui.window.Dialog

Files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
🧠 Learnings (5)
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/idea/**/*.kt : For SwingPanel z-index issues with Compose popups, enable Jewel's custom popup renderer: `JewelFlags.useCustomPopupRenderer = true` in `IdeaAgentToolWindowFactory`

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/idea/**/*.kt : For dialogs in IntelliJ plugins, use IntelliJ's `DialogWrapper` with `org.jetbrains.jewel.bridge.compose` instead of `androidx.compose.ui.window.Dialog`

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/idea/**/*.kt : For popup/dropdown menus in IntelliJ plugins, use Jewel's `PopupMenu` instead of `androidx.compose.ui.window.Popup`

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/compose/**/*.{kt,kts} : In Compose (Desktop/Android), use `AutoDevColors` from `cc.unitmesh.devins.ui.compose.theme` or `MaterialTheme.colorScheme`

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to {**/compose/**/*.kt,mpp-ui/src/jsMain/typescript/**/*.{ts,tsx}} : DO NOT hardcode colors (e.g., `Color(0xFF...)` or `#hex`). Always use design tokens for consistency across platforms

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt
🧬 Code graph analysis (3)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaMcpConfigDialog.kt (1)
  • IdeaMcpConfigDialog (64-328)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt (1)
  • IdeaTopToolbar (31-100)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt (1)
  • IdeaFileSearchPopup (32-140)
⏰ 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). (2)
  • GitHub Check: Build (241)
  • GitHub Check: Build (223)
🔇 Additional comments (8)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (4)

22-29: LGTM! Documentation accurately reflects the MCP configuration changes.

The updated comments clearly describe the new MCP configuration functionality and maintain consistency with the code changes.


46-46: LGTM! Proper Compose state management.

The state variable correctly manages dialog visibility using standard Compose patterns.


85-96: LGTM! Button correctly triggers dialog.

The MCP Configuration button properly opens the dialog by updating the state variable. The icon, description, and styling are appropriate.


160-167: Replace androidx.compose.ui.window.Dialog with DialogWrapper for IntelliJ plugin compliance.

The IdeaMcpConfigDialog at line 173 uses androidx.compose.ui.window.Dialog, which violates IntelliJ plugin guidelines. Per coding standards, IntelliJ plugin dialogs must use DialogWrapper with org.jetbrains.jewel.bridge.compose to ensure proper IDE integration and avoid rendering issues.

Replace the direct Dialog composable with DialogWrapper for IDE UI framework compliance.

⛔ Skipped due to learnings
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/idea/**/*.kt : For dialogs in IntelliJ plugins, use IntelliJ's `DialogWrapper` with `org.jetbrains.jewel.bridge.compose` instead of `androidx.compose.ui.window.Dialog`
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/idea/**/*.kt : For SwingPanel z-index issues with Compose popups, enable Jewel's custom popup renderer: `JewelFlags.useCustomPopupRenderer = true` in `IdeaAgentToolWindowFactory`
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/idea/**/*.kt : For popup/dropdown menus in IntelliJ plugins, use Jewel's `PopupMenu` instead of `androidx.compose.ui.window.Popup`
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt (1)

142-199: File list item composition and selection wiring look solid

The row-level clickable and checkbox both delegate to the same onClick, visual selection state is derived from isSelected, and Jewel theme tokens are used consistently. This item should behave as expected in the list with good UX.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt (2)

31-43: API extension for project and file selection callback is reasonable

Adding project: Project? = null, onFilesSelected: (List<VirtualFile>) -> Unit = {}, and internal showFileSearchPopup keeps the signature source-compatible while enabling the new file search flow. This is a clean way to evolve the toolbar without breaking existing callers.


89-99: Popup wiring and dismissal behavior look correct

The conditional popup:

  • Guards on both showFileSearchPopup and project != null.
  • Properly closes on dismiss and after onFilesSelected(files).

This ensures the dialog lifecycle is tied to toolbar state and avoids null-project misuse.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt (1)

16-17: New editor imports are consistent with later usage

IdeaTopToolbar and SelectedFileItem are imported and used as expected in IdeaDevInInputArea; nothing to change here.

Comment on lines 63 to 139
Dialog(onDismissRequest = onDismiss) {
Column(
modifier = Modifier
.width(500.dp)
.height(400.dp)
.clip(RoundedCornerShape(8.dp))
.background(JewelTheme.globalColors.panelBackground)
.padding(16.dp)
) {
// Header
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("Add Files to Context", style = JewelTheme.defaultTextStyle.copy(fontSize = 16.sp))
IconButton(onClick = onDismiss) {
Icon(
imageVector = IdeaComposeIcons.Close,
contentDescription = "Close",
tint = JewelTheme.globalColors.text.normal,
modifier = Modifier.size(16.dp)
)
}
}

Spacer(modifier = Modifier.height(12.dp))

// Search field using Jewel's TextField with TextFieldState
TextField(
state = searchQueryState,
placeholder = { Text("Search files...") },
modifier = Modifier.fillMaxWidth()
)

Spacer(modifier = Modifier.height(12.dp))

// File list
if (isLoading) {
Box(modifier = Modifier.fillMaxWidth().weight(1f), contentAlignment = Alignment.Center) {
Text("Loading...")
}
} else {
LazyColumn(modifier = Modifier.fillMaxWidth().weight(1f)) {
items(searchResults) { file ->
FileListItem(
file = file,
isSelected = file.virtualFile in selectedFiles,
onClick = {
selectedFiles = if (file.virtualFile in selectedFiles) {
selectedFiles - file.virtualFile
} else {
selectedFiles + file.virtualFile
}
}
)
}
}
}

Spacer(modifier = Modifier.height(12.dp))

// Footer with action buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End)
) {
OutlinedButton(onClick = onDismiss) { Text("Cancel") }
DefaultButton(
onClick = { onFilesSelected(selectedFiles.toList()) },
enabled = selectedFiles.isNotEmpty()
) {
Text("Add ${if (selectedFiles.isNotEmpty()) "(${selectedFiles.size})" else ""}")
}
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Replace Compose Dialog with IntelliJ DialogWrapper + Jewel bridge

This dialog is using androidx.compose.ui.window.Dialog, which is discouraged for IntelliJ plugins because of focus/z-order and window management issues. The project guidelines specify using IntelliJ’s DialogWrapper with org.jetbrains.jewel.bridge.compose for dialogs instead.

Refactor this to a DialogWrapper-backed dialog, e.g.:

  • Introduce a IdeaFileSearchDialog(project, onFilesSelected) class extending DialogWrapper and building its center panel with the Jewel compose bridge.
  • Extract the current Column UI into a separate @Composable (e.g., IdeaFileSearchContent(...)) used inside that bridge.
  • From callers (like IdeaTopToolbar), open the dialog via the new wrapper instead of a composable Dialog.

This will align with IntelliJ’s dialog lifecycle and avoid Swing/Compose interop glitches. As per coding guidelines / learnings for **/idea/**/*.kt.

- Fix EDT violation: move searchAllItems to background thread using Dispatchers.IO
- Remove unused onMcpConfigClick parameter from IdeaBottomToolbar
- Add History icon to IdeaComposeIcons for recently opened files menu
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt (2)

387-410: Consider using suspending readAction instead of blocking runReadAction.

Since searchAllItems is called from a coroutine context on Dispatchers.IO, you can use the suspending readAction function (already imported at line 15) instead of the blocking runReadAction. This is more idiomatic for coroutine-based code.

Apply this diff:

     try {
-        ApplicationManager.getApplication().runReadAction {
+        readAction {
             // Search files by name
             FilenameIndex.processFilesByName(query, false, scope) { file ->
                 if (file.isDirectory) {

Note: You'll need to mark this function as suspend:

-private fun searchAllItems(project: Project, query: String): SearchResults {
+private suspend fun searchAllItems(project: Project, query: String): SearchResults {

416-418: Optional: Avoid redundant loadRecentFiles call.

loadRecentFiles is already called in the initial LaunchedEffect(Unit) at line 73. When a search query is entered, this function is called again at line 416, unnecessarily re-loading the recent files. Consider passing the already-loaded recent files as a parameter to searchAllItems or filtering them in the caller.

Example refactor:

-private suspend fun searchAllItems(project: Project, query: String): SearchResults {
+private suspend fun searchAllItems(project: Project, query: String, recentFiles: List<IdeaFilePresentation>): SearchResults {
     val files = mutableListOf<IdeaFilePresentation>()
     val folders = mutableListOf<IdeaFilePresentation>()
     val scope = GlobalSearchScope.projectScope(project)
     val lowerQuery = query.lowercase()
     
     // ... existing search logic ...
     
     // Filter recent files by query
-    val recentFiles = loadRecentFiles(project).filter {
+    val filteredRecent = recentFiles.filter {
         it.name.lowercase().contains(lowerQuery) || it.presentablePath.lowercase().contains(lowerQuery)
     }
     
     return SearchResults(
         files = files.sortedBy { it.name },
         folders = folders.sortedBy { it.presentablePath },
-        recentFiles = recentFiles
+        recentFiles = filteredRecent
     )
 }

Then update the call at line 61:

-                searchAllItems(project, searchQuery)
+                searchAllItems(project, searchQuery, recentFiles)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0400c8e and 411f614.

📒 Files selected for processing (3)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (4 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt
🧰 Additional context used
📓 Path-based instructions (2)
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use expect/actual for platform-specific code (e.g., file I/O on JVM/JS/Wasm) in Kotlin Multiplatform projects
Check export first if some functions are not working well with CLI (TypeScript)
In Kotlin/JS @JsExport: Avoid Flow, use Promise instead
In Kotlin/JS @JsExport: Use concrete classes as return types and parameter types; avoid interface types
For WASM platform, avoid using emoji and UTF-8 in code

Files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt
**/idea/**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/idea/**/*.kt: For SwingPanel z-index issues with Compose popups, enable Jewel's custom popup renderer: JewelFlags.useCustomPopupRenderer = true in IdeaAgentToolWindowFactory
For popup/dropdown menus in IntelliJ plugins, use Jewel's PopupMenu instead of androidx.compose.ui.window.Popup
For dialogs in IntelliJ plugins, use IntelliJ's DialogWrapper with org.jetbrains.jewel.bridge.compose instead of androidx.compose.ui.window.Dialog

Files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt
🧠 Learnings (5)
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/idea/**/*.kt : For popup/dropdown menus in IntelliJ plugins, use Jewel's `PopupMenu` instead of `androidx.compose.ui.window.Popup`

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/idea/**/*.kt : For SwingPanel z-index issues with Compose popups, enable Jewel's custom popup renderer: `JewelFlags.useCustomPopupRenderer = true` in `IdeaAgentToolWindowFactory`

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/idea/**/*.kt : For dialogs in IntelliJ plugins, use IntelliJ's `DialogWrapper` with `org.jetbrains.jewel.bridge.compose` instead of `androidx.compose.ui.window.Dialog`

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to **/compose/**/*.{kt,kts} : In Compose (Desktop/Android), use `AutoDevColors` from `cc.unitmesh.devins.ui.compose.theme` or `MaterialTheme.colorScheme`

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
📚 Learning: 2025-12-02T00:20:34.480Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-02T00:20:34.480Z
Learning: Applies to {**/compose/**/*.kt,mpp-ui/src/jsMain/typescript/**/*.{ts,tsx}} : DO NOT hardcode colors (e.g., `Color(0xFF...)` or `#hex`). Always use design tokens for consistency across platforms

Applied to files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt
⏰ 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 (223)
  • GitHub Check: Build (241)
🔇 Additional comments (4)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt (1)

1543-1584: LGTM!

The History icon implementation follows the established pattern in this file perfectly. The icon is properly documented, uses lazy initialization, and maintains consistency with dimensions (24dp × 24dp), viewport (24×24), and styling (black fill) used by all other icons in this provider.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt (3)

38-268: LGTM! Correct use of Jewel's PopupMenu for IntelliJ plugin.

The implementation correctly uses PopupMenu from Jewel for the file search popup, which aligns with the coding guidelines for popup/dropdown menus in IntelliJ plugins. The structure and interaction patterns are well-designed.

As per coding guidelines and learnings for **/idea/**/*.kt.


58-76: LGTM! Proper EDT offloading with IO dispatcher.

Both search operations and initial file loading correctly use withContext(Dispatchers.IO) to avoid blocking the UI thread. This ensures responsive UI even with large projects.


270-441: LGTM! Well-structured data models and helper functions.

The helper composables, data classes, and utility functions are well-designed:

  • IdeaFilePresentation provides a clean abstraction with a convenient factory method
  • loadRecentFiles properly handles errors and filters invalid files
  • canBeAdded includes comprehensive filtering for binary files, ignored files, and files outside project content
  • The binary extension list is reasonable and covers common cases

Copilot finished reviewing on behalf of phodal December 2, 2025 12:22
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds file search functionality to the IDEA plugin's input area and reorganizes the toolbar configuration. The changes introduce a new file search popup using IntelliJ's FilenameIndex API, integrate it with the top toolbar, and shift the settings button from model configuration to MCP configuration to avoid duplication (since the left side already has model selection).

  • New Compose-based file search popup with recent files prioritization
  • File selection chips UI with remove functionality
  • MCP configuration now accessible directly from bottom toolbar
  • Selected files automatically appended to messages using /file:path format

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
IdeaComposeIcons.kt Adds History icon for recent files display
IdeaFileSearchPopup.kt New file search dialog with IntelliJ FilenameIndex integration and recent file support
IdeaTopToolbar.kt Simplified to single "Add File" button, removed unused buttons, integrated file search popup
IdeaBottomToolbar.kt Changed settings button from model config to MCP config dialog
IdeaAgentApp.kt Added file selection state management and automatic file path appending to messages

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* Contains @ trigger, file selection, and other context-related actions.
*
*
* Layout: @ - / - Clipboard - Save - Cursor | Selected Files... | Add
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on line 29 describing the layout is outdated and no longer matches the actual implementation. The toolbar now only contains an "Add File" button, not "@ - / - Clipboard - Save - Cursor | Selected Files... | Add". Update the comment to reflect the current layout: "Add File | Selected Files..."

Suggested change
* Layout: @ - / - Clipboard - Save - Cursor | Selected Files... | Add
* Layout: Add File | Selected Files...

Copilot uses AI. Check for mistakes.
Comment on lines +387 to +410
ApplicationManager.getApplication().runReadAction {
// Search files by name
FilenameIndex.processFilesByName(query, false, scope) { file ->
if (file.isDirectory) {
if (folders.size < 20) {
folders.add(IdeaFilePresentation.from(project, file))
}
} else if (canBeAdded(project, file) && files.size < 50) {
files.add(IdeaFilePresentation.from(project, file))
}
files.size < 50 || folders.size < 20
}

// Also search for folders containing the query
val fileIndex = ProjectFileIndex.getInstance(project)
fileIndex.iterateContent { file ->
if (file.isDirectory && file.name.lowercase().contains(lowerQuery)) {
if (folders.size < 20 && !folders.any { it.path == file.path }) {
folders.add(IdeaFilePresentation.from(project, file))
}
}
folders.size < 20
}
}
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code performs read operations on IntelliJ's PSI/VFS inside a runReadAction block while already executing on the IO dispatcher via withContext(Dispatchers.IO). This is incorrect - runReadAction should be called directly from a suspending context (not from IO dispatcher), or you should use ApplicationManager.getApplication().executeOnPooledThread() for background operations.

The proper pattern for IntelliJ read actions in coroutines is to use readAction { } from com.intellij.openapi.application package (which is already imported but not used), which properly handles the dispatching. Change line 387 from ApplicationManager.getApplication().runReadAction { to readAction { and remove the withContext(Dispatchers.IO) wrapper at line 59, as readAction already runs on an appropriate background thread.

Copilot uses AI. Check for mistakes.
} else if (canBeAdded(project, file) && files.size < 50) {
files.add(IdeaFilePresentation.from(project, file))
}
files.size < 50 || folders.size < 20
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for continuing iteration is incorrect. The lambda should return true to continue processing and false to stop. Currently, line 397 returns true only when both conditions are met (files.size < 50 || folders.size < 20), meaning it stops when EITHER limit is reached. This should be && (AND) instead of || (OR) to continue only when BOTH haven't reached their limits yet, or return true unconditionally since you're already checking limits inside the conditions above.

Suggested change
files.size < 50 || folders.size < 20
files.size < 50 && folders.size < 20

Copilot uses AI. Check for mistakes.
folders.add(IdeaFilePresentation.from(project, file))
}
}
folders.size < 20
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The iteration should continue until the folders limit is reached, but this returns true only when folders.size < 20, which means it stops as soon as 20 folders are found. However, the logic seems backwards - the processor expects true to continue and false to stop. This should return true unconditionally or check folders.size < 20 to determine when to stop (return false when limit reached). The current logic will stop iteration prematurely once 20 folders are collected.

Copilot uses AI. Check for mistakes.
Comment on lines +364 to +377
val recentFiles = mutableListOf<IdeaFilePresentation>()

try {
val fileList = EditorHistoryManager.getInstance(project).fileList
fileList.take(30)
.filter { it.isValid && !it.isDirectory && canBeAdded(project, it) }
.forEach { file ->
recentFiles.add(IdeaFilePresentation.from(project, file, isRecent = true))
}
} catch (e: Exception) {
// Ignore errors loading recent files
}

return recentFiles
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadRecentFiles is called inside withContext(Dispatchers.IO) but internally accesses IntelliJ APIs (EditorHistoryManager.getInstance(project).fileList) that should be accessed from a read action. These VFS operations need read access and should use readAction { } instead of running on the IO dispatcher. Consider wrapping the relevant operations in a readAction block or refactoring to use the proper threading model.

Suggested change
val recentFiles = mutableListOf<IdeaFilePresentation>()
try {
val fileList = EditorHistoryManager.getInstance(project).fileList
fileList.take(30)
.filter { it.isValid && !it.isDirectory && canBeAdded(project, it) }
.forEach { file ->
recentFiles.add(IdeaFilePresentation.from(project, file, isRecent = true))
}
} catch (e: Exception) {
// Ignore errors loading recent files
}
return recentFiles
return readAction {
val recentFiles = mutableListOf<IdeaFilePresentation>()
try {
val fileList = EditorHistoryManager.getInstance(project).fileList
fileList.take(30)
.filter { it.isValid && !it.isDirectory && canBeAdded(project, it) }
.forEach { file ->
recentFiles.add(IdeaFilePresentation.from(project, file, isRecent = true))
}
} catch (e: Exception) {
// Ignore errors loading recent files
}
recentFiles
}

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +61
}
onAddFileClick()
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The onAddFileClick() callback is invoked regardless of whether project is null. This means the callback is always triggered even when the file search popup cannot be shown (when project is null). Consider only calling onAddFileClick() when the popup can actually be displayed, or move it inside the if (project != null) block.

Suggested change
}
onAddFileClick()
onAddFileClick()
}

Copilot uses AI. Check for mistakes.
fun IdeaTopToolbar(
project: Project? = null,
onAtClick: () -> Unit = {},
onSlashClick: () -> Unit = {},
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter onSlashClick is defined but never used in the function body. All slash-related UI elements have been removed in this PR. Consider removing this unused parameter to clean up the API.

Suggested change
onSlashClick: () -> Unit = {},

Copilot uses AI. Check for mistakes.
@phodal phodal merged commit 98ead2f into master Dec 2, 2025
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants