feat(intellij): support auth. (#341)

release-0.0
Zhiming Ma 2023-08-09 13:55:24 +08:00 committed by GitHub
parent 220fcc0d65
commit 203949bb76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 160 additions and 2 deletions

View File

@ -0,0 +1,54 @@
package com.tabbyml.intellijtabby.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.tabbyml.intellijtabby.agent.Agent
import com.tabbyml.intellijtabby.agent.AgentService
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
open class OpenAuthPage : AnAction() {
private val logger = Logger.getInstance(OpenAuthPage::class.java)
override fun actionPerformed(e: AnActionEvent) {
val task = object : Task.Modal(
e.project,
"Tabby Server Authorization",
true
) {
lateinit var job: Job
override fun run(indicator: ProgressIndicator) {
val agentService = service<AgentService>()
job = agentService.scope.launch {
agentService.requestAuth(indicator)
}
logger.info("Authorization task started.")
while (job.isActive) {
indicator.checkCanceled()
Thread.sleep(100)
}
}
override fun onCancel() {
logger.info("Authorization task cancelled.")
job.cancel()
}
}
ProgressManager.getInstance().run(task)
}
override fun update(e: AnActionEvent) {
val agentService = service<AgentService>()
e.presentation.isVisible = agentService.status.value == Agent.Status.UNAUTHORIZED
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
}

View File

@ -16,7 +16,9 @@ import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.util.Key
import com.intellij.util.EnvironmentUtil
import com.intellij.util.io.BaseOutputReader
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.suspendCancellableCoroutine
import java.io.OutputStreamWriter
@ -36,6 +38,8 @@ class Agent : ProcessAdapter() {
private val statusFlow = MutableStateFlow(Status.NOT_INITIALIZED)
val status = statusFlow.asStateFlow()
private val authRequiredEventFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val authRequiredEvent = authRequiredEventFlow.asSharedFlow()
init {
logger.info("Environment variables: PATH: ${EnvironmentUtil.getValue("PATH")}")
@ -147,6 +151,19 @@ class Agent : ProcessAdapter() {
return request("postEvent", listOf(event))
}
data class AuthUrlResponse(
val authUrl: String,
val code: String,
)
suspend fun requestAuthUrl(): AuthUrlResponse? {
return request("requestAuthUrl", listOf())
}
suspend fun waitForAuthToken(code: String) {
return request("waitForAuthToken", listOf(code))
}
fun close() {
streamWriter.close()
process.killProcess()
@ -237,6 +254,7 @@ class Agent : ProcessAdapter() {
"authRequired" -> {
logger.info("Agent notification $event")
authRequiredEventFlow.tryEmit(Unit)
}
else -> {

View File

@ -1,18 +1,28 @@
package com.tabbyml.intellijtabby.agent
import com.intellij.ide.BrowserUtil
import com.intellij.lang.Language
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.notification.Notifications
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import com.tabbyml.intellijtabby.actions.OpenAuthPage
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
import io.ktor.util.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@ -40,6 +50,32 @@ class AgentService : Disposable {
updateConfig(createAgentConfig(it))
}
}
scope.launch {
logger.info("Add authRequired event listener.")
agent.authRequiredEvent.collect {
logger.info("Will show auth required notification.")
val notification = Notification(
"com.tabbyml.intellijtabby.notification.warning",
"Authorization required for Tabby server",
NotificationType.WARNING,
)
notification.addAction(object : OpenAuthPage() {
init {
getTemplatePresentation().text = "Open Authorization Page..."
getTemplatePresentation().description = "Open the authorization web page in your web browser."
}
override fun actionPerformed(e: AnActionEvent) {
notification.expire()
super.actionPerformed(e)
}
})
invokeLater {
Notifications.Bus.notify(notification)
}
}
}
}
private fun createAgentConfig(state: ApplicationSettingsState.State): Agent.Config {
@ -65,7 +101,7 @@ class AgentService : Disposable {
agent.status.first { it != Agent.Status.NOT_INITIALIZED }
}
suspend fun updateConfig(config: Agent.Config) {
private suspend fun updateConfig(config: Agent.Config) {
waitForInitialized()
agent.updateConfig(config)
}
@ -93,6 +129,40 @@ class AgentService : Disposable {
agent.postEvent(event)
}
suspend fun requestAuth(progress: ProgressIndicator) {
waitForInitialized()
progress.isIndeterminate = true
progress.text = "Generating authorization url..."
val authUrlResponse = agent.requestAuthUrl()
val notification = if (authUrlResponse != null) {
BrowserUtil.browse(authUrlResponse.authUrl)
progress.text = "Waiting for authorization from browser..."
agent.waitForAuthToken(authUrlResponse.code)
if (status.value == Agent.Status.READY) {
Notification(
"com.tabbyml.intellijtabby.notification.info",
"Congrats, you're authorized, start to use Tabby now.",
NotificationType.INFORMATION
)
} else {
Notification(
"com.tabbyml.intellijtabby.notification.warning",
"Connection error, please check settings and try again.",
NotificationType.WARNING
)
}
} else {
Notification(
"com.tabbyml.intellijtabby.notification.info",
"You are already authorized.",
NotificationType.INFORMATION
)
}
invokeLater {
Notifications.Bus.notify(notification)
}
}
override fun dispose() {
agent.close()
}

View File

@ -72,6 +72,7 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
override fun getChildren(e: AnActionEvent?): Array<AnAction> {
val actionManager = ActionManager.getInstance()
return arrayOf(
actionManager.getAction("Tabby.OpenAuthPage"),
actionManager.getAction("Tabby.ToggleAutoCompletionEnabled"),
actionManager.getAction("Tabby.OpenSettings"),
)
@ -103,7 +104,7 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
tooltip = "Tabby: Cannot connect to Server"
}
Agent.Status.UNAUTHORIZED -> {
icon = AllIcons.General.Error
icon = AllIcons.General.Warning
tooltip = "Tabby: Requires authorization"
}
}

View File

@ -47,6 +47,14 @@
nonDefaultProject="true"/>
<editorFactoryListener implementation="com.tabbyml.intellijtabby.editor.EditorListener"/>
<statusBarWidgetFactory implementation="com.tabbyml.intellijtabby.status.StatusBarWidgetFactory"/>
<notificationGroup id="com.tabbyml.intellijtabby.notification.info"
displayType="BALLOON"
bundle="strings"
key="tabby.info"/>
<notificationGroup id="com.tabbyml.intellijtabby.notification.warning"
displayType="STICKY_BALLOON"
bundle="strings"
key="tabby.warning"/>
</extensions>
<actions>
@ -71,6 +79,11 @@
<keyboard-shortcut first-keystroke="ESCAPE" keymap="$default"/>
</action>
<separator/>
<action id="Tabby.OpenAuthPage"
class="com.tabbyml.intellijtabby.actions.OpenAuthPage"
text="Open Authorization Page..."
description="Open the authorization web page in your web browser.">
</action>
<action id="Tabby.ToggleAutoCompletionEnabled"
class="com.tabbyml.intellijtabby.actions.ToggleAutoCompletionEnabled">
</action>

View File

@ -0,0 +1,2 @@
tabby.info=Tabby notification
tabby.warning=Tabby warnings