Skip to content

Commit

Permalink
Warn when @-file selected for context is too big (#1062)
Browse files Browse the repository at this point in the history
## Changes

* Warning for too big @-files was added
* One TODO removed - is it now possible to switch between addition of
few @-files just by moving cursor between them
* Minor code cleanup


![image](https://github.com/sourcegraph/jetbrains/assets/1519649/d15c90d0-e322-4161-b021-c82fce1636fa)


## Test plan

Manual QA tests only
  • Loading branch information
pkukielka authored Mar 13, 2024
1 parent fc71c43 commit fe4c100
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 90 deletions.
142 changes: 60 additions & 82 deletions src/main/java/com/sourcegraph/cody/PromptPanel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,28 @@ import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.components.JBList
import com.intellij.ui.components.JBScrollPane
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import com.sourcegraph.cody.agent.WebviewMessage
import com.sourcegraph.cody.agent.protocol.ContextItem
import com.sourcegraph.cody.agent.protocol.ContextItemFile
import com.sourcegraph.cody.chat.ChatSession
import com.sourcegraph.cody.chat.CodyChatMessageHistory
import com.sourcegraph.cody.chat.ui.SendButton
import com.sourcegraph.cody.ui.AutoGrowingTextArea
import com.sourcegraph.cody.vscode.CancellationToken
import com.sourcegraph.common.CodyBundle
import java.awt.Dimension
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.io.File
import java.awt.event.*
import javax.swing.DefaultListModel
import javax.swing.JLayeredPane
import javax.swing.KeyStroke
import javax.swing.border.EmptyBorder
import javax.swing.event.AncestorEvent
import javax.swing.event.AncestorListener
import javax.swing.event.DocumentEvent

class PromptPanel(project: Project, private val chatSession: ChatSession) : JLayeredPane() {

Expand Down Expand Up @@ -87,26 +81,16 @@ class PromptPanel(project: Project, private val chatSession: ChatSession) : JLay
addComponentListener(
object : ComponentAdapter() {
override fun componentResized(e: ComponentEvent?) {
// HACK
val jButtonPreferredSize = sendButton.preferredSize
sendButton.setBounds(
scrollPane.width - jButtonPreferredSize.width,
scrollPane.height - jButtonPreferredSize.height,
jButtonPreferredSize.width,
jButtonPreferredSize.height)
refreshViewLayout()
revalidate()
}
})

// Add user action listeners
sendButton.addActionListener { _ -> didSubmitChatMessage() }
textArea.document.addDocumentListener(
object : DocumentAdapter() {
override fun textChanged(e: DocumentEvent) {
refreshSendButton()
didUserInputChange(textArea.text)
}
})
textArea.addCaretListener {
refreshSendButton()
didUserInputChange()
}
contextFilesListView.addMouseListener(
object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
Expand All @@ -123,6 +107,8 @@ class PromptPanel(project: Project, private val chatSession: ChatSession) : JLay
}
.registerCustomShortcutSet(shortcut, textArea)
}

revalidate()
}

fun focus() = textArea.requestFocusInWindow()
Expand Down Expand Up @@ -162,26 +148,26 @@ class PromptPanel(project: Project, private val chatSession: ChatSession) : JLay

val selected = contextFilesListView.model.getElementAt(contextFilesListView.selectedIndex)
this.selectedContextItems.add(selected.contextItem)
val cfDisplayPath = selected.toString()
val expr = findAtExpressions(textArea.text).lastOrNull() ?: return
val cfDisplayPath = selected.contextItem.displayPath()
val expr =
findAtExpressions(textArea.text).find { it.endIndex == textArea.caretPosition } ?: return

textArea.replaceRange("@${cfDisplayPath} ", expr.startIndex, expr.endIndex)

setContextFilesSelector(listOf())
refreshViewLayout()
revalidate()
}

private fun didUserInputChange(text: String) {
val exp = findAtExpressions(text).lastOrNull()
if (exp == null ||
exp.endIndex <
text.length) { // TODO(beyang): instead of text.length, should be current cursor index
private fun didUserInputChange() {
val exp = findAtExpressions(textArea.text).find { it.endIndex == textArea.caretPosition }
if (exp == null) {
setContextFilesSelector(listOf())
refreshViewLayout()
revalidate()
return
}
val expTrimmed = exp.value.removePrefix("...")
this.chatSession.sendWebviewMessage(
WebviewMessage(command = "getUserContext", submitType = "user", query = exp.value))
WebviewMessage(command = "getUserContext", submitType = "user", query = expTrimmed))
}

/** State updaters */
Expand All @@ -192,12 +178,12 @@ class PromptPanel(project: Project, private val chatSession: ChatSession) : JLay
newSelectedIndex += contextFilesListView.model.size
}
contextFilesListView.selectedIndex = newSelectedIndex
refreshViewLayout()
revalidate()
}

/** View updaters */
@RequiresEdt
private fun refreshViewLayout() {
override fun revalidate() {
super.revalidate()

// get the height of the context files list based on font height and number of context files
val contextFilesContainerHeight =
if (contextFilesListViewModel.isEmpty) 0 else contextFilesListView.preferredSize.height + 2
Expand All @@ -208,14 +194,15 @@ class PromptPanel(project: Project, private val chatSession: ChatSession) : JLay
contextFilesContainer.isVisible = true
}

scrollPane.setBounds(0, contextFilesContainerHeight, width, scrollPane.preferredSize.height)
preferredSize = Dimension(scrollPane.width, scrollPane.height + contextFilesContainerHeight)

sendButton.setLocation(
scrollPane.width - sendButton.preferredSize.width,
scrollPane.height + contextFilesContainerHeight - sendButton.preferredSize.height)
sendButton.setBounds(
preferredSize.width - sendButton.preferredSize.width,
preferredSize.height - sendButton.preferredSize.height,
sendButton.preferredSize.width,
sendButton.preferredSize.height)

revalidate()
scrollPane.setBounds(0, contextFilesContainerHeight, width, scrollPane.preferredSize.height)
}

@RequiresEdt
Expand Down Expand Up @@ -245,7 +232,7 @@ class PromptPanel(project: Project, private val chatSession: ChatSession) : JLay
} else {
contextFilesListView.selectedIndex = -1
}
refreshViewLayout()
revalidate()
}
}

Expand All @@ -264,12 +251,41 @@ class PromptPanel(project: Project, private val chatSession: ChatSession) : JLay
val UP = CustomShortcutSet(KEY_UP)
val DOWN = CustomShortcutSet(KEY_DOWN)
val TAB = CustomShortcutSet(KEY_TAB)

private val atExpressionPattern = """(@(?:\\\s|\S)*)(?:\s|$)""".toRegex()

fun findAtExpressions(text: String): List<AtExpression> {
val matches = atExpressionPattern.findAll(text)
val expressions = ArrayList<AtExpression>()
for (match in matches) {
val mainMatch = match.groups[0] ?: continue
val prevIndex = mainMatch.range.first - 1
// filter out things like email addresses
if (prevIndex >= 0 && !text[prevIndex].isWhitespace()) continue

val subMatch = match.groups[1]
if (subMatch != null) {
val value = subMatch.value.substring(1).replace("\\ ", " ")
expressions.add(
AtExpression(subMatch.range.first, subMatch.range.last + 1, subMatch.value, value))
}
}
return expressions
}

private fun findContextFiles(contextItems: List<ContextItem>, text: String): List<ContextItem> {
val atExpressions = findAtExpressions(text)
return contextItems.filter { f -> atExpressions.any { it.value == f.displayPath() } }
}
}
}

data class DisplayedContextFile(val contextItem: ContextItem) {
override fun toString(): String {
return displayPath(contextItem)
val contextItemFile = contextItem as? ContextItemFile
val isTooLarge = contextItemFile?.title == "large-file" || contextItemFile?.isTooLarge == true
val warnIfNeeded = if (isTooLarge) "<i> - ⚠ File too large</i>" else ""
return "<html>${contextItem.displayPath()}$warnIfNeeded</html>"
}
}

Expand All @@ -279,41 +295,3 @@ data class AtExpression(
val rawValue: String,
val value: String
)

val atExpressionPattern = """(@(?:\\\s|\S)*)(?:\s|$)""".toRegex()

fun findAtExpressions(text: String): List<AtExpression> {
val matches = atExpressionPattern.findAll(text)
val expressions = ArrayList<AtExpression>()
for (match in matches) {
val mainMatch = match.groups[0] ?: continue
val prevIndex = mainMatch.range.first - 1
// filter out things like email addresses
if (prevIndex >= 0 && !text[prevIndex].isWhitespace()) continue

val subMatch = match.groups[1]
if (subMatch != null) {
val value = subMatch.value.substring(1).replace("\\ ", " ")
expressions.add(
AtExpression(subMatch.range.first, subMatch.range.last + 1, subMatch.value, value))
}
}
return expressions
}

fun findContextFiles(contextItems: List<ContextItem>, text: String): List<ContextItem> {
val atExpressions = findAtExpressions(text)
return contextItems.filter { f -> atExpressions.any { it.value == displayPath(f) } }
}

// TODO(beyang): temporary displayPath implementation. This should be replaced by acquiring the
// display path from the agent
// Current behavior: if the path contains more than three components, display the last three.
fun displayPath(contextItem: ContextItem): String {
val path = contextItem.uri.path
val pathComponents = path.split("/") // uri path is posix-style
if (pathComponents.size > 3) {
return "...${File.separator}${pathComponents.subList(pathComponents.size - 3, pathComponents.size).joinToString(File.separator)}"
}
return path.replace("/", File.separator)
}
24 changes: 17 additions & 7 deletions src/main/kotlin/com/sourcegraph/cody/agent/protocol/ContextItem.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package com.sourcegraph.cody.agent.protocol

import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonSerializer
import com.google.gson.*
import java.io.File
import java.lang.reflect.Type
import java.net.URI
import java.nio.file.Path
Expand All @@ -32,6 +28,19 @@ sealed class ContextItem {
}
}
}

// TODO(beyang): temporary displayPath implementation. This should be replaced by acquiring the
// display path from the agent
// Current behavior: if the path contains more than three components, display the last three.
fun displayPath(): String {
val path = uri.path
val pathComponents = path.split("/") // uri path is posix-style
if (pathComponents.size > 3) {
return "...${File.separator}${pathComponents.subList(pathComponents.size - 3, pathComponents.size).joinToString(
File.separator)}"
}
return path.replace("/", File.separator)
}
}

data class ContextItemFile(
Expand All @@ -44,7 +53,8 @@ data class ContextItemFile(
val source: ContextFileSource? =
null, // Oneof: embeddings, user, keyword, editor, filename, search, unified, selection,
// terminal
val content: String? = null
val content: String? = null,
val isTooLarge: Boolean? = null
) : ContextItem() {

fun isLocal() = repoName == null
Expand Down
2 changes: 1 addition & 1 deletion src/test/kotlin/com/sourcegraph/cody/PromptPanelTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class PromptPanelTest : TestCase() {
)

for (case in cases) {
assertEquals(case.expected, findAtExpressions(case.text))
assertEquals(case.expected, PromptPanel.findAtExpressions(case.text))
}
}
}

0 comments on commit fe4c100

Please sign in to comment.