Skip to content

Commit 0393ce5

Browse files
committed
feat(intellij): refactor CodeLens handling and add DiffHighlighter
1 parent 3551511 commit 0393ce5

File tree

7 files changed

+324
-8
lines changed

7 files changed

+324
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,78 @@
11
package com.tabbyml.intellijtabby.inlineChat
22

33
import com.intellij.codeInsight.codeVision.*
4+
import com.intellij.codeInsight.codeVision.CodeVisionState.Companion.READY_EMPTY
5+
import com.intellij.codeInsight.codeVision.ui.model.ClickableTextCodeVisionEntry
6+
import com.intellij.icons.AllIcons
7+
import com.intellij.openapi.components.serviceOrNull
8+
import com.intellij.openapi.diagnostic.Logger
49
import com.intellij.openapi.editor.Editor
5-
import javax.swing.Icon
10+
import com.intellij.openapi.fileEditor.FileDocumentManager
11+
import com.intellij.openapi.project.Project
12+
import com.intellij.openapi.util.TextRange
13+
import com.intellij.psi.PsiDocumentManager
14+
import com.tabbyml.intellijtabby.lsp.ConnectionService
15+
import kotlinx.coroutines.CoroutineScope
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.launch
18+
import org.eclipse.lsp4j.CodeLens
19+
import org.eclipse.lsp4j.CodeLensParams
20+
import org.eclipse.lsp4j.TextDocumentIdentifier
21+
import java.util.concurrent.CompletableFuture
622

723

8-
class InlineChatCodeVisionProvider: CodeVisionProvider<Any> {
24+
class InlineChatCodeVisionProvider : CodeVisionProvider<Any> {
25+
private val logger = Logger.getInstance(InlineChatCodeVisionProvider::class.java)
26+
27+
private val scope = CoroutineScope(Dispatchers.IO)
928
override val defaultAnchor: CodeVisionAnchorKind = CodeVisionAnchorKind.Top
1029
override val id: String = "InlineChatCodeVisionProvider"
1130
override val name: String = "Inline Chat Code Vision Provider"
12-
override val relativeOrderings: List<CodeVisionRelativeOrdering> = listOf()
31+
override val relativeOrderings: List<CodeVisionRelativeOrdering> = listOf(CodeVisionRelativeOrdering.CodeVisionRelativeOrderingFirst)
1332

1433
override fun precomputeOnUiThread(editor: Editor): Any {
1534
return Any()
1635
}
1736

1837
override fun computeCodeVision(editor: Editor, uiData: Any): CodeVisionState {
19-
TODO()
38+
val project = editor.project ?: return READY_EMPTY
39+
val document = editor.document
40+
val virtualFile = FileDocumentManager.getInstance()
41+
.getFile(editor.document)
42+
val uri = virtualFile?.url ?: return READY_EMPTY
43+
val codeLenses = getCodeLenses(project, uri).get() ?: return READY_EMPTY
44+
println("Code lenses: $codeLenses")
45+
// && (it.command?.arguments?.firstOrNull() as? Map<*, *>)?.get("action") != null
46+
val lenses: List<Pair<TextRange, CodeVisionEntry>> = codeLenses.filter { it.command != null }.mapIndexed { index, it ->
47+
val startOffset = document.getLineStartOffset(it.range.start.line) + it.range.start.character
48+
val endOffset = document.getLineStartOffset(it.range.end.line) + it.range.end.character
49+
// val title = it.command.title
50+
val title = "$index"
51+
val entry =
52+
ClickableTextCodeVisionEntry(title, id, { _, _ -> }, AllIcons.Actions.AddFile, "", "", emptyList())
53+
val textRange = TextRange(startOffset + index, startOffset + index + 1)
54+
textRange to entry
55+
}
56+
println("lenses: $lenses")
57+
return CodeVisionState.Ready(lenses)
58+
}
59+
60+
private fun getCodeLenses(project: Project, uri: String): CompletableFuture<List<CodeLens>?> {
61+
val params = CodeLensParams(TextDocumentIdentifier(uri))
62+
return CompletableFuture<List<CodeLens>?>().also { future ->
63+
scope.launch {
64+
try {
65+
val server = project.serviceOrNull<ConnectionService>()?.getServerAsync() ?: run {
66+
future.complete(null)
67+
return@launch
68+
}
69+
val result = server.textDocumentFeature.codeLens(params)
70+
future.complete(result.get())
71+
} catch (e: Exception) {
72+
future.completeExceptionally(e)
73+
}
74+
}
75+
}
2076
}
2177

2278
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package com.tabbyml.intellijtabby.inlineChat
2+
3+
import com.google.gson.JsonObject
4+
import com.intellij.codeHighlighting.*
5+
import com.intellij.openapi.editor.colors.EditorColors
6+
import com.intellij.codeInsight.daemon.impl.HighlightInfo
7+
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
8+
import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil
9+
import com.intellij.lang.annotation.HighlightSeverity
10+
import com.intellij.openapi.application.invokeLater
11+
import com.intellij.openapi.components.serviceOrNull
12+
import com.intellij.openapi.editor.Document
13+
import com.intellij.openapi.editor.Editor
14+
import com.intellij.openapi.editor.colors.EditorColorsManager
15+
import com.intellij.openapi.editor.markup.TextAttributes
16+
import com.intellij.openapi.fileEditor.FileDocumentManager
17+
import com.intellij.openapi.progress.ProgressIndicator
18+
import com.intellij.openapi.project.Project
19+
import com.intellij.openapi.util.TextRange
20+
import com.intellij.psi.PsiFile
21+
import com.tabbyml.intellijtabby.lsp.ConnectionService
22+
import kotlinx.coroutines.CoroutineScope
23+
import kotlinx.coroutines.Dispatchers
24+
import kotlinx.coroutines.launch
25+
import kotlinx.serialization.json.jsonPrimitive
26+
import org.eclipse.lsp4j.CodeLens
27+
import org.eclipse.lsp4j.CodeLensParams
28+
import org.eclipse.lsp4j.TextDocumentIdentifier
29+
import org.jetbrains.kotlin.idea.gradleTooling.get
30+
import java.awt.Color
31+
import java.awt.Font
32+
import java.util.concurrent.CompletableFuture
33+
34+
class DiffHighlighterRegister : TextEditorHighlightingPassFactoryRegistrar {
35+
override fun registerHighlightingPassFactory(register: TextEditorHighlightingPassRegistrar, project: Project) {
36+
register.registerTextEditorHighlightingPass(
37+
DiffHighlightingPassFactory(), TextEditorHighlightingPassRegistrar.Anchor.LAST,
38+
Pass.UPDATE_ALL, false, false
39+
)
40+
}
41+
}
42+
43+
class DiffHighlightingPassFactory : TextEditorHighlightingPassFactory {
44+
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
45+
// You can add conditions to determine if the pass should be created
46+
if (!file.isValid || !shouldCreateHighlightingPass(file, editor)) {
47+
return null
48+
}
49+
50+
return DiffHighLightingPass(file.project, editor.document, editor)
51+
}
52+
53+
private fun shouldCreateHighlightingPass(file: PsiFile, editor: Editor): Boolean {
54+
// Your logic to determine if the pass should be created
55+
// For example, check file type
56+
return true
57+
}
58+
}
59+
60+
class DiffHighLightingPass(project: Project, document: Document, val editor: Editor) :
61+
TextEditorHighlightingPass(project, document) {
62+
63+
private var lenses = emptyList<CodeLens>()
64+
private val file = FileDocumentManager.getInstance().getFile(myDocument)
65+
private val myHighlights = mutableListOf<HighlightInfo>()
66+
67+
override fun doCollectInformation(progress: ProgressIndicator) {
68+
colorsScheme = EditorColorsManager.getInstance().globalScheme
69+
val lineAttribuitesMap = mapOf<String, TextAttributes>(
70+
"header" to TextAttributes(
71+
null,
72+
colorsScheme?.getColor(EditorColors.DOCUMENTATION_COLOR),
73+
null,
74+
null,
75+
0
76+
),
77+
"footer" to TextAttributes(
78+
null,
79+
colorsScheme?.getColor(EditorColors.DOCUMENTATION_COLOR),
80+
null,
81+
null,
82+
0
83+
),
84+
"commentsFirstLine" to TextAttributes(
85+
null,
86+
colorsScheme?.getColor(EditorColors.DOCUMENTATION_COLOR),
87+
null,
88+
null,
89+
Font.ITALIC
90+
),
91+
"comments" to TextAttributes(
92+
null,
93+
colorsScheme?.getColor(EditorColors.READONLY_BACKGROUND_COLOR),
94+
null,
95+
null,
96+
Font.ITALIC
97+
),
98+
"waiting" to TextAttributes(
99+
null,
100+
colorsScheme?.getColor(EditorColors.TAB_UNDERLINE_INACTIVE),
101+
null,
102+
null,
103+
0
104+
),
105+
"inProgress" to TextAttributes(null, colorsScheme?.getColor(EditorColors.ADDED_LINES_COLOR), null, null, 0),
106+
"unchanged" to TextAttributes(null, null, null, null, 0),
107+
"inserted" to TextAttributes(null, colorsScheme?.getColor(EditorColors.ADDED_LINES_COLOR), null, null, 0),
108+
"deleted" to TextAttributes(null, colorsScheme?.getColor(EditorColors.DELETED_LINES_COLOR), null, null, 0),
109+
)
110+
val uri = file?.url ?: return
111+
lenses = getCodeLenses(myProject, uri).get() ?: emptyList()
112+
for (lens in lenses) {
113+
if ((lens.data as JsonObject?)?.get("type")?.asString != "previewChanges") continue
114+
val range = lens.range
115+
val startOffset = myDocument.getLineStartOffset(range.start.line)
116+
val endOffset = myDocument.getLineEndOffset(range.end.line)
117+
val textRange = TextRange(startOffset, endOffset)
118+
119+
// Create the inlay presentation for the CodeLens
120+
val lineType = (lens.data as JsonObject?)?.get("line")?.asString ?: continue
121+
// val textType = (lens.data as JsonObject?)?.get("text")?.asString
122+
val attributes = lineAttribuitesMap.get(lineType) ?: continue
123+
val builder = HighlightInfo.newHighlightInfo(HighlightInfoType.INFORMATION)
124+
.range(textRange)
125+
.textAttributes(attributes)
126+
.descriptionAndTooltip("diff line")
127+
.severity(HighlightSeverity.TEXT_ATTRIBUTES)
128+
val highlight = builder.create() ?: continue
129+
myHighlights.add(highlight)
130+
131+
invokeLater {
132+
if (editor.isDisposed) return@invokeLater
133+
editor.markupModel.addLineHighlighter(range.start.line, range.end.line, attributes)
134+
}
135+
}
136+
}
137+
138+
private fun addLineHighlighter(lenses: List<CodeLens>) {
139+
}
140+
141+
private val textAttributesMap = mapOf<String, TextAttributes>(
142+
"inserted" to TextAttributes(null, colorsScheme?.getColor(EditorColors.ADDED_LINES_COLOR), null, null, 0),
143+
"deleted" to TextAttributes(null, colorsScheme?.getColor(EditorColors.DELETED_LINES_COLOR), null, null, 0),
144+
)
145+
146+
override fun doApplyInformationToEditor() {
147+
// Apply highlighting information to the editor
148+
UpdateHighlightersUtil.setHighlightersToEditor(
149+
myProject, myDocument, 0, myDocument.textLength,
150+
myHighlights, colorsScheme, id
151+
)
152+
}
153+
154+
private val scope = CoroutineScope(Dispatchers.IO)
155+
156+
private fun getCodeLenses(project: Project, uri: String): CompletableFuture<List<CodeLens>?> {
157+
val params = CodeLensParams(TextDocumentIdentifier(uri))
158+
return CompletableFuture<List<CodeLens>?>().also { future ->
159+
scope.launch {
160+
try {
161+
val server = project.serviceOrNull<ConnectionService>()?.getServerAsync() ?: run {
162+
future.complete(null)
163+
return@launch
164+
}
165+
val result = server.textDocumentFeature.codeLens(params)
166+
future.complete(result.get())
167+
} catch (e: Exception) {
168+
future.completeExceptionally(e)
169+
}
170+
}
171+
}
172+
}
173+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.tabbyml.intellijtabby.inlineChat
2+
3+
import com.intellij.lang.annotation.AnnotationHolder
4+
import com.intellij.lang.annotation.ExternalAnnotator
5+
import com.intellij.openapi.components.serviceOrNull
6+
import com.intellij.openapi.editor.colors.TextAttributesKey
7+
import com.intellij.openapi.editor.markup.TextAttributes
8+
import com.intellij.openapi.fileEditor.FileDocumentManager
9+
import com.intellij.openapi.project.Project
10+
import com.intellij.openapi.util.TextRange
11+
import com.intellij.psi.PsiFile
12+
import com.tabbyml.intellijtabby.lsp.ConnectionService
13+
import kotlinx.coroutines.CoroutineScope
14+
import kotlinx.coroutines.Dispatchers
15+
import kotlinx.coroutines.launch
16+
import org.eclipse.lsp4j.CodeLens
17+
import org.eclipse.lsp4j.CodeLensParams
18+
import org.eclipse.lsp4j.TextDocumentIdentifier
19+
import java.awt.Color
20+
import java.util.concurrent.CompletableFuture
21+
22+
23+
interface InitInfo {
24+
val project: Project;
25+
val uri: String;
26+
}
27+
28+
class DiffHighlighter: ExternalAnnotator<InitInfo, List<CodeLens>>() {
29+
private val scope = CoroutineScope(Dispatchers.IO)
30+
31+
private fun getCodeLenses(project: Project, uri: String): CompletableFuture<List<CodeLens>?> {
32+
val params = CodeLensParams(TextDocumentIdentifier(uri))
33+
return CompletableFuture<List<CodeLens>?>().also { future ->
34+
scope.launch {
35+
try {
36+
val server = project.serviceOrNull<ConnectionService>()?.getServerAsync() ?: run {
37+
future.complete(null)
38+
return@launch
39+
}
40+
val result = server.textDocumentFeature.codeLens(params)
41+
future.complete(result.get())
42+
} catch (e: Exception) {
43+
future.completeExceptionally(e)
44+
}
45+
}
46+
}
47+
}
48+
49+
override fun collectInformation(file: PsiFile): InitInfo? {
50+
51+
val project = file.project
52+
val uri = file.virtualFile?.url ?: return null
53+
return object : InitInfo {
54+
override val project: Project = project
55+
override val uri: String = uri
56+
}
57+
}
58+
59+
override fun doAnnotate(collectedInfo: InitInfo?): List<CodeLens>? {
60+
val project = collectedInfo?.project ?: return null
61+
val uri = collectedInfo.uri
62+
return getCodeLenses(project, uri).get()
63+
}
64+
65+
override fun apply(file: PsiFile, annotationResult: List<CodeLens>?, holder: AnnotationHolder) {
66+
if (annotationResult == null) return
67+
val document = FileDocumentManager.getInstance().getDocument(file.virtualFile!!) ?: return
68+
for (codeLens in annotationResult) {
69+
val range = codeLens.range
70+
val startOffset = document.getLineStartOffset(range.start.line) + range.start.character
71+
val endOffset = document.getLineStartOffset(range.end.line) + range.end.character
72+
val textRange = TextRange(startOffset, endOffset)
73+
// Add to the editor
74+
val attributes = TextAttributes()
75+
attributes.backgroundColor = Color(200, 255, 200)
76+
holder.newAnnotation(
77+
com.intellij.lang.annotation.HighlightSeverity.INFORMATION,
78+
codeLens.command.title
79+
)
80+
.range(textRange)
81+
.textAttributes(TextAttributesKey.createTempTextAttributesKey("DiffHighlighter", attributes))
82+
.create()
83+
}
84+
}
85+
86+
}

clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/LanguageClient.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class LanguageClient(private val project: Project) : com.tabbyml.intellijtabby.l
6262
inlineCompletion = InlineCompletionCapabilities(
6363
dynamicRegistration = true,
6464
),
65-
codeLensCapabilities = CodeLensCapabilities(
65+
codeLens = CodeLensCapabilities(
6666
true,
6767
),
6868
),

clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/protocol/ProtocolData.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ data class TextDocumentClientCapabilities(
4848
val synchronization: SynchronizationCapabilities? = null,
4949
val completion: CompletionCapabilities? = null,
5050
val inlineCompletion: InlineCompletionCapabilities? = null,
51-
var codeLensCapabilities: CodeLensCapabilities ? = null,
51+
var codeLens: CodeLensCapabilities ? = null,
5252
)
5353

5454
data class InlineCompletionCapabilities(

clients/intellij/src/main/kotlin/com/tabbyml/intellijtabby/lsp/protocol/server/TextDocumentFeature.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ interface TextDocumentFeature {
3232
@JsonNotification
3333
fun willSave(params: WillSaveTextDocumentParams)
3434

35-
@JsonNotification
35+
@JsonRequest
3636
fun codeLens(params: CodeLensParams): CompletableFuture<List<CodeLens>?>
3737
}

clients/intellij/src/main/resources/META-INF/plugin.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
</intentionAction>
6969

7070
<codeInsight.lineMarkerProvider implementationClass="com.tabbyml.intellijtabby.inlineChat.GutterIconProvider"/>
71-
<codeInsight.inlayProvider language=" " implementationClass="com.tabbyml.intellijtabby.inlineChat.CodeLensInlayHintsProvider" />
71+
<codeInsight.codeVisionProvider implementation="com.tabbyml.intellijtabby.inlineChat.InlineChatCodeVisionProvider" />
72+
<highlightingPassFactory implementation="com.tabbyml.intellijtabby.inlineChat.DiffHighlighterRegister" />
7273
</extensions>
7374

7475
<actions>

0 commit comments

Comments
 (0)