Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes 5.2.2 #198

Merged
merged 2 commits into from
Dec 4, 2024
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
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pluginGroup = "com.smallcloud"
pluginName = Refact.ai
pluginRepositoryUrl = https://github.com/smallcloudai/refact-intellij
# SemVer format -> https://semver.org
pluginVersion = 5.2.1
pluginVersion = 5.2.2

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 241
Expand Down
2 changes: 1 addition & 1 deletion refact_lsp
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.10.4
v0.10.5
44 changes: 43 additions & 1 deletion src/main/kotlin/com/smallcloud/refactai/Initializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package com.smallcloud.refactai

import com.intellij.ide.plugins.PluginInstaller
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.keymap.Keymap
import com.intellij.openapi.keymap.KeymapManagerListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.smallcloud.refactai.io.CloudMessageService
import com.smallcloud.refactai.listeners.ACTION_ID_
import com.smallcloud.refactai.listeners.UninstallListener
import com.smallcloud.refactai.lsp.LSPActiveDocNotifierService
import com.smallcloud.refactai.lsp.LSPProcessHolder.Companion.initialize
Expand All @@ -21,7 +25,6 @@ import com.smallcloud.refactai.utils.isJcefCanStart
import java.util.concurrent.atomic.AtomicBoolean
import com.smallcloud.refactai.lsp.LSPProcessHolder.Companion.getInstance as getLSPProcessHolder


class Initializer : ProjectActivity, Disposable {
override suspend fun execute(project: Project) {
val shouldInitialize = !(initialized.getAndSet(true) || ApplicationManager.getApplication().isUnitTestMode)
Expand All @@ -36,6 +39,45 @@ class Initializer : ProjectActivity, Disposable {
notificationStartup()
PluginInstaller.addStateListener(UninstallListener())
UpdateChecker.instance

ApplicationManager.getApplication()
.messageBus
.connect(PluginState.instance)
.subscribe(KeymapManagerListener.TOPIC, object : KeymapManagerListener {
override fun shortcutsChanged(
keymap: Keymap,
actionIds: MutableCollection<String?>,
fromSettings: Boolean
) {
if (Thread.currentThread().stackTrace.count { it.className.startsWith("com.smallcloud.refactai.Initializer") } > 1) {
return
}
for (id in actionIds) {
if (!listOf(IdeActions.ACTION_INSERT_INLINE_COMPLETION, ACTION_ID_).contains(id)) {
continue
}
val shortcuts = keymap.getShortcuts(id)
if (id == IdeActions.ACTION_INSERT_INLINE_COMPLETION) {
keymap.removeAllActionShortcuts(ACTION_ID_)
for (shortcut in shortcuts) {
keymap.addShortcut(
ACTION_ID_,
shortcut
)
}
} else if (id == ACTION_ID_) {
keymap.removeAllActionShortcuts(IdeActions.ACTION_INSERT_INLINE_COMPLETION)
for (shortcut in shortcuts) {
keymap.addShortcut(
IdeActions.ACTION_INSERT_INLINE_COMPLETION,
shortcut
)
}
}
}
}
})

ApplicationManager.getApplication().getService(CloudMessageService::class.java)
if (!isJcefCanStart()) {
emitInfo(RefactAIBundle.message("notifications.chatCanNotStartWarning"), false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Document
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
import com.intellij.ui.components.JBLabel
import com.intellij.util.application
import com.intellij.util.ui.JBFont
import com.smallcloud.refactai.Resources
import com.smallcloud.refactai.io.ConnectionStatus
import com.smallcloud.refactai.io.streamedInferenceFetch
import com.smallcloud.refactai.lsp.LSPProcessHolder.Companion.getInstance
Expand All @@ -40,6 +43,8 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
import javax.swing.JComponent
import javax.swing.SwingConstants
import kotlin.io.path.Path
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
Expand Down Expand Up @@ -109,6 +114,17 @@ private val specialSymbolsRegex = "^[:\\s\\t\\n\\r(){},.\"'\\];]*\$".toRegex()
class RefactAICompletionProvider : DebouncedInlineCompletionProvider() {
private val logger = Logger.getInstance("inlineCompletion")

override val providerPresentation = object: InlineCompletionProviderPresentation {
override fun getTooltip(project: Project?): JComponent {
val selectedModelCode = InferenceGlobalContext.lastAutoModel ?: ""
val text = if (selectedModelCode.isNotEmpty()) {
"${Resources.titleStr}: $selectedModelCode"
} else {
Resources.titleStr
}
return JBLabel(text, Resources.Icons.LOGO_RED_16x16, SwingConstants.LEADING)
}
}
override val id: InlineCompletionProviderID = InlineCompletionProviderID("Refact.ai")
override val suggestionUpdateManager: InlineCompletionSuggestionUpdateManager = Default()
override val insertHandler: InlineCompletionInsertHandler = InsertHandler()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.smallcloud.refactai.listeners

import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.diff.impl.patch.IdeaTextPatchBuilder
import com.intellij.openapi.diff.impl.patch.UnifiedDiffWriter
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.VcsDataKeys
import com.intellij.openapi.vcs.VcsException
import com.intellij.ui.AnimatedIcon
import com.intellij.vcsUtil.VcsUtil
import com.smallcloud.refactai.RefactAIBundle
import com.smallcloud.refactai.Resources
import com.smallcloud.refactai.lsp.lspGetCommitMessage
import java.io.IOException
import java.io.StringWriter
import java.util.concurrent.ExecutionException


class GenerateGitCommitMessageAction : AnAction(
Resources.titleStr,
RefactAIBundle.message("generateCommitMessage.action.description"),
Resources.Icons.LOGO_RED_13x13
) {
private val spinIcon = AnimatedIcon.Default.INSTANCE
override fun update(event: AnActionEvent) {
ApplicationManager.getApplication().invokeLater {
val commitWorkflowUi = event.getData(VcsDataKeys.COMMIT_WORKFLOW_UI)
if (commitWorkflowUi == null) {
event.presentation.isVisible = false
return@invokeLater
}

val lspService =
event.project?.service<com.smallcloud.refactai.lsp.LSPProcessHolder>() ?: return@invokeLater

val isEnabled = lspService.isWorking && (commitWorkflowUi.getIncludedChanges().isNotEmpty() && commitWorkflowUi.getIncludedUnversionedFiles().isNotEmpty())

event.presentation.isEnabled = isEnabled
event.presentation.text = if (lspService.isWorking) {
RefactAIBundle.message("generateCommitMessage.action.selectFiles")
} else {
RefactAIBundle.message("generateCommitMessage.action.loginInRefactAI")
}
}
}

override fun actionPerformed(event: AnActionEvent) {
val project = event.project
if (project == null || project.basePath == null) {
return
}


val gitDiff = getDiff(event, project) ?: return
val commitWorkflowUi = event.getData(VcsDataKeys.COMMIT_WORKFLOW_UI)
if (commitWorkflowUi != null) {
ApplicationManager.getApplication().executeOnPooledThread {
event.presentation.icon = spinIcon
ApplicationManager.getApplication().invokeLater {
commitWorkflowUi.commitMessageUi.stopLoading()
}
val message = lspGetCommitMessage(project, gitDiff, commitWorkflowUi.commitMessageUi.text)
ApplicationManager.getApplication().invokeLater {
commitWorkflowUi.commitMessageUi.stopLoading()
commitWorkflowUi.commitMessageUi.setText(message)
}
event.presentation.icon = Resources.Icons.LOGO_RED_13x13
}
}
}

override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}

private fun getDiff(event: AnActionEvent, project: Project): String? {
val commitWorkflowUi = event.getData(VcsDataKeys.COMMIT_WORKFLOW_UI)
?: throw IllegalStateException("Could not retrieve commit workflow ui.")

try {
val projectFile = project.projectFile ?: return null
val projectFileVcsRoot = VcsUtil.getVcsRootFor(project, projectFile) ?: return null

try {
val includedChanges = commitWorkflowUi.getIncludedChanges()
commitWorkflowUi.getIncludedUnversionedFiles()
val filePatches = IdeaTextPatchBuilder.buildPatch(
project, includedChanges, projectFileVcsRoot.toNioPath(), false, true
)
val diffWriter = StringWriter()
UnifiedDiffWriter.write(
null,
projectFileVcsRoot.toNioPath(),
filePatches,
diffWriter,
"\n",
null,
null
)
return diffWriter.toString()
} catch (e: VcsException) {
throw RuntimeException("Unable to create git diff", e)
} catch (e: IOException) {
throw RuntimeException("Unable to create git diff", e)
}
} catch (e: InterruptedException) {
throw RuntimeException(e)
} catch (e: ExecutionException) {
throw RuntimeException(e)
}
}
}
22 changes: 22 additions & 0 deletions src/main/kotlin/com/smallcloud/refactai/lsp/LSPHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,25 @@ fun lspGetCodeLens(editor: Editor): String {
return res
}
}

fun lspGetCommitMessage(project: Project, diff: String, currentMessage: String): String {
val url = getLSPProcessHolder(project)?.url?.resolve("/v1/commit-message-from-diff") ?: return ""
val data = Gson().toJson(
mapOf(
"diff" to diff,
"text" to currentMessage,
)
)
InferenceGlobalContext.connection.post(url, data, dataReceiveEnded={
InferenceGlobalContext.status = ConnectionStatus.CONNECTED
InferenceGlobalContext.lastErrorMsg = null
}, failedDataReceiveEnded = {
InferenceGlobalContext.status = ConnectionStatus.ERROR
if (it != null) {
InferenceGlobalContext.lastErrorMsg = it.message
}
}).let {
val res = it.get()!!.get() as String
return res
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ class Events {
class Config {
abstract class BaseFeatures()

data class Features(val ast: Boolean, val vecdb: Boolean, val images: Boolean? = false) : BaseFeatures()
data class Features(val ast: Boolean, val vecdb: Boolean, val images: Boolean? = true) : BaseFeatures()

data class ThemeProps(
val mode: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.fileChooser.FileChooser
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.keymap.Keymap
import com.intellij.openapi.keymap.KeymapManager
import com.intellij.openapi.keymap.KeymapUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.SystemInfo
import com.intellij.ui.jcef.JBCefBrowser
import com.intellij.ui.jcef.JBCefBrowserBase
Expand All @@ -26,12 +29,13 @@ import kotlinx.coroutines.launch
import org.cef.CefApp
import org.cef.CefSettings
import org.cef.browser.CefBrowser
import org.cef.handler.CefDisplayHandlerAdapter
import org.cef.handler.CefKeyboardHandler
import org.cef.handler.CefKeyboardHandlerAdapter
import org.cef.handler.CefLoadHandlerAdapter
import org.cef.callback.CefFileDialogCallback
import org.cef.handler.*
import java.util.*
import java.util.concurrent.atomic.AtomicReference
import javax.swing.JComponent


fun getActionKeybinding(actionId: String): String {
// Get the KeymapManager instance
val keymapManager: KeymapManager = KeymapManager.getInstance()
Expand Down Expand Up @@ -77,6 +81,23 @@ class ChatWebView(val editor: Editor, val messageHandler: (event: Events.FromCha
}
}

fun showFileChooserDialog(project: Project?, title: String?, isMultiple: Boolean, filters: Vector<String>): String {
val filePath: AtomicReference<String> = AtomicReference("")
ApplicationManager.getApplication().invokeAndWait {
var fileChooserDescriptor =
FileChooserDescriptor(true, false, false, false, false, false)
fileChooserDescriptor.title = if (title.isNullOrEmpty() || title.isBlank()) "Choose File" else title
fileChooserDescriptor =
fileChooserDescriptor.withFileFilter { file -> filters.any { filter -> file.name.endsWith(filter) } }
val file = FileChooser.chooseFile(fileChooserDescriptor, project, null)
if (file != null) {
filePath.set(file.canonicalPath)
}
}
return filePath.get()
}


val webView by lazy {
val isOSREnable = when {
SystemInfo.isWindows -> false
Expand All @@ -98,11 +119,13 @@ class ChatWebView(val editor: Editor, val messageHandler: (event: Events.FromCha
if (!isOSREnable) {
val onTabHandler: CefKeyboardHandler = object : CefKeyboardHandlerAdapter() {
override fun onKeyEvent(browser: CefBrowser?, event: CefKeyboardHandler.CefKeyEvent?): Boolean {
val wasTabPressed = event?.type == CefKeyboardHandler.CefKeyEvent.EventType.KEYEVENT_KEYUP && event.modifiers == 0 && event.character == '\t';
val wasTabPressed =
event?.type == CefKeyboardHandler.CefKeyEvent.EventType.KEYEVENT_KEYUP && event.modifiers == 0 && event.character == '\t';
val currentEditor = FileEditorManager.getInstance(editor.project).selectedTextEditor
val isInDiffMode = currentEditor != null && ModeProvider.getOrCreateModeProvider(currentEditor).isDiffMode()
val isInDiffMode =
currentEditor != null && ModeProvider.getOrCreateModeProvider(currentEditor).isDiffMode()

if(wasTabPressed && currentEditor != null && isInDiffMode) {
if (wasTabPressed && currentEditor != null && isInDiffMode) {
ApplicationManager.getApplication().invokeLater {
ModeProvider.getOrCreateModeProvider(currentEditor)
.onTabPressed(currentEditor, null, DataContext.EMPTY_CONTEXT)
Expand Down Expand Up @@ -144,6 +167,22 @@ class ChatWebView(val editor: Editor, val messageHandler: (event: Events.FromCha
return super.onConsoleMessage(browser, level, message, source, line)
}
}, browser.cefBrowser)
if (SystemInfo.isLinux) {
browser.jbCefClient.addDialogHandler({ cefBrowser, mode, title, defaultFilePath, filters, callback ->
val filePath = showFileChooserDialog(
editor.project,
title,
mode == CefDialogHandler.FileDialogMode.FILE_DIALOG_OPEN_MULTIPLE,
filters
)
if (filePath.isNotEmpty()) {
callback.Continue(Vector(listOf(filePath)))
} else {
callback.Cancel()
}
true
}, browser.cefBrowser)
}

CefApp.getInstance().registerSchemeHandlerFactory("http", "refactai", RequestHandlerFactory())

Expand Down
7 changes: 7 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ integrated into a single package that follows your privacy settings.</p>
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
<separator/>
</group>
<!-- Temporary disable; JB has broken vcs plugin in 2024.3 -->
<!-- <group id="RefactAI.GenerateGitCommitMessageGroup">-->
<!-- <add-to-group group-id="Vcs.MessageActionGroup" anchor="first"/>-->
<!-- <action-->
<!-- id="RefactAIGenerateGitCommitMessage"-->
<!-- class="com.smallcloud.refactai.listeners.GenerateGitCommitMessageAction" />-->
<!-- </group>-->
</actions>
<resource-bundle>bundles.RefactAI</resource-bundle>
<applicationListeners>
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/bundles/RefactAI.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
cancel=Cancel

generateCommitMessage.action.description=Generate git commit message
generateCommitMessage.action.selectFiles=Select files
generateCommitMessage.action.loginInRefactAI=Login in Refact.ai
privacy.contextMenu=Refact.ai Privacy
privacy.privacyRules=Privacy Rules
privacy.addPrivacyRole=Add Privacy Rule
Expand Down
Loading
Loading