Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -217,48 +217,65 @@ private fun IdeaChangeSummaryHeader(
}

/**
* Undo a file change by restoring the original content
* Undo a file change using IntelliJ's RollbackWorker for proper VCS integration.
* Falls back to manual revert if RollbackWorker fails.
*/
private fun undoChange(project: Project, change: FileChange) {
private fun undoChange(project: Project, fileChange: FileChange) {
ApplicationManager.getApplication().invokeLater {
runWriteAction {
try {
when (change.changeType) {
ChangeType.CREATE -> {
// For created files, delete or clear the content
try {
// Convert FileChange to IntelliJ Change and use RollbackWorker
val change = FileChangeConverter.toChange(project, fileChange)
val rollbackWorker = com.intellij.openapi.vcs.changes.ui.RollbackWorker(project)
rollbackWorker.doRollback(listOf(change), false)
} catch (e: Exception) {
logger.warn("RollbackWorker failed, falling back to manual revert", e)
// Fallback to manual revert
performManualUndo(project, fileChange)
}
}
}
Comment on lines 219 to +236
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# First, check the actual imports in the file
echo "=== Checking all imports in IdeaFileChangeSummary.kt ==="
head -50 mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeSummary.kt | grep "^import"

echo -e "\n=== Checking for FileChangeConverter import ==="
rg "import.*FileChangeConverter" mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeSummary.kt

echo -e "\n=== Finding FileChangeConverter definition in the codebase ==="
rg "class FileChangeConverter|object FileChangeConverter" --type=kt

echo -e "\n=== Checking if FileChangeConverter is defined in this file itself ==="
rg -n "FileChangeConverter" mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeSummary.kt | head -20

echo -e "\n=== Checking RollbackWorker.doRollback signature ==="
rg -nA 5 "fun doRollback" --type=kt | head -30

Repository: phodal/auto-dev-sketch

Length of output: 2014


🏁 Script executed:

#!/bin/bash

# Check the actual imports more thoroughly
echo "=== Full import section of IdeaFileChangeSummary.kt ==="
head -100 mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeSummary.kt | grep "^import"

echo -e "\n=== Searching for FileChangeConverter definition anywhere ==="
rg "class FileChangeConverter|object FileChangeConverter"

echo -e "\n=== Searching for FileChangeConverter usage in other files ==="
rg "FileChangeConverter" mpp-idea/src/main/kotlin/ | head -20

echo -e "\n=== Checking for RollbackWorker import ==="
rg "import.*RollbackWorker"

echo -e "\n=== Checking if RollbackWorker is available in IntelliJ API ==="
rg "com.intellij.openapi.vcs.changes" mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeSummary.kt

echo -e "\n=== Looking at lines around 227-228 in context ==="
sed -n '220,240p' mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeSummary.kt

Repository: phodal/auto-dev-sketch

Length of output: 3757


🏁 Script executed:

#!/bin/bash

# Find RollbackWorker.doRollback signature
echo "=== Searching for RollbackWorker.doRollback definition ==="
rg "fun doRollback" -A 5

echo -e "\n=== Checking IdeaFileChangeDiffDialog for comparison ==="
sed -n '1,50p' mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeDiffDialog.kt | grep -E "^import|FileChangeConverter"

echo -e "\n=== Finding where FileChangeConverter is defined in IdeaFileChangeDiffDialog ==="
rg -n "object FileChangeConverter" mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeDiffDialog.kt -A 3

echo -e "\n=== Checking if FileChangeConverter needs to be public/exported ==="
grep -n "object FileChangeConverter\|fun toChange" mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeDiffDialog.kt

Repository: phodal/auto-dev-sketch

Length of output: 2039


🏁 Script executed:

#!/bin/bash

# Verify the exact package structure
echo "=== Checking package declarations ==="
head -5 mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeSummary.kt
head -5 mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeDiffDialog.kt

echo -e "\n=== Verify if FileChangeConverter is public ==="
grep -B2 "object FileChangeConverter" mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeDiffDialog.kt

echo -e "\n=== Check all usages of RollbackWorker.doRollback parameters ==="
rg "\.doRollback" -B 2 -A 2

Repository: phodal/auto-dev-sketch

Length of output: 3448


Add missing imports for FileChangeConverter and RollbackWorker.

Line 227 uses FileChangeConverter.toChange() but the import is missing. Add import cc.unitmesh.devins.idea.toolwindow.changes.FileChangeConverter at the top of the file. Additionally, add import com.intellij.openapi.vcs.changes.ui.RollbackWorker and replace the fully qualified name com.intellij.openapi.vcs.changes.ui.RollbackWorker(project) on line 228 with just RollbackWorker(project) for consistency with IdeaFileChangeDiffDialog.kt.

🤖 Prompt for AI Agents
In
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeSummary.kt
around lines 219 to 236, add the missing imports for FileChangeConverter and
RollbackWorker and simplify the instantiation: add import
cc.unitmesh.devins.idea.toolwindow.changes.FileChangeConverter and import
com.intellij.openapi.vcs.changes.ui.RollbackWorker at the top of the file, then
replace the fully-qualified
com.intellij.openapi.vcs.changes.ui.RollbackWorker(project) usage with
RollbackWorker(project) in the undoChange function for consistency.


/**
* Manual undo fallback when RollbackWorker fails.
*/
private fun performManualUndo(project: Project, change: FileChange) {
runWriteAction {
try {
when (change.changeType) {
ChangeType.CREATE -> {
// For created files, delete or clear the content
val virtualFile = LocalFileSystem.getInstance().findFileByPath(change.filePath)
virtualFile?.let { vf ->
val document = FileDocumentManager.getInstance().getDocument(vf)
document?.setText("")
}
}
Comment on lines +245 to +252
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

CREATE case should delete the file, not just clear content.

For ChangeType.CREATE, the current implementation clears the file content with setText(""), leaving an empty file on disk. To properly undo a file creation, the file should be deleted entirely using virtualFile.delete(project).

Apply this diff:

                 ChangeType.CREATE -> {
-                    // For created files, delete or clear the content
+                    // For created files, delete them
                     val virtualFile = LocalFileSystem.getInstance().findFileByPath(change.filePath)
-                    virtualFile?.let { vf ->
-                        val document = FileDocumentManager.getInstance().getDocument(vf)
-                        document?.setText("")
-                    }
+                    virtualFile?.delete(project)
                 }
🤖 Prompt for AI Agents
In
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeSummary.kt
around lines 245-252, the CREATE branch currently clears file content instead of
removing the created file; replace the document.setText("") logic with a call to
virtualFile.delete(project) to delete the file from disk (use the surrounding
class's project reference or pass the appropriate Project instance), wrap the
delete in a null-check and try/catch to handle IO exceptions, and refresh the
VFS if needed so the IDE reflects the removed file.

ChangeType.EDIT, ChangeType.RENAME -> {
// Restore original content
change.originalContent?.let { original ->
val virtualFile = LocalFileSystem.getInstance().findFileByPath(change.filePath)
virtualFile?.let { vf ->
val document = FileDocumentManager.getInstance().getDocument(vf)
document?.setText("")
}
}
ChangeType.EDIT, ChangeType.RENAME -> {
// Restore original content
change.originalContent?.let { original ->
val virtualFile = LocalFileSystem.getInstance().findFileByPath(change.filePath)
virtualFile?.let { vf ->
val document = FileDocumentManager.getInstance().getDocument(vf)
document?.setText(original)
}
document?.setText(original)
}
}
ChangeType.DELETE -> {
// For deleted files, we would need to recreate them
change.originalContent?.let { original ->
val parentPath = change.filePath.substringBeforeLast('/')
val fileName = change.filePath.substringAfterLast('/')
val parentDir = LocalFileSystem.getInstance().findFileByPath(parentPath)
parentDir?.let { dir ->
val newFile = dir.createChildData(project, fileName)
val document = FileDocumentManager.getInstance().getDocument(newFile)
document?.setText(original)
}
}
ChangeType.DELETE -> {
// For deleted files, we would need to recreate them
change.originalContent?.let { original ->
val parentPath = change.filePath.substringBeforeLast('/')
val fileName = change.filePath.substringAfterLast('/')
val parentDir = LocalFileSystem.getInstance().findFileByPath(parentPath)
parentDir?.let { dir ->
val newFile = dir.createChildData(project, fileName)
val document = FileDocumentManager.getInstance().getDocument(newFile)
document?.setText(original)
}
}
}
} catch (e: Exception) {
logger.error("Failed to undo change for ${change.filePath}", e)
}
} catch (e: Exception) {
logger.error("Failed to undo change for ${change.filePath}", e)
}
}
}
Comment on lines +238 to 281
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

Refactor duplicated performManualUndo logic into a shared utility.

This function duplicates nearly identical logic from IdeaFileChangeDiffDialog.kt (lines 231-273). Extract the common undo logic into a shared utility function to improve maintainability and ensure consistent behavior across both usages.

Consider creating a shared utility like this:

object FileChangeUndoUtil {
    fun performManualUndo(project: Project, change: FileChange, onSuccess: (() -> Unit)? = null) {
        runWriteAction {
            try {
                when (change.changeType) {
                    ChangeType.CREATE -> {
                        val virtualFile = LocalFileSystem.getInstance().findFileByPath(change.filePath)
                        virtualFile?.delete(project)
                    }
                    ChangeType.EDIT, ChangeType.RENAME -> {
                        change.originalContent?.let { original ->
                            val virtualFile = LocalFileSystem.getInstance().findFileByPath(change.filePath)
                            virtualFile?.let { vf ->
                                val document = FileDocumentManager.getInstance().getDocument(vf)
                                document?.setText(original)
                            }
                        }
                    }
                    ChangeType.DELETE -> {
                        change.originalContent?.let { original ->
                            val parentPath = change.filePath.substringBeforeLast('/')
                            val fileName = change.filePath.substringAfterLast('/')
                            val parentDir = LocalFileSystem.getInstance().findFileByPath(parentPath)
                            parentDir?.let { dir ->
                                val newFile = dir.createChildData(project, fileName)
                                val document = FileDocumentManager.getInstance().getDocument(newFile)
                                document?.setText(original)
                            }
                        }
                    }
                }
                onSuccess?.invoke()
            } catch (e: Exception) {
                logger.error("Failed to undo change for ${change.filePath}", e)
            }
        }
    }
}
🤖 Prompt for AI Agents
In
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/changes/IdeaFileChangeSummary.kt
around lines 238-281, the performManualUndo implementation duplicates logic
present in IdeaFileChangeDiffDialog.kt; extract the shared undo logic into a
single utility (e.g., object FileChangeUndoUtil with fun
performManualUndo(project: Project, change: FileChange, onSuccess: (() -> Unit)?
= null)) that implements the Create/Edit/Rename/Delete handling (use
VirtualFile.delete(project) for CREATE, restore original text for EDIT/RENAME,
recreate file and set text for DELETE), call runWriteAction inside the utility,
invoke onSuccess after success, log exceptions with the existing logger, replace
the duplicated implementations in both files with calls to this utility, and
update imports/removals accordingly.

6 changes: 4 additions & 2 deletions mpp-vscode/webview/src/components/Timeline.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
border: 1px solid var(--vscode-input-border);
}

/* Assistant messages: transparent background for unified look */
.timeline-item.message.assistant {
background-color: var(--vscode-editorWidget-background);
border: 1px solid var(--vscode-panel-border);
background-color: transparent;
border: none;
padding: 4px 12px;
}

.timeline-item.message.system {
Expand Down
8 changes: 2 additions & 6 deletions mpp-vscode/webview/src/components/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,8 @@ const TerminalItemRenderer: React.FC<{ item: TerminalTimelineItem; onAction?: (a
</div>
);

const TaskCompleteItemRenderer: React.FC<{ item: { success: boolean; message: string } }> = ({ item }) => (
<div className={`timeline-item task-complete ${item.success ? 'success' : 'error'}`}>
<span className="task-icon">{item.success ? '✅' : '❌'}</span>
<span className="task-message">{item.message}</span>
</div>
);
// Task complete is not displayed - the completion is implicit from the response
const TaskCompleteItemRenderer: React.FC<{ item: { success: boolean; message: string } }> = () => null;

const ErrorItemRenderer: React.FC<{ item: { message: string } }> = ({ item }) => (
<div className="timeline-item error">
Expand Down
100 changes: 100 additions & 0 deletions mpp-vscode/webview/src/components/sketch/DevInRenderer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
.devin-renderer {
display: flex;
flex-direction: column;
gap: 8px;
}

.devin-tool-item {
border: 1px solid var(--vscode-panel-border);
border-radius: 4px;
overflow: hidden;
background-color: var(--vscode-editorWidget-background);
border-left: 3px solid var(--vscode-testing-iconPassed, #4caf50);
}

.devin-tool-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
user-select: none;
transition: background-color 0.2s;
}

/* Only show pointer cursor when header is clickable (has params) */
.devin-tool-header.clickable {
cursor: pointer;
}

.devin-tool-header.clickable:hover {
background-color: var(--vscode-list-hoverBackground);
}

.devin-tool-icon {
font-size: 14px;
flex-shrink: 0;
}

.devin-tool-name {
font-weight: 600;
font-size: 12px;
color: var(--vscode-foreground);
}

.devin-tool-details {
font-size: 11px;
color: var(--vscode-descriptionForeground);
font-family: var(--vscode-editor-font-family, monospace);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}

.devin-tool-actions {
display: flex;
align-items: center;
gap: 4px;
margin-left: auto;
flex-shrink: 0;
}

.devin-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border: none;
border-radius: 4px;
background: transparent;
cursor: pointer;
font-size: 14px;
color: var(--vscode-descriptionForeground);
transition: background-color 0.2s;
}

.devin-action-btn:hover {
background-color: var(--vscode-list-hoverBackground);
}

.devin-action-btn.toggle {
font-size: 10px;
}

.devin-tool-params {
padding: 8px 12px;
border-top: 1px solid var(--vscode-panel-border);
background-color: var(--vscode-editor-background);
}

.devin-tool-params pre {
margin: 0;
font-family: var(--vscode-editor-font-family, monospace);
font-size: 11px;
line-height: 1.4;
white-space: pre-wrap;
word-break: break-word;
color: var(--vscode-editor-foreground);
}

Loading
Loading