Skip to content
This repository has been archived by the owner on Oct 24, 2021. It is now read-only.

Commit

Permalink
Added login button
Browse files Browse the repository at this point in the history
  • Loading branch information
KodingDev committed Jul 19, 2021
1 parent c19aed5 commit 9dd3f0d
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 62 deletions.
60 changes: 60 additions & 0 deletions src/main/kotlin/dev/koding/copilot/auth/OAuthHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")

package dev.koding.copilot.auth

import com.google.gson.annotations.SerializedName
import dev.koding.copilot.config.settings
import dev.koding.copilot.httpClient
import dev.koding.copilot.util.Notifications
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.awt.Desktop
import java.net.URI
import javax.swing.JOptionPane

const val authenticateUrl = "https://vscode-auth.github.com/authorize/" +
"?callbackUri=vscode://vscode.github-authentication/did-authenticate" +
"&scope=read:user" +
"&responseType=code" +
"&authServer=https://github.com"

data class GitHubTokenResponse(
@SerializedName("access_token")
val accessToken: String?,
@SerializedName("token_type")
val tokenType: String?,
val scope: String?
)

data class CopilotTokenResponse(
val token: String,
@SerializedName("expires_at")
val expiry: Int
)

private suspend fun getAuthToken(url: String): CopilotTokenResponse {
val code = Url(url).parameters["code"] ?: error("Code not present")

val tokenResponse = httpClient.post<GitHubTokenResponse>("https://vscode-auth.github.com/token") {
parameter("code", code)
accept(ContentType.Application.Json)
}

return httpClient.get("https://api.github.com/copilot_internal/token") {
header("Authorization", "token ${tokenResponse.accessToken ?: error("Invalid GitHub token")}")
}
}

fun handleLogin(handler: (String) -> Unit = { Notifications.send("Login successful") }) {
Desktop.getDesktop().browse(URI.create(authenticateUrl))
val url = JOptionPane.showInputDialog("Enter the callback URL")

// TODO: Change from global scope
GlobalScope.launch {
val token = getAuthToken(url).token
settings.token = token
handler(token)
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package dev.koding.copilot.completion

import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.completion.CompletionContributor
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.completion.CompletionSorter
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.TextRange
import dev.koding.copilot.auth.handleLogin
import dev.koding.copilot.completion.api.CompletionRequest
import dev.koding.copilot.completion.api.CompletionResponse
import dev.koding.copilot.config.settings
import dev.koding.copilot.copilotIcon
import dev.koding.copilot.util.Notifications
import io.ktor.client.features.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
Expand All @@ -19,95 +23,79 @@ import kotlin.math.max

class CopilotCompletionContributor : CompletionContributor() {

private var notified = false

override fun fillCompletionVariants(parameters: CompletionParameters, result: CompletionResultSet) {
if (parameters.isAutoPopup) return

if (settings.token == null) {
if (notified) return
@Suppress("DialogTitleCapitalization")
Notification(
"Error Report",
"GitHub Copilot",
"You have not set a token for GitHub Copilot.",
NotificationType.ERROR
).notify(parameters.editor.project)
return run { notified = true }
}
if (settings.token?.isBlank() == true) return Notifications.send(
"You have not set a token for GitHub Copilot.",
type = NotificationType.ERROR,
once = true,
Notifications.NotificationAction("Login") { handleLogin() })

val (prefix, suffix) = parameters.prefixSuffix
val prompt = """
// Language: ${parameters.originalFile.language.displayName}
// Path: ${parameters.originalFile.name}
${parameters.prompt}
""".trimIndent()

val (prefix, suffix) = parameters.prefixSuffix
val matcher = result.prefixMatcher
val set = result
.withPrefixMatcher(CopilotPrefixMatcher(matcher.cloneWithPrefix(matcher.prefix)))
.withRelevanceSorter(CompletionSorter.defaultSorter(parameters, matcher).weigh(CopilotWeigher()))

// TODO: Fix freeze
var response: CompletionResponse? = null
var errored = false

val job = GlobalScope.launch {
try {
response = CompletionRequest(prompt).send(settings.token!!)
} catch (e: ClientRequestException) {
if (!notified) {
@Suppress("DialogTitleCapitalization")
Notification(
"Error Report",
"GitHub Copilot",
"Failed to fetch response. Is your copilot token valid?",
NotificationType.ERROR
).notify(parameters.editor.project)
notified = true
}

return@launch result.stopHere()
errored = true
return@launch Notifications.send(
"Failed to fetch response. Is your copilot token valid?",
type = NotificationType.ERROR,
once = true,
Notifications.NotificationAction("Login") { handleLogin() })
}
}

if (result.isStopped) return
while (response == null) {
if (errored) return
try {
ProgressManager.getInstance().progressIndicator.checkCanceled()
Thread.sleep(10)
} catch (e: ProcessCanceledException) {
job.cancel()
return result.stopHere()
return
}
}

val choices = response!!.choices.filter { it.text.isNotBlank() }
if (choices.isEmpty()) return

val originalMatcher = result.prefixMatcher
val set =
result.withPrefixMatcher(CopilotPrefixMatcher(originalMatcher.cloneWithPrefix(originalMatcher.prefix)))
.withRelevanceSorter(
CompletionSorter.defaultSorter(parameters, originalMatcher)
.weigh(CopilotWeigher())
)

set.restartCompletionOnAnyPrefixChange()
set.addAllElements(choices.map { choice ->
val completion = choice.text.removePrefix(prefix.trim()).removeSuffix(suffix.trim())
val insert = "$prefix${completion.trim()}\n"

PrioritizedLookupElement.withPriority(
LookupElementBuilder.create(choice, "")
.withInsertHandler { context, _ ->
val caret = context.editor.caretModel
val startOffset = caret.visualLineStart
val endOffset = caret.visualLineEnd

context.document.deleteString(startOffset, endOffset)
context.document.insertString(startOffset, insert)
caret.moveToOffset(startOffset + insert.length - 1)
}
.withPresentableText(prefix.split(".").last())
.withTailText(completion, true)
.withTypeText("GitHub Copilot")
.withIcon(copilotIcon)
.bold(), Double.MAX_VALUE
)
LookupElementBuilder.create(choice, "")
.withInsertHandler { context, _ ->
val caret = context.editor.caretModel
val startOffset = caret.visualLineStart
val endOffset = caret.visualLineEnd

context.document.deleteString(startOffset, endOffset)
context.document.insertString(startOffset, insert)
caret.moveToOffset(startOffset + insert.length - 1)
}
.withPresentableText(prefix.split(".").last().trim())
.withTailText(completion, true)
.withCaseSensitivity(false)
.withTypeText("GitHub Copilot")
.withIcon(copilotIcon)
.bold()
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="dev.koding.copilot.config.ApplicationConfigurable">
<grid id="27dc6" binding="panel" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="27dc6" binding="panel" layout-manager="GridLayoutManager" row-count="3" column-count="5" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="15" left="15" bottom="15" right="15"/>
<constraints>
<xy x="20" y="20" width="614" height="400"/>
Expand All @@ -19,13 +19,13 @@
</component>
<vspacer id="38819">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="2" column="0" row-span="1" col-span="3" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<grid id="351a0" layout-manager="GridLayoutManager" row-count="3" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="5" vgap="5">
<margin top="5" left="5" bottom="5" right="5"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<grid row="1" column="0" row-span="1" col-span="5" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
Expand Down Expand Up @@ -72,8 +72,21 @@
</constraints>
<properties/>
</component>
<component id="8ef0" class="javax.swing.JButton" binding="loginButton">
<constraints>
<grid row="0" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Login"/>
</properties>
</component>
</children>
</grid>
<hspacer id="58992">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
</children>
</grid>
</form>
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package dev.koding.copilot.config

import com.intellij.openapi.options.Configurable
import dev.koding.copilot.auth.handleLogin
import java.text.NumberFormat
import javax.swing.JButton
import javax.swing.JFormattedTextField
import javax.swing.JPanel
import javax.swing.JTextField
Expand All @@ -13,6 +15,7 @@ class ApplicationConfigurable : Configurable {
private lateinit var panel: JPanel
private lateinit var copilotToken: JTextField
private lateinit var contextLines: JFormattedTextField
private lateinit var loginButton: JButton

override fun apply() {
settings.token = copilotToken.text
Expand All @@ -21,6 +24,7 @@ class ApplicationConfigurable : Configurable {

override fun createComponent(): JPanel {
contextLines.formatterFactory = DefaultFormatterFactory(NumberFormatter(NumberFormat.getIntegerInstance()))
loginButton.addActionListener { handleLogin { copilotToken.text = it } }
return panel
}

Expand Down
36 changes: 36 additions & 0 deletions src/main/kotlin/dev/koding/copilot/util/Notifications.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dev.koding.copilot.util

import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent

object Notifications {

private val group = NotificationGroupManager.getInstance().getNotificationGroup("GitHub Copilot")
private var shown = mutableSetOf<String>()

@Suppress("DialogTitleCapitalization")
fun send(
message: String,
type: NotificationType = NotificationType.INFORMATION,
once: Boolean = false,
vararg actions: NotificationAction
) {
if (once && message in shown) return
group.createNotification(message, type)
.apply {
actions.forEach {
addAction(object : AnAction(it.name) {
override fun actionPerformed(e: AnActionEvent) = it.action()
})
}
shown += message
}.notify(null)
}

data class NotificationAction(
val name: String,
val action: () -> Unit
)
}
4 changes: 3 additions & 1 deletion src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
id="Settings.Copilot"
groupId="tools"
instance="dev.koding.copilot.config.ApplicationConfigurable"/>

<completion.contributor language="any"
implementationClass="dev.koding.copilot.completion.CopilotCompletionContributor"
order="first"/>

<notificationGroup id="GitHub Copilot" displayType="STICKY_BALLOON"/>
</extensions>
</idea-plugin>
Loading

0 comments on commit 9dd3f0d

Please sign in to comment.