Skip to content

Commit

Permalink
Generated Protocol Bindings (#1931)
Browse files Browse the repository at this point in the history
Adds generated protocol bindings boilerplate

## Test plan

- Bindings are as of know unused. No additional testing required
- Verified that build still works
  • Loading branch information
RXminuS authored Jul 25, 2024
1 parent 5659de4 commit 39b176f
Show file tree
Hide file tree
Showing 202 changed files with 4,519 additions and 1 deletion.
56 changes: 56 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import java.util.EnumSet
import java.util.jar.JarFile
import java.util.zip.ZipFile
import org.jetbrains.changelog.markdownToHTML
import org.jetbrains.intellij.or
import org.jetbrains.intellij.tasks.RunPluginVerifierTask.FailureLevel
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.incremental.deleteDirectoryContents

fun properties(key: String) = project.findProperty(key).toString()

Expand All @@ -22,6 +24,7 @@ val isForceAgentBuild =
isForceBuild ||
properties("forceCodyBuild") == "true" ||
properties("forceAgentBuild") == "true"
val isForceProtocolCopy = properties("forceProtocolCopy") == "true"
val isForceCodeSearchBuild = isForceBuild || properties("forceCodeSearchBuild") == "true"

// As https://www.jetbrains.com/updates/updates.xml adds a new "IntelliJ IDEA" YYYY.N version, add
Expand Down Expand Up @@ -110,6 +113,7 @@ spotless {
ktfmt()
trimTrailingWhitespace()
target("src/**/*.kt")
targetExclude("src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/**/*.kt")
toggleOffOn()
}
}
Expand Down Expand Up @@ -341,6 +345,57 @@ tasks {
return buildCodyDir
}

fun copyProtocol(): Unit {
// This is just a temporary solution to make it easier to use certain generated protocol
// messages.
// Ultimately this should entirely be replaced by use of the generated protocol.
if (!isForceProtocolCopy) {
return
}
val codyDir = downloadCody()
val sourceDir =
codyDir.resolve(
Paths.get(
"agent",
"bindings",
"kotlin",
"lib",
"src",
"main",
"kotlin",
"com",
"sourcegraph",
"cody",
"agent",
"protocol_generated")
.toString())
val targetDir =
layout.projectDirectory.asFile.resolve(
"src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated")

targetDir.deleteDirectoryContents()
sourceDir.copyRecursively(targetDir, overwrite = true)
// in each file replace the package name
for (file in targetDir.walkTopDown()) {
if (file.isFile && file.extension == "kt") {
val content = file.readText()
// This is only a temporary solution to inject the notice.
// I've kept here so that it's clear where the files are modified.
val newContent =
"""
|/*
| * Generated file - DO NOT EDIT MANUALLY
| * They are copied from the cody agent project using the copyProtocol gradle task.
| * This is only a temporary solution before we fully migrate to generated protocol messages.
| */
|
"""
.trimMargin() + content
file.writeText(newContent)
}
}
}

// System properties that are used for testing purposes. These properties
// should be consistently set in different local dev environments, like `./gradlew :runIde`,
// `./gradlew test` or when testing inside IntelliJ
Expand All @@ -363,6 +418,7 @@ tasks {
return ideaDir.walk().find { it.name == "ideaIC-$ideaVersion" }
}

register("copyProtocol") { copyProtocol() }
register("buildCodeSearch") { buildCodeSearch() }
register("buildCody") { buildCody() }

Expand Down
14 changes: 14 additions & 0 deletions scripts/copy-protocol.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -eu

echo "====================================================="
echo "= Copying protocol files from CODY_DIR ="
echo "====================================================="

# if CODY_DIR is not set then we assume it's relative to the gitroot (look that up)
# and then ../cody/ (which will need to be converted into an absolute path)
if [ -z "${CODY_DIR:-}" ]; then
CODY_DIR="$(git rev-parse --show-toplevel)/../cody/"
echo "CODY_DIR is not set so using ${CODY_DIR}"
fi
CODY_DIR="$CODY_DIR" ./gradlew copyProtocol -PforceProtocolCopy=true
21 changes: 20 additions & 1 deletion src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentServer.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("FunctionName")

package com.sourcegraph.cody.agent

import com.sourcegraph.cody.agent.protocol.AttributionSearchParams
Expand Down Expand Up @@ -37,13 +39,30 @@ import java.util.concurrent.CompletableFuture
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest

interface CodyAgentServer : _LegacyAgentServer, _SubsetGeneratedCodyAgentServer

// This is subset of the generated protocol bindings.
// This is only temporary until all legacy bindings are made redundant.
// Make sure to copy from the generated bindings verbatim!
interface _SubsetGeneratedCodyAgentServer {
// ========
// Requests
// ========

// =============
// Notifications
// =============
}

// TODO: Requests waiting to be migrated & tested for compatibility. Avoid placing new protocol
// messages here.
/**
* Interface for the server-part of the Cody agent protocol. The implementation of this interface is
* written in TypeScript in the file "cody/agent/src/agent.ts". The Eclipse LSP4J bindings create a
* Java implementation of this interface by using a JVM-reflection feature called "Proxy", which
* works similar to JavaScript Proxy.
*/
interface CodyAgentServer {
interface _LegacyAgentServer {
// Requests
@JsonRequest("initialize") fun initialize(clientInfo: ClientInfo): CompletableFuture<ServerInfo>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sourcegraph.cody.agent

import com.google.gson.*
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import java.io.IOException
import java.lang.reflect.Field

class EnumTypeAdapterFactory : TypeAdapterFactory {
override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
val rawType = type.rawType as? Class<*> ?: return null
if (!rawType.isEnum) {
return null
}
@Suppress("UNCHECKED_CAST")
return EnumTypeAdapter(rawType as Class<out Enum<*>>) as TypeAdapter<T>
}
}

class EnumTypeAdapter<T : Enum<T>>(private val classOfT: Class<T>) : TypeAdapter<T>() {
private val nameToConstant: Map<String, T> = HashMap()
private val constantToName: Map<T, String> = HashMap()

init {
for (constant in classOfT.enumConstants) {
val name = getSerializedName(constant) ?: constant.name
(nameToConstant as HashMap)[name.lowercase()] = constant
(constantToName as HashMap)[constant] = name
}
}

private fun getSerializedName(enumConstant: T): String? {
return try {
val field: Field = classOfT.getField(enumConstant.name)
field.getAnnotation(SerializedName::class.java)?.value
} catch (e: NoSuchFieldException) {
null
}
}

override fun write(out: JsonWriter, value: T?) {
if (value == null) {
out.nullValue()
} else {
out.value(constantToName[value])
}
}

@Throws(IOException::class)
override fun read(`in`: JsonReader): T? {
val value = `in`.nextString()
return nameToConstant[value.lowercase()]
?: throw JsonParseException("Unknown enum value: $value")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* @deprecated Replaced by {@link package com.sourcegraph.cody.agent.generated_protocol} and {@link package com.sourcegraph.cody.agent.protocol_extensions}
*/
@Deprecated
package com.sourcegraph.cody.agent.protocol;
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.sourcegraph.cody.agent.protocol_extensions

import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities
import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities.ChatEnum
import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities.CodeLensesEnum
import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities.CompletionsEnum
import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities.EditEnum
import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities.EditWorkspaceEnum
import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities.GitEnum
import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities.IgnoreEnum
import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities.ProgressBarsEnum
import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities.ShowDocumentEnum
import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities.UntitledDocumentsEnum

object ClientCapabilitiesFactory {
fun build(
completions: String? = null,
chat: String? = null,
git: String? = null,
progressBars: String? = null,
edit: String? = null,
editWorkspace: String? = null,
codeLenses: String? = null,
showDocument: String? = null,
ignore: String? = null,
untitledDocuments: String? = null
): ClientCapabilities {
return ClientCapabilities(
completions = completions?.toEnumIgnoreCase<CompletionsEnum>(),
chat = chat?.toEnumIgnoreCase<ChatEnum>(),
git = git?.toEnumIgnoreCase<GitEnum>(),
progressBars = progressBars?.toEnumIgnoreCase<ProgressBarsEnum>(),
edit = edit?.toEnumIgnoreCase<EditEnum>(),
editWorkspace = editWorkspace?.toEnumIgnoreCase<EditWorkspaceEnum>(),
codeLenses = codeLenses?.toEnumIgnoreCase<CodeLensesEnum>(),
showDocument = showDocument?.toEnumIgnoreCase<ShowDocumentEnum>(),
ignore = ignore?.toEnumIgnoreCase<IgnoreEnum>(),
untitledDocuments = untitledDocuments?.toEnumIgnoreCase<UntitledDocumentsEnum>())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.sourcegraph.cody.agent.protocol_extensions

import com.sourcegraph.cody.agent.protocol_generated.ClientCapabilities
import com.sourcegraph.cody.agent.protocol_generated.ClientInfo
import com.sourcegraph.cody.agent.protocol_generated.ExtensionConfiguration

object ClientInfoFactory {
fun build(
version: String,
ideVersion: String,
workspaceRootUri: String,
extensionConfiguration: ExtensionConfiguration?,
capabilities: ClientCapabilities?
): ClientInfo {
return ClientInfo(
name = "JetBrains",
version = version,
ideVersion = ideVersion,
workspaceRootUri = workspaceRootUri,
extensionConfiguration = extensionConfiguration,
capabilities = capabilities,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.sourcegraph.cody.agent.protocol_extensions

// because line / column should probably be a
// long
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.LogicalPosition
import com.sourcegraph.cody.agent.protocol_generated.Position
import kotlin.math.max
import kotlin.math.min

fun Position(line: Int, character: Int): Position {
return Position(line.toLong(), character.toLong())
}

object PositionFactory {
fun fromOffset(document: Document, offset: Int): Position {
val line = document.getLineNumber(offset)
val lineStartOffset = document.getLineStartOffset(line)
return Position(line.toLong(), (offset - lineStartOffset).toLong())
}
}

fun Position.isStartOrEndOfDocumentMarker(document: Document): Boolean {
return line < 0 || line > document.lineCount
}

fun Position.getRealLine(document: Document): Int {
return min(max(0, document.lineCount - 1), line.toInt())
}

fun Position.getRealColumn(document: Document): Int {
val realLine = getRealLine(document)
val lineLength = document.getLineEndOffset(realLine) - document.getLineStartOffset(realLine)
return min(lineLength, character.toInt())
}

fun Position.toLogicalPosition(document: Document): LogicalPosition {
return LogicalPosition(getRealLine(document), getRealColumn(document))
}

/** Return zero-based offset of this position in the document. */
fun Position.toOffset(document: Document): Int {
val lineStartOffset = document.getLineStartOffset(getRealLine(document))
return lineStartOffset + getRealColumn(document)
}
Loading

0 comments on commit 39b176f

Please sign in to comment.