Skip to content

Commit 80ba956

Browse files
committed
feat: add Diagnostics tag
1 parent 0d3bfc7 commit 80ba956

File tree

6 files changed

+172
-1
lines changed

6 files changed

+172
-1
lines changed

src/main/java/ee/carlrobert/codegpt/toolwindow/chat/structure/data/PsiStructureRepository.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ class PsiStructureRepository(
226226
is WebTagDetails -> null
227227
is ImageTagDetails -> null
228228
is CodeAnalyzeTagDetails -> null
229+
is DiagnosticsTagDetails -> null
229230
}
230231

231232
virtualFile?.takeIf { it.isValid && it.exists()}
@@ -253,6 +254,7 @@ class PsiStructureRepository(
253254
is WebTagDetails -> false
254255
is ImageTagDetails -> false
255256
is CodeAnalyzeTagDetails -> false
257+
is DiagnosticsTagDetails -> false
256258
}
257259
}
258260
.toSet()
@@ -280,6 +282,7 @@ class PsiStructureRepository(
280282
is WebTagDetails -> null
281283
is ImageTagDetails -> null
282284
is CodeAnalyzeTagDetails -> null
285+
is DiagnosticsTagDetails -> null
283286
}
284287

285288
virtualFile?.takeIf { it.isValid && it.exists()}

src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/PromptTextFieldConstants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ object PromptTextFieldConstants {
1717
"history", "hist", "h",
1818
"personas", "persona", "p",
1919
"docs", "doc", "d",
20+
"diagnostics", "diagnostic", "diag",
2021
"mcp", "m",
2122
"web", "w",
2223
"image", "img", "i"

src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SearchManager.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import ee.carlrobert.codegpt.ui.textarea.lookup.LookupGroupItem
1010
import ee.carlrobert.codegpt.ui.textarea.lookup.action.CodeAnalyzeActionItem
1111
import ee.carlrobert.codegpt.ui.textarea.lookup.action.WebActionItem
1212
import ee.carlrobert.codegpt.ui.textarea.lookup.action.ImageActionItem
13+
import ee.carlrobert.codegpt.ui.textarea.lookup.action.DiagnosticsActionItem
1314
import ee.carlrobert.codegpt.ui.textarea.lookup.group.*
1415
import kotlinx.coroutines.CancellationException
1516

@@ -35,6 +36,7 @@ class SearchManager(
3536
PersonasGroupItem(tagManager),
3637
DocsGroupItem(tagManager),
3738
CodeAnalyzeActionItem(tagManager),
39+
DiagnosticsActionItem(tagManager),
3840
MCPGroupItem(),
3941
WebActionItem(tagManager),
4042
ImageActionItem(project, tagManager)

src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/TagProcessorFactory.kt

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package ee.carlrobert.codegpt.ui.textarea
22

3+
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl
4+
import com.intellij.codeInsight.daemon.impl.HighlightInfo
5+
import com.intellij.lang.annotation.HighlightSeverity
36
import com.intellij.openapi.components.service
7+
import com.intellij.openapi.fileEditor.FileDocumentManager
48
import com.intellij.openapi.progress.ProgressManager
9+
import com.intellij.openapi.project.DumbService
510
import com.intellij.openapi.project.Project
11+
import com.intellij.openapi.util.text.StringUtil
612
import com.intellij.openapi.vfs.VirtualFile
13+
import com.intellij.psi.PsiDocumentManager
14+
import com.intellij.psi.PsiManager
715
import ee.carlrobert.codegpt.EncodingManager
816
import ee.carlrobert.codegpt.completions.CompletionRequestUtil
917
import ee.carlrobert.codegpt.conversations.Conversation
@@ -33,6 +41,7 @@ object TagProcessorFactory {
3341
is ImageTagDetails -> ImageTagProcessor(tagDetails)
3442
is EmptyTagDetails -> TagProcessor { _, _ -> }
3543
is CodeAnalyzeTagDetails -> TagProcessor { _, _ -> }
44+
is DiagnosticsTagDetails -> DiagnosticsTagProcessor(project, tagDetails)
3645
}
3746
}
3847
}
@@ -248,4 +257,116 @@ class ConversationTagProcessor(
248257
}
249258
message.conversationsHistoryIds?.add(tagDetails.conversationId)
250259
}
260+
}
261+
262+
class DiagnosticsTagProcessor(
263+
private val project: Project,
264+
private val tagDetails: DiagnosticsTagDetails,
265+
) : TagProcessor {
266+
override fun process(message: Message, promptBuilder: StringBuilder) {
267+
promptBuilder
268+
.append("\n## Current File Problems\n")
269+
.append(getDiagnosticsString(project, tagDetails.virtualFile))
270+
.append("\n")
271+
}
272+
273+
private fun getDiagnosticsString(project: Project, virtualFile: VirtualFile): String {
274+
return try {
275+
DumbService.getInstance(project).runReadActionInSmartMode<String> {
276+
val document = FileDocumentManager.getInstance().getDocument(virtualFile)
277+
?: return@runReadActionInSmartMode "No document found for file"
278+
279+
PsiDocumentManager.getInstance(project).commitDocument(document)
280+
281+
val psiManager = PsiManager.getInstance(project)
282+
val psiFile = psiManager.findFile(virtualFile)
283+
?: return@runReadActionInSmartMode "No PSI file found for: ${virtualFile.path}"
284+
285+
val rangeHighlights =
286+
DaemonCodeAnalyzerImpl.getHighlights(
287+
document,
288+
HighlightSeverity.WEAK_WARNING,
289+
project
290+
)
291+
// TODO: Find a better solution
292+
val fileLevel: List<HighlightInfo> = try {
293+
val method = DaemonCodeAnalyzerImpl::class.java.methods.firstOrNull {
294+
it.name == "getFileLevelHighlights" && it.parameterCount == 2
295+
}
296+
if (method != null) {
297+
@Suppress("UNCHECKED_CAST")
298+
method.invoke(null, project, psiFile) as? List<HighlightInfo> ?: emptyList()
299+
} else {
300+
emptyList()
301+
}
302+
} catch (_: Throwable) {
303+
emptyList()
304+
}
305+
306+
val highlights = (rangeHighlights.asSequence() + fileLevel.asSequence())
307+
.distinctBy { Triple(it.description, it.startOffset, it.severity) }
308+
.sortedWith(
309+
compareBy<HighlightInfo>(
310+
{ severityOrder(it.severity) },
311+
{ it.startOffset.coerceAtLeast(0) }
312+
)
313+
)
314+
.toList()
315+
316+
if (highlights.isEmpty()) {
317+
return@runReadActionInSmartMode ""
318+
}
319+
320+
val maxItems = 200
321+
val overflow = (highlights.size - maxItems).coerceAtLeast(0)
322+
val shown = highlights.take(maxItems)
323+
324+
buildString {
325+
append("File: ${virtualFile.name}\n")
326+
append("Path: ${virtualFile.path}\n\n")
327+
328+
shown.forEach { info ->
329+
val startOffset = info.startOffset.coerceIn(0, document.textLength)
330+
val lineColText =
331+
if (info.startOffset >= 0 && document.textLength > 0) {
332+
val line = document.getLineNumber(startOffset) + 1
333+
val col = startOffset - document.getLineStartOffset(line - 1) + 1
334+
"line $line, col $col"
335+
} else {
336+
"file-level"
337+
}
338+
339+
val rawMessage = info.description ?: info.toolTip ?: ""
340+
val message = StringUtil.removeHtmlTags(rawMessage, false).trim()
341+
342+
val severityLabel = when (info.severity) {
343+
HighlightSeverity.ERROR -> "ERROR"
344+
HighlightSeverity.WARNING -> "WARNING"
345+
HighlightSeverity.WEAK_WARNING -> "WEAK_WARNING"
346+
HighlightSeverity.INFORMATION -> "INFO"
347+
else -> info.severity.toString()
348+
}
349+
350+
append("- [$severityLabel] $lineColText: $message\n")
351+
}
352+
353+
if (overflow > 0) {
354+
append("... ($overflow more not shown)\n")
355+
}
356+
}
357+
}
358+
} catch (e: Exception) {
359+
"Error retrieving diagnostics: ${e.message}"
360+
}
361+
}
362+
363+
private fun severityOrder(severity: HighlightSeverity): Int {
364+
return when (severity) {
365+
HighlightSeverity.ERROR -> 0
366+
HighlightSeverity.WARNING -> 1
367+
HighlightSeverity.WEAK_WARNING -> 2
368+
HighlightSeverity.INFORMATION -> 3
369+
else -> 4
370+
}
371+
}
251372
}

src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagDetails.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,7 @@ data class HistoryTagDetails(
131131

132132
class EmptyTagDetails : TagDetails("")
133133

134-
class CodeAnalyzeTagDetails : TagDetails("Code Analyze", AllIcons.Actions.DependencyAnalyzer)
134+
class CodeAnalyzeTagDetails : TagDetails("Code Analyze", AllIcons.Actions.DependencyAnalyzer)
135+
136+
data class DiagnosticsTagDetails(val virtualFile: VirtualFile) :
137+
TagDetails("${virtualFile.name} Problems", AllIcons.General.InspectionsEye)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package ee.carlrobert.codegpt.ui.textarea.lookup.action
2+
3+
import com.intellij.icons.AllIcons
4+
import com.intellij.openapi.project.Project
5+
import com.intellij.openapi.vfs.VirtualFile
6+
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel
7+
import ee.carlrobert.codegpt.ui.textarea.header.tag.DiagnosticsTagDetails
8+
import ee.carlrobert.codegpt.ui.textarea.header.tag.EditorTagDetails
9+
import ee.carlrobert.codegpt.ui.textarea.header.tag.FileTagDetails
10+
import ee.carlrobert.codegpt.ui.textarea.header.tag.TagManager
11+
import ee.carlrobert.codegpt.util.EditorUtil
12+
13+
class DiagnosticsActionItem(
14+
private val tagManager: TagManager
15+
) : AbstractLookupActionItem() {
16+
17+
override val displayName: String = "Diagnostics"
18+
override val icon = AllIcons.General.InspectionsEye
19+
override val enabled: Boolean
20+
get() = tagManager.getTags().none { it is DiagnosticsTagDetails } &&
21+
tagManager.getTags().any { it is FileTagDetails || it is EditorTagDetails }
22+
23+
override fun execute(project: Project, userInputPanel: UserInputPanel) {
24+
val virtualFile = findVirtualFile(project)
25+
virtualFile?.let { file ->
26+
userInputPanel.addTag(DiagnosticsTagDetails(file))
27+
}
28+
}
29+
30+
private fun findVirtualFile(project: Project): VirtualFile? {
31+
val existingFile = tagManager.getTags()
32+
.firstNotNullOfOrNull { tag ->
33+
when (tag) {
34+
is FileTagDetails -> tag.virtualFile
35+
is EditorTagDetails -> tag.virtualFile
36+
else -> null
37+
}
38+
}
39+
return existingFile ?: EditorUtil.getSelectedEditor(project)?.virtualFile
40+
}
41+
}

0 commit comments

Comments
 (0)