feat(intellij): support auth. (#341)
parent
220fcc0d65
commit
203949bb76
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
tabby.info=Tabby notification
|
||||
tabby.warning=Tabby warnings
|
||||
Loading…
Reference in New Issue