Skip to content

Inlay code complete #110

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

Merged
merged 11 commits into from
Mar 20, 2024
31 changes: 31 additions & 0 deletions src/222/main/resources/META-INF/autodev-core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
parentId="cc.unitmesh.devti.settings.AutoDevSettingsConfigurable"
id="cc.unitmesh.autodevCoder"
bundle="messages.AutoDevBundle" key="settings.autodev.coder"/>
<applicationService
serviceInterface="com.intellij.temporary.inlay.codecomplete.LLMInlayManager"
serviceImplementation="com.intellij.temporary.inlay.codecomplete.LLMInlayManagerImpl"/>

<applicationService serviceImplementation="cc.unitmesh.devti.settings.AutoDevSettingsState"/>

Expand Down Expand Up @@ -60,6 +63,9 @@

<notificationGroup id="AutoDev.notify" displayType="STICKY_BALLOON" bundle="messages.AutoDevBundle"
key="name"/>
<!-- <editorFactoryListener implementation="cc.unitmesh.devti.editor.inlay.AutoDevEditorListener"/>-->
<!-- <typedHandler order="first, before completionAutoPopup"-->
<!-- implementation="cc.unitmesh.devti.editor.inlay.TypeOverHandler"/>-->

<intentionAction>
<className>cc.unitmesh.devti.intentions.AutoDevIntentionHelper</className>
Expand Down Expand Up @@ -174,6 +180,10 @@
<listener topic="com.intellij.ide.plugins.DynamicPluginListener"
class="cc.unitmesh.devti.AutoDevUnloadListener"/>
</applicationListeners>
<!-- <projectListeners>-->
<!-- <listener topic="com.intellij.openapi.command.CommandListener"-->
<!-- class="cc.unitmesh.devti.editor.inlay.LLMCommandListener"/>-->
<!-- </projectListeners>-->

<extensions defaultExtensionNs="cc.unitmesh">
<autoDevIntention>
Expand All @@ -186,6 +196,11 @@
<bundleName>messages.AutoDevBundle</bundleName>
<categoryKey>intention.category.llm</categoryKey>
</autoDevIntention>
<autoDevIntention>
<className>cc.unitmesh.devti.intentions.action.CodeCompletionInlayIntention</className>
<bundleName>messages.AutoDevBundle</bundleName>
<categoryKey>intention.category.llm</categoryKey>
</autoDevIntention>
<autoDevIntention>
<className>cc.unitmesh.devti.intentions.action.AutoTestThisBaseIntention</className>
<bundleName>messages.AutoDevBundle</bundleName>
Expand All @@ -201,6 +216,22 @@
</extensions>

<actions>
<action id="llm.applyInlays"
class="cc.unitmesh.devti.actions.LLMApplyInlaysAction">
<keyboard-shortcut first-keystroke="TAB" keymap="$default"/>
<override-text place="MainMenu" text="Apply Completions to Editor"/>
<override-text place="EditorPopup" text="Accept"/>
</action>
<action id="cc.unitmesh.devti.inlayCompleteCode"
class="cc.unitmesh.devti.actions.InlayCompleteCodeAction"
text="Inlay Complete Code"
description="Inlay complete code!"
>
<keyboard-shortcut keymap="$default" first-keystroke="control PERIOD"/>

<add-to-group group-id="ShowIntentionsGroup" relative-to-action="ShowIntentionActions" anchor="after"/>
</action>

<group id="AutoDevIntentionsActionGroup" class="cc.unitmesh.devti.intentions.IntentionsActionGroup"
icon="cc.unitmesh.devti.AutoDevIcons.AI_COPILOT" searchable="false">

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package com.intellij.temporary.inlay.presentation

import com.intellij.codeInsight.codeVision.CodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.model.RangeCodeVisionModel
import com.intellij.codeInsight.codeVision.ui.renderers.painters.ICodeVisionEntryBasePainter
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.temporary.inlay.codecomplete.presentation.PresentationUtil.fontMetrics
import com.intellij.temporary.inlay.codecomplete.presentation.PresentationUtil.getFont
import com.intellij.temporary.inlay.codecomplete.presentation.PresentationUtil.getThemeInfoProvider
import com.intellij.ui.paint.EffectPainter2D
import com.intellij.util.ui.GraphicsUtil
import java.awt.*
import java.awt.geom.Rectangle2D
import javax.swing.text.StyleConstants
import kotlin.math.ceil

class LLMTextInlayPainter : ICodeVisionEntryBasePainter<String> {

override fun paint(
editor: Editor,
textAttributes: TextAttributes,
g: Graphics,
value: String,
point: Point,
state: RangeCodeVisionModel.InlayState,
hovered: Boolean,
hoveredEntry: CodeVisionEntry?
) {
renderCodeBlock(
editor,
value,
value.split("\n"),
g,
Rectangle2D.Double(point.x.toDouble(), point.y.toDouble(), 0.0, 0.0),
textAttributes,
)
}

override fun size(editor: Editor, state: RangeCodeVisionModel.InlayState, value: String): Dimension {
val themeInfoProvider = getThemeInfoProvider()
val fontMetrics = editor.component.getFontMetrics(themeInfoProvider.font(editor, 0))
return Dimension(fontMetrics.stringWidth(value), fontMetrics.height)
}

companion object {
fun calculateWidth(editor: Editor, text: String, textLines: List<String>): Int {
val metrics = fontMetrics(editor, getFont(editor, text))
var maxWidth = 0
for (line in textLines) {
maxWidth = Math.max(maxWidth, metrics.stringWidth(line))
}

return maxWidth
}

private fun renderEffects(
g2d: Graphics2D,
x: Double,
baseline: Double,
width: Double,
charHeight: Int,
descent: Int,
textAttributes: TextAttributes,
font: Font?,
) {
val effectType = textAttributes.effectType
if (effectType == null || effectType.ordinal == StyleConstants.CharacterConstants.Underline) {
EffectPainter2D.LINE_UNDERSCORE.paint(
g2d, x, baseline,
width, 5.0, font
)
}
}

private fun renderBackground(
g: Graphics2D,
attributes: TextAttributes,
x: Double,
y: Double,
width: Double,
height: Double,
) {
val color = attributes.backgroundColor
if (color != null) {
g.color = color
g.fillRoundRect(x.toInt(), y.toInt(), width.toInt(), height.toInt(), 1, 1)
}
}

fun renderCodeBlock(
editor: Editor,
content: String,
contentLines: List<String>,
g: Graphics,
region: Rectangle2D,
textAttributes: TextAttributes,
) {
if (content.isEmpty() || contentLines.isEmpty()) return

val themeInfoProvider = getThemeInfoProvider()
val attributes = editor.selectionModel.textAttributes

val inSelectedBlock = textAttributes.backgroundColor == attributes.backgroundColor
val foregroundColor = textAttributes.foregroundColor ?: if (inSelectedBlock) {
attributes.foregroundColor ?: editor.colorsScheme.defaultForeground
} else {
themeInfoProvider.foregroundColor(editor, false)
}

val clipBounds = g.clipBounds
val g2 = g.create() as Graphics2D
GraphicsUtil.setupAAPainting(g2)
val font = themeInfoProvider.font(editor, 0)
g2.font = font

val metrics = fontMetrics(editor, font)
val lineHeight = editor.lineHeight.toDouble()
val fontBaseline = ceil(font.createGlyphVector(metrics.fontRenderContext, "Alb").visualBounds.height)
val linePadding = (lineHeight - fontBaseline) / 2.0

val offsetX = region.x
val offsetY = region.y + fontBaseline + linePadding

var lineOffset = 0
g2.clip = if (clipBounds != null && clipBounds != region) region.createIntersection(clipBounds) else region

for (line in contentLines) {
renderBackground(g2, attributes, offsetX, region.y + lineOffset, region.width, lineHeight)
g2.color = foregroundColor
g2.drawString(line, offsetX.toFloat(), (offsetY + lineOffset).toFloat())
if (editor is EditorImpl) {
renderEffects(
g2,
offsetX,
offsetY + lineOffset,
metrics.stringWidth(line).toDouble(),
editor.charHeight,
editor.descent,
attributes,
font,
)
}
lineOffset = (lineOffset + lineHeight).toInt()
}
g2.dispose()
}
}

}

30 changes: 30 additions & 0 deletions src/233/main/resources/META-INF/autodev-core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@

<applicationService serviceImplementation="cc.unitmesh.devti.settings.AutoDevSettingsState"/>

<applicationService
serviceInterface=" com.intellij.temporary.inlay.codecomplete.LLMInlayManager"
serviceImplementation=" com.intellij.temporary.inlay.codecomplete.LLMInlayManagerImpl"/>

<typedHandler order="first, before completionAutoPopup"
implementation="cc.unitmesh.devti.editor.inlay.TypeOverHandler"/>


<statusBarWidgetFactory id="AIAssistant"
implementation="cc.unitmesh.devti.statusbar.AutoDevStatusBarWidgetFactory"/>

Expand Down Expand Up @@ -175,6 +183,11 @@
</applicationListeners>

<extensions defaultExtensionNs="cc.unitmesh">
<autoDevIntention>
<className>cc.unitmesh.devti.intentions.action.CodeCompletionInlayIntention</className>
<bundleName>messages.AutoDevBundle</bundleName>
<categoryKey>intention.category.llm</categoryKey>
</autoDevIntention>
<autoDevIntention>
<className>cc.unitmesh.devti.intentions.action.NewChatWithCodeBaseIntention</className>
<bundleName>messages.AutoDevBundle</bundleName>
Expand All @@ -200,6 +213,23 @@
</extensions>

<actions>
<action id="llm.applyInlays"
class="cc.unitmesh.devti.actions.LLMApplyInlaysAction">
<keyboard-shortcut first-keystroke="TAB" keymap="$default"/>
<override-text place="MainMenu" text="Apply Completions to Editor"/>
<override-text place="EditorPopup" text="Accept"/>
</action>

<action id="cc.unitmesh.devti.inlayCompleteCode"
class="cc.unitmesh.devti.actions.InlayCompleteCodeAction"
text="Inlay Complete Code"
description="Inlay complete code!"
>
<keyboard-shortcut keymap="$default" first-keystroke="control PERIOD"/>

<add-to-group group-id="ShowIntentionsGroup" relative-to-action="ShowIntentionActions" anchor="after"/>
</action>

<group id="AutoDevIntentionsActionGroup" class="cc.unitmesh.devti.intentions.IntentionsActionGroup"
icon="cc.unitmesh.devti.AutoDevIcons.AI_COPILOT" searchable="false">

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package cc.unitmesh.devti.actions

import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.command.CommandProcessor
import com.intellij.temporary.inlay.codecomplete.LLMInlayManager

/**
* A quick insight action is an action that can be triggered by a user,
* user can input custom text to call with LLM.
*/
class InlayCompleteCodeAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val dataContext = e.dataContext
val editor = dataContext.getData(CommonDataKeys.EDITOR) ?: return
val project = dataContext.getData(CommonDataKeys.PROJECT) ?: return
if (project.isDisposed) return;

val commandProcessor = CommandProcessor.getInstance()
if (commandProcessor.isUndoTransparentActionInProgress) return

val llmInlayManager = LLMInlayManager.getInstance()
llmInlayManager.editorModified(editor)
}
}

Loading