diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt index 82b62a15d8..b6118271d3 100644 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaBottomToolbar.kt @@ -19,11 +19,11 @@ import org.jetbrains.jewel.ui.component.Icon /** * Bottom toolbar for the input section. - * Provides send/stop buttons, model selector, settings, and token info. + * Provides send/stop buttons, model selector, MCP config, and token info. * - * Layout: ModelSelector - Token Info | MCP Settings - Prompt Optimization - Send Button + * Layout: ModelSelector - Token Info | MCP Config - Prompt Optimization - Send Button * - Left side: Model configuration (blends with background) - * - Right side: MCP, prompt optimization, and send + * - Right side: MCP config, prompt optimization, and send * * Note: @ and / triggers are now in the top toolbar (IdeaTopToolbar). */ @@ -33,7 +33,6 @@ fun IdeaBottomToolbar( sendEnabled: Boolean, isExecuting: Boolean = false, onStopClick: () -> Unit = {}, - onSettingsClick: () -> Unit = {}, onPromptOptimizationClick: () -> Unit = {}, totalTokens: Int? = null, // Model selector props @@ -43,6 +42,7 @@ fun IdeaBottomToolbar( onConfigureClick: () -> Unit = {}, modifier: Modifier = Modifier ) { + var showMcpConfigDialog by remember { mutableStateOf(false) } Row( modifier = modifier .fillMaxWidth() @@ -81,14 +81,14 @@ fun IdeaBottomToolbar( horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically ) { - // MCP Settings button + // MCP Config button - opens MCP configuration dialog IconButton( - onClick = onSettingsClick, + onClick = { showMcpConfigDialog = true }, modifier = Modifier.size(32.dp) ) { Icon( imageVector = IdeaComposeIcons.Settings, - contentDescription = "MCP Settings", + contentDescription = "MCP Configuration", tint = JewelTheme.globalColors.text.normal, modifier = Modifier.size(16.dp) ) @@ -156,5 +156,12 @@ fun IdeaBottomToolbar( } } } + + // MCP Configuration Dialog + if (showMcpConfigDialog) { + IdeaMcpConfigDialog( + onDismiss = { showMcpConfigDialog = false } + ) + } } diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt new file mode 100644 index 0000000000..cd77397667 --- /dev/null +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaFileSearchPopup.kt @@ -0,0 +1,442 @@ +package cc.unitmesh.devins.idea.editor + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import cc.unitmesh.devins.idea.toolwindow.IdeaComposeIcons +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.readAction +import com.intellij.openapi.fileEditor.impl.EditorHistoryManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.jewel.foundation.theme.JewelTheme +import org.jetbrains.jewel.ui.component.* + +/** + * Context menu popup for adding files/folders to workspace. + * Uses Jewel's PopupMenu for native IntelliJ look and feel. + * + * Layout: + * - Files (submenu with matching files) + * - Folders (submenu with matching folders) + * - Recently Opened Files (submenu) + * - Clear Context + * - Search field at bottom + */ +@Composable +fun IdeaFileSearchPopup( + project: Project, + onDismiss: () -> Unit, + onFilesSelected: (List) -> Unit +) { + val searchQueryState = rememberTextFieldState("") + val searchQuery by remember { derivedStateOf { searchQueryState.text.toString() } } + + // Grouped search results + var files by remember { mutableStateOf>(emptyList()) } + var folders by remember { mutableStateOf>(emptyList()) } + var recentFiles by remember { mutableStateOf>(emptyList()) } + + // Submenu expansion states + var filesExpanded by remember { mutableStateOf(false) } + var foldersExpanded by remember { mutableStateOf(false) } + var recentExpanded by remember { mutableStateOf(false) } + + // Load data based on search query - run on background thread to avoid EDT blocking + LaunchedEffect(searchQuery) { + val results = withContext(Dispatchers.IO) { + if (searchQuery.length >= 2) { + searchAllItems(project, searchQuery) + } else { + SearchResults(emptyList(), emptyList(), loadRecentFiles(project)) + } + } + files = results.files + folders = results.folders + recentFiles = results.recentFiles + } + + // Initial load - run on background thread + LaunchedEffect(Unit) { + recentFiles = withContext(Dispatchers.IO) { + loadRecentFiles(project) + } + } + + PopupMenu( + onDismissRequest = { + onDismiss() + true + }, + horizontalAlignment = Alignment.Start, + modifier = Modifier.widthIn(min = 280.dp, max = 450.dp) + ) { + // Files submenu + if (files.isNotEmpty() || searchQuery.length >= 2) { + submenu( + submenu = { + if (files.isEmpty()) { + passiveItem { + Text( + "No files found", + style = JewelTheme.defaultTextStyle.copy( + fontSize = 13.sp, + color = JewelTheme.globalColors.text.normal.copy(alpha = 0.5f) + ) + ) + } + } else { + files.take(10).forEach { file -> + selectableItem( + selected = false, + onClick = { + onFilesSelected(listOf(file.virtualFile)) + onDismiss() + } + ) { + FileMenuItem(file) + } + } + if (files.size > 10) { + passiveItem { + Text( + "... and ${files.size - 10} more", + style = JewelTheme.defaultTextStyle.copy( + fontSize = 11.sp, + color = JewelTheme.globalColors.text.normal.copy(alpha = 0.5f) + ) + ) + } + } + } + } + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = IdeaComposeIcons.InsertDriveFile, + contentDescription = null, + tint = JewelTheme.globalColors.text.normal, + modifier = Modifier.size(16.dp) + ) + Text("Files", style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp)) + } + } + } + } + + // Folders submenu + if (folders.isNotEmpty() || searchQuery.length >= 2) { + submenu( + submenu = { + if (folders.isEmpty()) { + passiveItem { + Text( + "No folders found", + style = JewelTheme.defaultTextStyle.copy( + fontSize = 13.sp, + color = JewelTheme.globalColors.text.normal.copy(alpha = 0.5f) + ) + ) + } + } else { + folders.take(10).forEach { folder -> + selectableItem( + selected = false, + onClick = { + onFilesSelected(listOf(folder.virtualFile)) + onDismiss() + } + ) { + FolderMenuItem(folder) + } + } + } + } + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = IdeaComposeIcons.Folder, + contentDescription = null, + tint = JewelTheme.globalColors.text.normal, + modifier = Modifier.size(16.dp) + ) + Text("Folders", style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp)) + } + } + } + + // Recently Opened Files submenu + submenu( + submenu = { + if (recentFiles.isEmpty()) { + passiveItem { + Text( + "No recent files", + style = JewelTheme.defaultTextStyle.copy( + fontSize = 13.sp, + color = JewelTheme.globalColors.text.normal.copy(alpha = 0.5f) + ) + ) + } + } else { + recentFiles.take(15).forEach { file -> + selectableItem( + selected = false, + onClick = { + onFilesSelected(listOf(file.virtualFile)) + onDismiss() + } + ) { + FileMenuItem(file) + } + } + } + } + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = IdeaComposeIcons.History, + contentDescription = null, + tint = JewelTheme.globalColors.text.normal, + modifier = Modifier.size(16.dp) + ) + Text("Recently Opened Files", style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp)) + } + } + + separator() + + // Clear Context action + selectableItem( + selected = false, + onClick = { + onFilesSelected(emptyList()) + onDismiss() + } + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = IdeaComposeIcons.Close, + contentDescription = null, + tint = JewelTheme.globalColors.text.normal, + modifier = Modifier.size(16.dp) + ) + Text("Clear Context", style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp)) + } + } + + separator() + + // Search field at bottom + passiveItem { + TextField( + state = searchQueryState, + placeholder = { Text("Focus context") }, + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp) + ) + } + } +} + +@Composable +private fun FileMenuItem(file: IdeaFilePresentation) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = IdeaComposeIcons.InsertDriveFile, + contentDescription = null, + tint = JewelTheme.globalColors.text.normal, + modifier = Modifier.size(16.dp) + ) + Column(modifier = Modifier.weight(1f)) { + Text( + text = file.name, + style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp), + maxLines = 1 + ) + Text( + text = file.presentablePath, + style = JewelTheme.defaultTextStyle.copy( + fontSize = 11.sp, + color = JewelTheme.globalColors.text.normal.copy(alpha = 0.6f) + ), + maxLines = 1 + ) + } + } +} + +@Composable +private fun FolderMenuItem(folder: IdeaFilePresentation) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = IdeaComposeIcons.Folder, + contentDescription = null, + tint = JewelTheme.globalColors.text.normal, + modifier = Modifier.size(16.dp) + ) + Text( + text = folder.presentablePath, + style = JewelTheme.defaultTextStyle.copy(fontSize = 13.sp), + maxLines = 1 + ) + } +} + +/** + * Search results grouped by type. + */ +data class SearchResults( + val files: List, + val folders: List, + val recentFiles: List +) + +/** + * File presentation data class for Compose UI. + */ +data class IdeaFilePresentation( + val virtualFile: VirtualFile, + val name: String, + val path: String, + val presentablePath: String, + val isRecentFile: Boolean = false, + val isDirectory: Boolean = false +) { + companion object { + fun from(project: Project, file: VirtualFile, isRecent: Boolean = false): IdeaFilePresentation { + val basePath = project.basePath ?: "" + val relativePath = if (file.path.startsWith(basePath)) { + file.path.removePrefix(basePath).removePrefix("/") + } else { + file.path + } + + return IdeaFilePresentation( + virtualFile = file, + name = file.name, + path = file.path, + presentablePath = relativePath, + isRecentFile = isRecent, + isDirectory = file.isDirectory + ) + } + } +} + +private fun loadRecentFiles(project: Project): List { + val recentFiles = mutableListOf() + + 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 +} + +private fun searchAllItems(project: Project, query: String): SearchResults { + val files = mutableListOf() + val folders = mutableListOf() + val scope = GlobalSearchScope.projectScope(project) + val lowerQuery = query.lowercase() + + try { + 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 + } + } + } catch (e: Exception) { + // Ignore search errors + } + + // Filter recent files by query + val recentFiles = loadRecentFiles(project).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 + ) +} + +private fun canBeAdded(project: Project, file: VirtualFile): Boolean { + if (!file.isValid) return false + if (file.isDirectory) return true // Allow directories + + val fileIndex = ProjectFileIndex.getInstance(project) + if (!fileIndex.isInContent(file)) return false + if (fileIndex.isUnderIgnored(file)) return false + + // Skip binary files + val extension = file.extension?.lowercase() ?: "" + val binaryExtensions = setOf("jar", "class", "exe", "dll", "so", "dylib", "png", "jpg", "jpeg", "gif", "ico", "pdf", "zip", "tar", "gz") + if (extension in binaryExtensions) return false + + return true +} + diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt index fecb461afc..f9ea0fb258 100644 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/editor/IdeaTopToolbar.kt @@ -12,10 +12,11 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import cc.unitmesh.devins.idea.toolwindow.IdeaComposeIcons +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.component.Icon import org.jetbrains.jewel.ui.component.Text @@ -24,21 +25,22 @@ import org.jetbrains.jewel.ui.component.Tooltip /** * Top toolbar for the input section. * Contains @ trigger, file selection, and other context-related actions. - * + * * Layout: @ - / - Clipboard - Save - Cursor | Selected Files... | Add */ @Composable fun IdeaTopToolbar( + project: Project? = null, onAtClick: () -> Unit = {}, onSlashClick: () -> Unit = {}, - onClipboardClick: () -> Unit = {}, - onSaveClick: () -> Unit = {}, - onCursorClick: () -> Unit = {}, onAddFileClick: () -> Unit = {}, selectedFiles: List = emptyList(), onRemoveFile: (SelectedFileItem) -> Unit = {}, + onFilesSelected: (List) -> Unit = {}, modifier: Modifier = Modifier ) { + var showFileSearchPopup by remember { mutableStateOf(false) } + Row( modifier = modifier .fillMaxWidth() @@ -51,54 +53,28 @@ fun IdeaTopToolbar( horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically ) { - // @ trigger button - ToolbarIconButton(onClick = onAtClick, tooltip = "@ Agent/File Reference") { - Icon( - imageVector = IdeaComposeIcons.AlternateEmail, - contentDescription = "@ Agent", - tint = JewelTheme.globalColors.text.normal, - modifier = Modifier.size(16.dp) - ) - } - // / trigger button - ToolbarIconButton(onClick = onSlashClick, tooltip = "/ Commands") { - Text(text = "/", style = JewelTheme.defaultTextStyle.copy(fontSize = 14.sp, fontWeight = FontWeight.Bold)) - } - // Clipboard button - ToolbarIconButton(onClick = onClipboardClick, tooltip = "Paste from Clipboard") { + ToolbarIconButton( + onClick = { + if (project != null) { + showFileSearchPopup = true + } + onAddFileClick() + }, + tooltip = "Add File" + ) { Icon( - imageVector = IdeaComposeIcons.ContentPaste, - contentDescription = "Clipboard", - modifier = Modifier.size(16.dp), - tint = JewelTheme.globalColors.text.normal - ) - } - // Save button - ToolbarIconButton(onClick = onSaveClick, tooltip = "Save to Workspace") { - Icon( - imageVector = IdeaComposeIcons.Save, - contentDescription = "Save", - modifier = Modifier.size(16.dp), - tint = JewelTheme.globalColors.text.normal - ) - } - // Cursor button - ToolbarIconButton(onClick = onCursorClick, tooltip = "Current Selection") { - Icon( - imageVector = IdeaComposeIcons.TextFields, - contentDescription = "Cursor", + imageVector = IdeaComposeIcons.Add, + contentDescription = "Add File", modifier = Modifier.size(16.dp), tint = JewelTheme.globalColors.text.normal ) } } - // Separator if (selectedFiles.isNotEmpty()) { Box(Modifier.width(1.dp).height(20.dp).background(JewelTheme.globalColors.borders.normal)) } - // Selected files as chips Row( modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.spacedBy(4.dp), @@ -108,16 +84,18 @@ fun IdeaTopToolbar( FileChip(file = file, onRemove = { onRemoveFile(file) }) } } + } - // Add file button - ToolbarIconButton(onClick = onAddFileClick, tooltip = "Add File") { - Icon( - imageVector = IdeaComposeIcons.Add, - contentDescription = "Add File", - modifier = Modifier.size(16.dp), - tint = JewelTheme.globalColors.text.normal - ) - } + // File search popup + if (showFileSearchPopup && project != null) { + IdeaFileSearchPopup( + project = project, + onDismiss = { showFileSearchPopup = false }, + onFilesSelected = { files -> + onFilesSelected(files) + showFileSearchPopup = false + } + ) } } diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt index 98667eab3e..cdbf724294 100644 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentApp.kt @@ -13,6 +13,8 @@ import cc.unitmesh.devins.idea.editor.IdeaDevInInput import cc.unitmesh.devins.idea.editor.IdeaInputListener import cc.unitmesh.devins.idea.editor.IdeaInputTrigger import cc.unitmesh.devins.idea.editor.IdeaModelConfigDialogWrapper +import cc.unitmesh.devins.idea.editor.IdeaTopToolbar +import cc.unitmesh.devins.idea.editor.SelectedFileItem import cc.unitmesh.devins.idea.toolwindow.codereview.IdeaCodeReviewContent import cc.unitmesh.devins.idea.toolwindow.codereview.IdeaCodeReviewViewModel import cc.unitmesh.devins.idea.components.header.IdeaAgentTabsHeader @@ -178,7 +180,6 @@ fun IdeaAgentApp( onAbort = { viewModel.cancelTask() }, workspacePath = project.basePath, totalTokens = null, - onSettingsClick = { viewModel.setShowConfigDialog(true) }, onAtClick = {}, availableConfigs = availableConfigs, currentConfigName = currentConfigName, @@ -224,7 +225,6 @@ fun IdeaAgentApp( onAbort = { remoteVm.cancelTask() }, workspacePath = project.basePath, totalTokens = null, - onSettingsClick = { viewModel.setShowConfigDialog(true) }, onAtClick = {}, availableConfigs = availableConfigs, currentConfigName = currentConfigName, @@ -323,7 +323,6 @@ private fun IdeaDevInInputArea( onAbort: () -> Unit, workspacePath: String? = null, totalTokens: Int? = null, - onSettingsClick: () -> Unit = {}, onAtClick: () -> Unit = {}, availableConfigs: List = emptyList(), currentConfigName: String? = null, @@ -332,10 +331,31 @@ private fun IdeaDevInInputArea( ) { var inputText by remember { mutableStateOf("") } var devInInput by remember { mutableStateOf(null) } + var selectedFiles by remember { mutableStateOf>(emptyList()) } Column( modifier = Modifier.fillMaxSize().padding(8.dp) ) { + // Top toolbar with file selection + IdeaTopToolbar( + project = project, + onAtClick = onAtClick, + selectedFiles = selectedFiles, + onRemoveFile = { file -> + selectedFiles = selectedFiles.filter { it.path != file.path } + }, + onFilesSelected = { files -> + val newItems = files.map { vf -> + SelectedFileItem( + name = vf.name, + path = vf.path, + virtualFile = vf + ) + } + selectedFiles = (selectedFiles + newItems).distinctBy { it.path } + } + ) + // DevIn Editor via SwingPanel - uses weight(1f) to fill available space SwingPanel( modifier = Modifier @@ -356,9 +376,18 @@ private fun IdeaDevInInputArea( override fun onSubmit(text: String, trigger: IdeaInputTrigger) { if (text.isNotBlank() && !isProcessing) { - onSend(text) + // Append file references to the message + val filesText = selectedFiles.joinToString("\n") { "/file:${it.path}" } + val fullText = if (filesText.isNotEmpty()) { + "$text\n$filesText" + } else { + text + } + onSend(fullText) clearInput() inputText = "" + // Clear selected files after sending + selectedFiles = emptyList() } } @@ -388,20 +417,28 @@ private fun IdeaDevInInputArea( } ) - // Bottom toolbar with Compose + // Bottom toolbar with Compose (MCP config is handled internally) IdeaBottomToolbar( onSendClick = { val text = devInInput?.text?.trim() ?: inputText.trim() if (text.isNotBlank() && !isProcessing) { - onSend(text) + // Append file references to the message + val filesText = selectedFiles.joinToString("\n") { "/file:${it.path}" } + val fullText = if (filesText.isNotEmpty()) { + "$text\n$filesText" + } else { + text + } + onSend(fullText) devInInput?.clearInput() inputText = "" + // Clear selected files after sending + selectedFiles = emptyList() } }, sendEnabled = inputText.isNotBlank() && !isProcessing, isExecuting = isProcessing, onStopClick = onAbort, - onSettingsClick = onSettingsClick, totalTokens = totalTokens, availableConfigs = availableConfigs, currentConfigName = currentConfigName, diff --git a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt index 5ffb0dbc7f..a188c29394 100644 --- a/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt +++ b/mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt @@ -1540,5 +1540,47 @@ object IdeaComposeIcons { }.build() } + /** + * History icon (clock with arrow) + */ + val History: ImageVector by lazy { + ImageVector.Builder( + name = "History", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f + ).apply { + path( + fill = SolidColor(Color.Black) + ) { + moveTo(13f, 3f) + curveToRelative(-4.97f, 0f, -9f, 4.03f, -9f, 9f) + horizontalLineTo(1f) + lineToRelative(3.89f, 3.89f) + lineToRelative(0.07f, 0.14f) + lineTo(9f, 12f) + horizontalLineTo(6f) + curveToRelative(0f, -3.87f, 3.13f, -7f, 7f, -7f) + reflectiveCurveToRelative(7f, 3.13f, 7f, 7f) + reflectiveCurveToRelative(-3.13f, 7f, -7f, 7f) + curveToRelative(-1.93f, 0f, -3.68f, -0.79f, -4.94f, -2.06f) + lineToRelative(-1.42f, 1.42f) + curveTo(8.27f, 19.99f, 10.51f, 21f, 13f, 21f) + curveToRelative(4.97f, 0f, 9f, -4.03f, 9f, -9f) + reflectiveCurveToRelative(-4.03f, -9f, -9f, -9f) + close() + moveTo(12f, 8f) + verticalLineToRelative(5f) + lineToRelative(4.28f, 2.54f) + lineToRelative(0.72f, -1.21f) + lineToRelative(-3.5f, -2.08f) + verticalLineTo(8f) + horizontalLineTo(12f) + close() + } + }.build() + } + }