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

Completion #132

Closed
wants to merge 2 commits into from
Closed
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 .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ repositories {
// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
intellij {
// version.set("LATEST-EAP-SNAPSHOT")
version.set("2022.3.1")
type.set("PC") // Target IDE Platform
version.set("2024.1")
type.set("IU") // Target IDE Platform

plugins.set(listOf(
"Git4Idea",
Expand All @@ -51,8 +51,8 @@ tasks {
}

patchPluginXml {
sinceBuild.set("223")
untilBuild.set("241.*")
sinceBuild.set("241")
untilBuild.set("242.*")
}

signPlugin {
Expand Down
2 changes: 1 addition & 1 deletion refact_lsp
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.7.2
main
44 changes: 44 additions & 0 deletions src/main/kotlin/com/smallcloud/refactai/Initializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ 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.diagnostic.Logger
import com.intellij.openapi.keymap.Keymap
import com.intellij.openapi.keymap.KeymapManagerListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.startup.StartupActivity
import com.smallcloud.refactai.account.LoginStateService
import com.smallcloud.refactai.account.login
import com.smallcloud.refactai.io.ConnectivityManager
import com.smallcloud.refactai.io.InferenceGlobalContext
import com.smallcloud.refactai.listeners.ACTION_ID_
import com.smallcloud.refactai.listeners.UninstallListener
import com.smallcloud.refactai.lsp.LSPProcessHolder
import com.smallcloud.refactai.lsp.lspProjectInitialize
Expand All @@ -20,6 +24,7 @@ import com.smallcloud.refactai.settings.AppSettingsState
import com.smallcloud.refactai.settings.settingsStartup
import com.smallcloud.refactai.statistic.UsageStats
import com.smallcloud.refactai.struct.DeploymentMode
import org.jetbrains.annotations.NonNls
import java.util.concurrent.atomic.AtomicBoolean


Expand Down Expand Up @@ -51,6 +56,45 @@ class Initializer : StartupActivity, Disposable {
PluginInstaller.addStateListener(UninstallListener())
UpdateChecker.instance
LSPProcessHolder.instance.startup()

ApplicationManager.getApplication()
.messageBus
.connect(PluginState.instance)
.subscribe(KeymapManagerListener.TOPIC, object : KeymapManagerListener {
override fun shortcutsChanged(
keymap: Keymap,
actionIds: @NonNls 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
)
}
}
}
}
})

}
PrivacyService.instance.projectOpened(project)
lspProjectInitialize(ProjectRootManager.getInstance(project).contentRoots.map { it.toString() })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smallcloud.refactai.modes.completion.structs
package com.smallcloud.refactai.codecompletion


data class Completion(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smallcloud.refactai.modes.completion
package com.smallcloud.refactai.codecompletion

import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.Key
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smallcloud.refactai.modes
package com.smallcloud.refactai.codecompletion

import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package com.smallcloud.refactai.codecompletion

import com.intellij.codeInsight.inline.completion.InlineCompletionFontUtils
import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorCustomElementRenderer
import com.intellij.openapi.editor.Inlay
import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.ex.util.EditorActionAvailabilityHint
import com.intellij.openapi.editor.ex.util.addActionAvailabilityHint
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.Disposer
import org.jetbrains.annotations.ApiStatus
import java.awt.Graphics
import java.awt.Rectangle


// delete this file ASAP

fun String.formatBeforeRendering(editor: Editor): String {
val tabSize = editor.settings.getTabSize(editor.project)
val tab = " ".repeat(tabSize)
return replace("\t", tab)
}

class InlineBlockElementRenderer(private val editor: Editor, lines: List<String>) : EditorCustomElementRenderer {

private val font = InlineCompletionFontUtils.font(editor)
private val width = editor
.contentComponent
.getFontMetrics(font)
.stringWidth(lines.maxBy { it.length })

val lines = lines.map { it.formatBeforeRendering(editor) }

override fun calcWidthInPixels(inlay: Inlay<*>) = width

override fun calcHeightInPixels(inlay: Inlay<*>) = editor.lineHeight * lines.size

override fun paint(inlay: Inlay<*>, g: Graphics, targetRegion: Rectangle, textAttributes: TextAttributes) {
g.color = InlineCompletionFontUtils.color(editor)
g.font = font
lines.forEachIndexed { i, it -> g.drawString(it, 0, targetRegion.y + editor.ascent + i * editor.lineHeight) }
}
}

class InlineSuffixRenderer(private val editor: Editor, suffix: String) : EditorCustomElementRenderer {
private val font = InlineCompletionFontUtils.font(editor)
private val width = editor.contentComponent.getFontMetrics(font).stringWidth(suffix)

val suffix = suffix.formatBeforeRendering(editor)

override fun calcWidthInPixels(inlay: Inlay<*>): Int = width
override fun calcHeightInPixels(inlay: Inlay<*>): Int {
return editor.contentComponent.getFontMetrics(font).height
}

override fun paint(inlay: Inlay<*>, g: Graphics, targetRegion: Rectangle, textAttributes: TextAttributes) {
g.color = InlineCompletionFontUtils.color(editor)
g.font = font
g.drawString(suffix, targetRegion.x, targetRegion.y + editor.ascent)
}
}


class InlineCompletionGrayTextElementCustom(override val text: String, private val delta: Int = 0) :
InlineCompletionElement {

override fun toPresentable(): InlineCompletionElement.Presentable = Presentable(this, delta)

open class Presentable(override val element: InlineCompletionElement, val delta: Int = 0) :
InlineCompletionElement.Presentable {
private var suffixInlay: Inlay<*>? = null
private var blockInlay: Inlay<*>? = null

override fun isVisible(): Boolean = suffixInlay != null || blockInlay != null

/**
* Temporal workaround for an internal plugin. **Should not be used.**
*/
@ApiStatus.Internal
@ApiStatus.Experimental
protected open fun getText(): String = element.text

override fun render(editor: Editor, offset: Int) {
val text = getText()
if (text.isEmpty()) return
val lines = text.lines()
renderSuffix(editor, lines, offset - delta)
if (lines.size > 1) {
renderBlock(lines.drop(1), editor, offset)
}
}

override fun getBounds(): Rectangle? {
val bounds = suffixInlay?.bounds?.let { Rectangle(it) }
blockInlay?.bounds?.let { bounds?.add(Rectangle(it)) }
return bounds
}

override fun startOffset(): Int? = suffixInlay?.offset
override fun endOffset(): Int? = suffixInlay?.offset

override fun dispose() {
blockInlay?.also(Disposer::dispose)
blockInlay = null
suffixInlay?.also(Disposer::dispose)
suffixInlay = null
}

private fun renderSuffix(editor: Editor, lines: List<String>, offset: Int) {
// The following is a hacky solution to the effect described in ML-977
// ML-1781 Inline completion renders on the left to the caret after moving it
editor.forceLeanLeft()

val line = lines.first()
if (line.isEmpty()) {
suffixInlay =
editor.inlayModel.addInlineElement(editor.caretModel.offset, object : EditorCustomElementRenderer {
override fun calcWidthInPixels(inlay: Inlay<*>) = 1
override fun calcHeightInPixels(inlay: Inlay<*>) = 1
override fun paint(
inlay: Inlay<*>,
g: Graphics,
targetRegion: Rectangle,
textAttributes: TextAttributes
) {
}
})
return
}
editor.inlayModel.execute(true) {
val element = editor.inlayModel.addInlineElement(offset, true, InlineSuffixRenderer(editor, line))
?: return@execute
element.addActionAvailabilityHint(
EditorActionAvailabilityHint(
IdeActions.ACTION_INSERT_INLINE_COMPLETION,
EditorActionAvailabilityHint.AvailabilityCondition.CaretOnStart,
)
)
suffixInlay = element
}
}

private fun renderBlock(
lines: List<String>,
editor: Editor,
offset: Int
) {
val element = editor.inlayModel.addBlockElement(
offset, true, false, 1,
InlineBlockElementRenderer(editor, lines)
) ?: return

blockInlay = element
}

private fun Editor.forceLeanLeft() {
val visualPosition = caretModel.visualPosition
if (visualPosition.leansRight) {
val leftLeaningPosition = VisualPosition(visualPosition.line, visualPosition.column, false)
caretModel.moveToVisualPosition(leftLeaningPosition)
}
}
}
}
Loading
Loading