Skip to content

Conversation

@phodal
Copy link
Member

@phodal phodal commented Nov 30, 2025

Summary

Implement advanced input components for mpp-idea module with full DevIn language support.

Changes

New Components

  • IdeaInputListener.kt - Listener interface for input events (editorAdded, onSubmit, onStop, onTextChanged)
  • IdeaDevInInput.kt - EditorTextField-based input with DevIn language support
    • Syntax highlighting and completion via IntelliJ's Language API
    • Enter to submit, Shift/Ctrl/Cmd+Enter for newline
    • LookupManagerListener integration for completion popup handling
    • Placeholder text support
  • IdeaBottomToolbar.kt - Jewel-based bottom toolbar
    • Send/Stop buttons
    • @ trigger for agent completion
    • Settings button
    • Workspace path and token count display
  • IdeaInputSection.kt - Pure Compose input section (fallback)

Integration

  • IdeaAgentApp.kt - Updated to use DevInInputArea with SwingPanel
    • Embeds IdeaDevInInput via SwingPanel for native IntelliJ completion
    • Combines Swing editor with Compose bottom toolbar
    • Text change listener syncs editor state with Compose

Features

  • Full DevIn language support with IntelliJ native completion
  • Enter to submit, Shift+Enter for newline
  • Completion popup suppresses Enter key submission
  • Token usage and workspace path display
  • Consistent Jewel theme styling

References

Based on:

  • DevInEditorInput.kt from mpp-ui (Compose Multiplatform)
  • AutoDevInput.kt from core (IntelliJ IDEA)

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

Summary by CodeRabbit

  • New Features

    • IDE-style multi-line input with DevIn syntax support, Enter-to-send / Shift+Enter newline, editor-backed input area, and Send/Stop controls
    • Bottom toolbar with Send/Stop, Settings, and @-mention trigger; workspace and token usage indicators
  • Improvements

    • Richer timeline and message visuals (icons, stable item IDs, new tool-call entries)
    • Better editor document handling and input lifecycle (more reliable completion and disposal)
  • API

    • Input listener hooks for submit, stop, and text-change events

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

- Add IdeaInputListener interface for input events
- Add IdeaDevInInput with EditorTextField and DevIn language support
- Add IdeaBottomToolbar with Jewel-based send/stop/@/settings buttons
- Add IdeaInputSection combining input with toolbar
- Update IdeaAgentApp to use new IdeaInputSection

Features:
- Multi-line text input with Enter to send, Shift+Enter for newline
- DevIn language syntax highlighting and completion
- LookupManager integration for completion popup handling
- Workspace path and token count display
- Replace pure Compose IdeaInputSection with SwingPanel-embedded IdeaDevInInput
- DevInInputArea now embeds EditorTextField for full DevIn language support
- Add text change listener to IdeaDevInInput for Compose state sync
- Combine SwingPanel editor with Compose bottom toolbar

This enables IntelliJ native completion popup and DevIn syntax highlighting.
@coderabbitai
Copy link

coderabbitai bot commented Nov 30, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a Swing-backed DevIn editor, a Compose input section and bottom toolbar, and integrates them into the IdeaAgentApp chat UI with send/stop/@/settings wiring, token/workspace indicators, and a listener API for editor→host events.

Changes

Cohort / File(s) Change Summary
Compose UI: Input & Toolbar
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputSection.kt, mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt
New Compose components: IdeaInputSection (multi-line input, placeholder, send/newline handling, state sync, InputHints) and IdeaBottomToolbar (workspace/token display, @, settings, Send/Stop buttons, callbacks).
Swing-backed Editor
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaDevInInput.kt
New IdeaDevInInput EditorTextField subclass with DevIn language support, placeholder, Enter-submit vs Shift/Ctrl+Enter newline, completion-aware submit disabling, document recreation, input listeners, and disposal management.
Listener API
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputListener.kt
New IdeaInputTrigger enum and IdeaInputListener interface (default no-op methods: editorAdded, onSubmit, onStop, onTextChanged) for editor-host event propagation.
Tool window integration & UI tweaks
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
Replaces ChatInputArea with DevInInputArea that embeds IdeaDevInInput via SwingPanel, wires input listeners and toolbar callbacks, adds workspacePath/totalTokens/onSettingsClick/onAtClick parameters, and updates timeline keys and several UI bubbles to use icons.
Core/editor document creation
core/src/main/kotlin/cc/unitmesh/devti/gui/chat/ui/AutoDevInput.kt
Change in recreateDocument: create a new empty Document via EditorFactory inside a ReadAction instead of retrieving from a LightVirtualFile; retains listener initialization.
Timeline item IDs & ToolCallItem
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/JewelRenderer.kt
TimelineItem now includes stable id (generated via companion counter); message/tool/error/task/terminal items carry itemTimestamp and itemId; new ToolCallItem data class added; minor message content tweaks.
Orchestrator tool execution
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/orchestrator/ToolOrchestrator.kt
Execution flow now checks for SubAgent tools early: validates input, creates and executes a SubAgent invocation, returning its result; falls back to existing ExecutableTool path for other tools.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ComposeUI as IdeaInputSection\n(Compose)
    participant SwingEditor as IdeaDevInInput\n(Swing Editor)
    participant Listener as IdeaInputListener\n(Event)
    participant ToolWindow as IdeaAgentApp
    participant Toolbar as IdeaBottomToolbar\n(Compose)

    User->>ComposeUI: Focus / type
    ComposeUI->>SwingEditor: embed via SwingPanel / forward focus
    User->>SwingEditor: Type / Press Enter
    SwingEditor->>Listener: onTextChanged(text)
    SwingEditor->>Listener: onSubmit(text, trigger) when Enter (if not blocked by completion)
    Listener->>ToolWindow: deliver submit event
    ToolWindow->>ComposeUI: update isProcessing / clear or disable input
    User->>Toolbar: Click Send / Stop / @ / Settings
    Toolbar->>ToolWindow: emit onSendClick / onStopClick / onAtClick / onSettingsClick
    ToolWindow->>SwingEditor: request stop or append text via API/listener
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on:
    • IdeaDevInInput.kt (IntelliJ editor APIs, document recreation, Lookup/completion handling, writeAction/caret logic).
    • Lifecycle/disposal between SwingPanel, Disposer, and Compose in IdeaAgentApp.kt.
    • Event wiring and state sync across IdeaInputListener, IdeaDevInInput, IdeaInputSection, and IdeaBottomToolbar.
    • ToolOrchestrator.kt SubAgent execution branch correctness and error handling.

Possibly related PRs

Poem

🐰 I hopped a SwingPanel into view,
Keys and tokens danced — a shiny new queue,
Buttons to send, stop, and call an @,
Settings tucked close, workspace badge sat,
I nibble code and cheer this UI debut. 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% 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 directly matches the PR's main objective: adding advanced DevIn input components with SwingPanel integration across multiple new files.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d41e8ff and 72babb1.

📒 Files selected for processing (4)
  • core/src/main/kotlin/cc/unitmesh/devti/gui/chat/ui/AutoDevInput.kt (2 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/orchestrator/ToolOrchestrator.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/JewelRenderer.kt (4 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt (8 hunks)

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. 3 suggestions posted.

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

// Workspace indicator
if (!workspacePath.isNullOrEmpty()) {
val projectName = workspacePath.substringAfterLast('/')
.ifEmpty { workspacePath.substringAfterLast('\\') }
Copy link

Choose a reason for hiding this comment

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

On Windows-style paths, substringAfterLast('/') returns the original path when / is absent, so the .ifEmpty { substringAfterLast('\\') } branch won’t run and projectName becomes the full path; consider detecting the separator or using a path API to extract the last segment to avoid incorrect display.

🤖 Was this useful? React with 👍 or 👎

val id = UUID.randomUUID()
val file = LightVirtualFile("IdeaDevInInput-$id", language, "")

val document = FileDocumentManager.getInstance().getDocument(file)
Copy link

Choose a reason for hiding this comment

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

After recreateDocument(), the internal DocumentListener that forwards to onTextChanged isn’t reattached to the new document, so text-change callbacks stop firing after clear/recreate; consider registering the same listener for the new document (e.g., inside initializeDocumentListeners).

🤖 Was this useful? React with 👍 or 👎

val file = LightVirtualFile("IdeaDevInInput-$id", language, "")

val document = FileDocumentManager.getInstance().getDocument(file)
?: throw IllegalStateException("Can't create in-memory document")
Copy link

Choose a reason for hiding this comment

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

When replacing the document, listeners attached to the previous document (both external listeners and the anonymous internal one) are not removed, which risks leaks and duplicate callbacks; consider detaching them from the old document before setDocument (Guideline: no_memory_leaks).

🤖 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: 4

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

4-4: Consider using specific import instead of wildcard.

The java.util.* import is broader than necessary since only EventListener is used from this package.

-import java.util.*
+import java.util.EventListener
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputSection.kt (4)

11-11: Unused import.

The Color import from androidx.compose.ui.graphics appears unused in this file as all colors are obtained from JewelTheme.


46-54: Dual state synchronization could be simplified.

Maintaining both textFieldState and inputText with a LaunchedEffect collector introduces complexity. Consider using only textFieldState.text.toString() directly in the places where inputText is referenced, or derive inputText as a computed value.


94-98: Duplicate send logic between keyboard handler and button click.

The send logic (check isNotBlank, call onSend, clear text) is duplicated in both the onPreviewKeyEvent handler and the onSendClick lambda. Consider extracting this into a local function to ensure consistency.

+    val doSend: () -> Unit = {
+        if (inputText.isNotBlank()) {
+            onSend(inputText)
+            textFieldState.edit { replace(0, length, "") }
+        }
+    }
+
     // In onPreviewKeyEvent:
-                            if (inputText.isNotBlank()) {
-                                onSend(inputText)
-                                textFieldState.edit { replace(0, length, "") }
-                            }
+                            doSend()

     // In IdeaBottomToolbar:
-            onSendClick = {
-                if (inputText.isNotBlank()) {
-                    onSend(inputText)
-                    textFieldState.edit { replace(0, length, "") }
-                }
-            },
+            onSendClick = doSend,

Also applies to: 112-116


138-156: Remove unused InputHints composable or mark with TODO.

InputHints is defined but never referenced or rendered anywhere in the codebase. Either remove it or add a TODO comment if it's intended for future use.

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

707-709: Empty update block provides no reactive updates.

The update lambda is intended for updating the Swing component when Compose state changes, but it's currently empty. This is where you could update the Swing component's enabled state or sync state changes.

         update = { panel ->
-            // Update panel if needed
+            // Sync processing state to disable editor during execution
+            devInInput?.isEnabled = !isProcessing
         }
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (2)

8-8: Unused import.

The Color import from androidx.compose.ui.graphics appears unused since all colors are obtained from JewelTheme or AutoDevColors.


64-67: Consider using IntelliJ icons instead of emoji characters.

Emoji characters (📁, ⚙, ⏹) may render inconsistently across platforms or font configurations. Consider using IntelliJ's AllIcons or Jewel's icon components for a more consistent appearance within the IDE.

Also applies to: 128-131

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

224-227: Redundant text = "" after recreateDocument().

The recreateDocument() method creates a new document with empty content (""), so the subsequent text = "" assignment is unnecessary.

 fun clearInput() {
     recreateDocument()
-    text = ""
 }

184-185: Consider graceful error handling instead of throwing.

Throwing IllegalStateException if the document cannot be created will crash the UI. Consider logging an error and returning early, or using a fallback approach.

 val document = FileDocumentManager.getInstance().getDocument(file)
-    ?: throw IllegalStateException("Can't create in-memory document")
+    ?: run {
+        // Log error and keep existing document
+        com.intellij.openapi.diagnostic.Logger.getInstance(IdeaDevInInput::class.java)
+            .error("Failed to create in-memory document for DevIn input")
+        return
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 737ed17 and 9fe3faf.

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

📄 CodeRabbit inference engine (AGENTS.md)

Use expect/actual for platform-specific code in KMP projects (e.g., file I/O on JVM/JS/Wasm)

Files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputSection.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaDevInInput.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputListener.kt
🧠 Learnings (1)
📚 Learning: 2025-11-30T02:30:49.805Z
Learnt from: CR
Repo: phodal/auto-dev-sketch PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-30T02:30:49.805Z
Learning: Applies to **/src/{androidMain,desktopMain}/**/*.kt : For 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/toolwindow/IdeaAgentApp.kt
🧬 Code graph analysis (2)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputSection.kt (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (1)
  • IdeaBottomToolbar (22-161)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt (2)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaDevInInput.kt (3)
  • recreateDocument (179-189)
  • addInputListener (200-202)
  • clearInput (224-227)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (1)
  • IdeaBottomToolbar (22-161)
⏰ 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 (4)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputListener.kt (1)

6-38: Well-designed listener interface with sensible defaults.

The interface follows the IntelliJ platform convention of extending EventListener for use with EventDispatcher, and default method implementations allow consumers to override only the events they care about.

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

47-75: Project name extraction handles both path separators correctly.

The chained substringAfterLast calls properly handle both Unix (/) and Windows (\) path separators with a sensible "Project" fallback.


134-158: Send/Stop button toggle implementation is clean.

The conditional rendering based on isExecuting provides clear visual feedback, and the use of AutoDevColors aligns with the project's design system as per the learnings.

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

52-57: Enter submit action triggers even when editor is empty after trim.

The submit action checks text.trim().isNotEmpty() which is correct, but the onSubmit callback receives the trimmed text. Ensure consumers expect trimmed input.

Comment on lines +665 to +710
factory = {
val input = IdeaDevInInput(
project = project,
disposable = parentDisposable,
showAgent = true
).apply {
recreateDocument()

addInputListener(object : IdeaInputListener {
override fun editorAdded(editor: EditorEx) {
// Editor is ready
}
true
} else {
false
}
},
enabled = !isProcessing
)

if (isProcessing) {
DefaultButton(onClick = onAbort) {
Text("Stop")
}
} else {
DefaultButton(
onClick = {
if (inputText.isNotBlank()) {
onSend(inputText)
textFieldState.edit { replace(0, length, "") }
}
},
enabled = inputText.isNotBlank()
) {
Text("Send")
override fun onSubmit(text: String, trigger: IdeaInputTrigger) {
if (text.isNotBlank() && !isProcessing) {
onSend(text)
clearInput()
inputText = ""
}
}

override fun onStop() {
onAbort()
}

override fun onTextChanged(text: String) {
inputText = text
}
})
}

// Register for disposal
Disposer.register(parentDisposable, input)
devInInput = input

// Wrap in a JPanel to handle sizing
JPanel(BorderLayout()).apply {
add(input, BorderLayout.CENTER)
preferredSize = Dimension(800, 120)
minimumSize = Dimension(200, 80)
}
},
update = { panel ->
// Update panel if needed
}
}
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stale closure capture of isProcessing in SwingPanel factory.

The factory lambda runs only once when the SwingPanel is first composed. The IdeaInputListener created inside captures the initial value of isProcessing, which will never update. This means the !isProcessing check on line 679 will always reflect the initial state.

Consider one of these approaches:

  1. Move the processing check to the onSend callback and let the parent handle gating.
  2. Use a Ref or mutable holder that can be updated from the update block.
+    // Use a ref to avoid stale closure
+    val isProcessingRef = remember { mutableStateOf(isProcessing) }
+    LaunchedEffect(isProcessing) { isProcessingRef.value = isProcessing }

     SwingPanel(
         ...
         factory = {
             val input = IdeaDevInInput(...).apply {
                 addInputListener(object : IdeaInputListener {
                     override fun onSubmit(text: String, trigger: IdeaInputTrigger) {
-                        if (text.isNotBlank() && !isProcessing) {
+                        if (text.isNotBlank() && !isProcessingRef.value) {
                             onSend(text)
                             ...
                         }
                     }
                     ...
                 })
             }
             ...
         },

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +713 to +732
IdeaBottomToolbar(
onSendClick = {
val text = devInInput?.text?.trim() ?: inputText.trim()
if (text.isNotBlank() && !isProcessing) {
onSend(text)
devInInput?.clearInput()
inputText = ""
}
},
sendEnabled = inputText.isNotBlank() && !isProcessing,
isExecuting = isProcessing,
onStopClick = onAbort,
onAtClick = {
devInInput?.appendText("@")
onAtClick()
},
onSettingsClick = onSettingsClick,
workspacePath = workspacePath,
totalTokens = totalTokens
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Send button enabled state inconsistent with editor submit behavior.

The sendEnabled check on line 722 uses inputText.isNotBlank(), but the button's onSendClick on line 715 also checks devInInput?.text. These could diverge if inputText state isn't perfectly synchronized with the editor's actual text. Consider using a single source of truth.

🤖 Prompt for AI Agents
In mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt
around lines 713 to 732, the send button enabled state uses
inputText.isNotBlank() while the onSendClick handler reads devInInput?.text,
which can diverge; unify to a single source of truth by computing a single
trimmed currentText (e.g. val currentText = devInInput?.text?.trim() ?:
inputText.trim()) and use that variable both for the sendEnabled boolean and
inside onSendClick (call onSend(currentText) and clear/reset state accordingly)
so the button state and submit behavior are always consistent.

- Fix memory leak by storing and properly disposing internal DocumentListener
- Fix recreateDocument to use EditorFactory instead of FileDocumentManager
- Re-attach listeners when document is recreated
- Fix Windows path handling in project name extraction
- Remove unused imports (Color, java.util.*)
- Extract duplicate send logic to avoid code duplication
- Remove redundant text assignment in clearInput()

Addresses review comments from PR #7
@phodal
Copy link
Member Author

phodal commented Nov 30, 2025

Code Review Fixes Applied ✅

I've addressed all the critical review comments from the automated code review:

Memory Leak Fixes

  • Fixed: Stored internalDocumentListener as a field instead of anonymous object to enable proper disposal
  • Fixed: Added removal of internal listener in dispose() method
  • Fixed: Re-attach internal listener when document is recreated in recreateDocument()

Document Creation Fix

  • Fixed: Changed from FileDocumentManager.getInstance().getDocument(file) to EditorFactory.getInstance().createDocument("") wrapped in ReadAction.compute
  • Fixed: Properly remove old listeners before replacing document
  • This resolves the IllegalStateException: Can't create in-memory document error

Code Quality Improvements

  • Fixed: Windows path handling - normalize separators before extracting project name
  • Fixed: Removed unused imports (Color, java.util.*)
  • Fixed: Extracted duplicate send logic into doSend() function
  • Fixed: Removed redundant text = "" in clearInput() since recreateDocument() already creates empty document

Files Changed

  • IdeaDevInInput.kt - Memory leak fixes, document creation fix
  • IdeaBottomToolbar.kt - Path handling, unused import removal
  • IdeaInputListener.kt - Specific import instead of wildcard
  • IdeaInputSection.kt - Duplicate code elimination, unused import removal

All changes have been pushed to the feat/idea-advanced-input branch.

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 (5)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaDevInInput.kt (3)

43-48: LGTM on class structure and fields.

The class properly extends EditorTextField and implements Disposable. The showAgent parameter is declared but unused — consider removing it if not needed for future functionality, or document its intended purpose.


76-94: Redundant command nesting.

WriteCommandAction.runWriteCommandAction already wraps the action in a command. The outer CommandProcessor.getInstance().executeCommand is redundant.

 private fun insertNewLine(editor: Editor) {
-    CommandProcessor.getInstance().executeCommand(project, {
-        val eol = "\n"
-        val document = editor.document
-        val caretOffset = editor.caretModel.offset
-        val lineEndOffset = document.getLineEndOffset(document.getLineNumber(caretOffset))
-        val textAfterCaret = document.getText(TextRange(caretOffset, lineEndOffset))
-
-        WriteCommandAction.runWriteCommandAction(project) {
-            if (textAfterCaret.isBlank()) {
-                document.insertString(caretOffset, eol)
-                EditorModificationUtil.moveCaretRelatively(editor, 1)
-            } else {
-                document.insertString(caretOffset, eol)
-                editor.caretModel.moveToOffset(caretOffset + eol.length)
-            }
+    val eol = "\n"
+    val document = editor.document
+    val caretOffset = editor.caretModel.offset
+    val lineEndOffset = document.getLineEndOffset(document.getLineNumber(caretOffset))
+    val textAfterCaret = document.getText(TextRange(caretOffset, lineEndOffset))
+
+    WriteCommandAction.runWriteCommandAction(project, "Insert New Line", null, {
+        if (textAfterCaret.isBlank()) {
+            document.insertString(caretOffset, eol)
+            EditorModificationUtil.moveCaretRelatively(editor, 1)
+        } else {
+            document.insertString(caretOffset, eol)
+            editor.caretModel.moveToOffset(caretOffset + eol.length)
         }
-    }, "Insert New Line", null)
+    })
 }

153-155: Consider defensive casting.

The cast editor as EditorEx assumes the editor is always an EditorEx. While this is typically true in IntelliJ, a safe cast would be more defensive.

 override fun onEditorAdded(editor: Editor) {
-    editorListeners.multicaster.editorAdded(editor as EditorEx)
+    (editor as? EditorEx)?.let { editorListeners.multicaster.editorAdded(it) }
 }
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputSection.kt (1)

93-107: Consider handling Alt+Enter modifier.

The key event check excludes Shift, Ctrl, and Meta modifiers but not Alt. On some systems, Alt+Enter might be expected to behave like other modifier+Enter combinations (newline instead of submit).

                     if (keyEvent.key == Key.Enter &&
                         keyEvent.type == KeyEventType.KeyDown &&
                         !keyEvent.isShiftPressed &&
                         !keyEvent.isCtrlPressed &&
                         !keyEvent.isMetaPressed &&
+                        !keyEvent.isAltPressed &&
                         !isProcessing
                     ) {
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (1)

110-133: Consider using proper icons instead of emoji characters.

The emoji characters ("@", "⚙") may render inconsistently across different platforms and themes. Consider using IntelliJ's icon system or Jewel icons for consistent appearance.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fe3faf and d41e8ff.

📒 Files selected for processing (4)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaDevInInput.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputListener.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputSection.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/IdeaInputListener.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

Use expect/actual for platform-specific code in KMP projects (e.g., file I/O on JVM/JS/Wasm)

Files:

  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaDevInInput.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputSection.kt
🧬 Code graph analysis (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputSection.kt (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (1)
  • IdeaBottomToolbar (21-162)
⏰ 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 (223)
  • GitHub Check: Build (241)
🔇 Additional comments (16)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaDevInInput.kt (8)

52-57: LGTM!

The internal document listener is now properly stored as a field, enabling correct cleanup during disposal. This addresses the previous review feedback.


59-74: LGTM!

The submit action correctly trims input and validates non-empty text before firing the submit event. The newline action safely handles null editor.


96-143: LGTM!

The init block properly configures the editor settings, registers shortcuts for both submit and newline actions, and correctly integrates with the LookupManagerListener to prevent accidental submissions during completion popup display.


145-151: LGTM!

Clean shortcut registration and unregistration pattern.


157-166: LGTM!

The editor is properly configured with appropriate visual settings for the input use case.


173-178: LGTM!

The dispose method now correctly removes both the internal document listener and external listeners, addressing the previous review feedback about potential memory leaks.


184-208: LGTM!

The document recreation logic now properly removes listeners from the old document before replacing it and re-adds all listeners (including the internal one) to the new document. This correctly addresses the previous review feedback.


210-240: LGTM!

The public API methods are well-documented and correctly implemented. The appendText method properly uses WriteCommandAction for thread-safe document modifications.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaInputSection.kt (4)

34-44: LGTM!

Well-documented composable with clear parameter descriptions. The default values for optional callbacks provide sensible fallbacks.


45-61: LGTM!

The state synchronization pattern using snapshotFlow with distinctUntilChanged is correct and efficient. The doSend lambda properly clears the input after submission.


115-130: LGTM!

The toolbar integration is clean, with proper callback wiring. The onAtClick handler correctly appends the "@" character before invoking the callback.


137-155: InputHints is defined but not used within this file.

The InputHints composable is defined here but not used within IdeaInputSection. Verify this is intentional for external use, or consider integrating it into the input section if hints should be displayed.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt (4)

21-32: LGTM!

Well-documented composable with sensible parameter defaults. The API surface is clean and focused.


47-76: Windows path handling is now correctly addressed.

The path normalization using replace('\\', '/') before substringAfterLast('/') correctly handles both Unix and Windows path separators. This addresses the previous review feedback.


78-102: LGTM!

The token usage indicator has proper null and zero-value checks with clean conditional rendering.


135-159: LGTM!

The Send/Stop button toggle logic correctly responds to the isExecuting state, and the Send button properly respects the sendEnabled flag.

…jis with icons

- Fix ClassCastException in AnalysisAgent by validating SubAgent input before creating invocation
- Fix duplicate key error in LazyColumn by adding unique ID to TimelineItem
- Replace all emojis with Material Design icons from AutoDevComposeIcons
  - Tool icon (Build)
  - Error icon (Error)
  - Success/Failure icons (CheckCircle/Error)
  - Settings icon (Settings)
- Remove emoji from recovery advice and confirmation messages

Fixes:
- java.lang.ClassCastException: LinkedHashMap cannot be cast to ContentHandlerContext
- IllegalArgumentException: Key was already used in LazyColumn
@phodal phodal force-pushed the feat/idea-advanced-input branch from 2203e15 to 72babb1 Compare November 30, 2025 06:58
@phodal
Copy link
Member Author

phodal commented Nov 30, 2025

Additional Fixes Applied

Added critical bug fixes and UI improvements:

Bug Fixes

1. ClassCastException in AnalysisAgent

Issue: java.lang.ClassCastException: LinkedHashMap cannot be cast to ContentHandlerContext

Root Cause: ToolOrchestrator.executeGenericTool() was passing raw Map<String, Any> directly to SubAgent.createInvocation(), but SubAgent expects validated TInput type.

Solution: Added type checking and input validation for SubAgent:

if (tool is SubAgent<*, *>) {
    val validatedInput = subAgent.validateInput(params)
    val invocation = subAgent.createInvocation(validatedInput)
    return invocation.execute(context)
}

2. Duplicate Key in LazyColumn

Issue: IllegalArgumentException: Key "1764485447561" was already used

Root Cause: Using timestamp as LazyColumn key, but multiple items can be created in the same millisecond.

Solution: Added unique ID generation to TimelineItem:

sealed class TimelineItem(val timestamp: Long, val id: String = generateId()) {
    companion object {
        private var idCounter = 0L
        fun generateId(): String = "${System.currentTimeMillis()}-${idCounter++}"
    }
}

UI Improvements

Replace Emojis with Material Design Icons

Replaced all emojis with proper icons from AutoDevComposeIcons:

Component Before After
Tool name 🔧 Icon(AutoDevComposeIcons.Build)
Error message Icon(AutoDevComposeIcons.Error)
Success Icon(AutoDevComposeIcons.CheckCircle)
Settings Icon(AutoDevComposeIcons.Settings)
Recovery advice 🔧 prefix Removed emoji
Confirmation ⚠️ prefix Removed emoji

Benefits:

  • Better visual consistency with IntelliJ theme
  • Proper icon sizing and coloring
  • No font rendering issues with emojis

Files Changed

  • ToolOrchestrator.kt - SubAgent input validation
  • JewelRenderer.kt - Unique ID generation, emoji removal
  • IdeaAgentApp.kt - Icon replacements in UI
  • AutoDevInput.kt - Document creation fix (from previous commit)

All tests should now pass without ClassCastException or duplicate key errors.

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