Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add idea plugin for back-in-time debugging #136

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
build
.kotlin
local.properties
.intellijPlatform
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ plugins {
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.buildconfig) apply false
alias(libs.plugins.maven.publish) apply false
alias(libs.plugins.jetbrainsCompose) apply false
alias(libs.plugins.sqldelight) apply false
alias(libs.plugins.intelliJPlatform) apply false
// convention plugin
alias(libs.plugins.backintimeLint) apply false
alias(libs.plugins.backintimePublication) apply false
alias(libs.plugins.backintimeCompilerModule) apply false
alias(libs.plugins.intelliJComposeFeature) apply false
}

allprojects {
Expand Down
2 changes: 1 addition & 1 deletion compiler-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ kotlin {
implementation(projects.core.annotations)
implementation(projects.core.websocket.server)
implementation(projects.core.websocket.event)
implementation(projects.tooling.model)
implementation(projects.tooling.core.model)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.junit.Assert.assertEquals
import kotlin.test.Test
import kotlin.test.assertIs

class RegisterInstanceEventTest : BackInTimeDebugServiceTest() {
class RegisterInstanceTest : BackInTimeDebugServiceTest() {
@BackInTime
private class TestStateHolder

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import kotlin.test.assertIs
/**
* Checks if relationship between state holders are captured as expected.
*/
class RegisterRelationShipTest : BackInTimeDebugServiceTest() {
class RegisterNewDependencyTest : BackInTimeDebugServiceTest() {
@BackInTime
private class ParentTestStateHolderWithNormalChild {
val child = ChildTestStateHolder()
Expand Down
2 changes: 1 addition & 1 deletion core/runtime/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ kotlin {
commonMain.dependencies {
implementation(projects.core.websocket.client)
implementation(projects.core.websocket.event)
implementation(projects.tooling.model)
implementation(projects.tooling.core.model)
implementation(libs.ktor.client.core)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class BackInTimeWebSocketServer {
private var server: ApplicationEngine? = null
val isRunning: Boolean get() = server?.application?.isActive == true

val runningPort: Int? get() = server?.environment?.connectors?.firstOrNull()?.port

private val mutableSessionInfoList = mutableSetOf<SessionInfo>()
val sessionInfoList: List<SessionInfo> get() = mutableSessionInfoList.toList()

Expand Down
2 changes: 1 addition & 1 deletion demo/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ dependencies {
implementation(projects.core.runtime)
implementation(projects.core.annotations)
implementation(projects.core.websocket.event)
implementation(projects.tooling.model)
implementation(projects.tooling.core.model)
implementation(libs.core.ktx)
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.activity.compose)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.kitakkun.backintime.evaluation

import android.app.Application
import com.kitakkun.backintime.demo.flipper.FlipperInitializer
import com.kitakkun.backintime.core.runtime.BackInTimeDebugService
import com.kitakkun.backintime.core.runtime.connector.BackInTimeKtorWebSocketConnector
import com.kitakkun.backintime.core.runtime.getBackInTimeDebugService
import com.kitakkun.backintime.core.runtime.internal.BackInTimeCompilerInternalApi
import com.kitakkun.backintime.evaluation.di.appModule
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
Expand All @@ -11,7 +14,11 @@ class MyApplication : Application() {
override fun onCreate() {
super.onCreate()

FlipperInitializer().init(this)
@OptIn(BackInTimeCompilerInternalApi::class)
val service: BackInTimeDebugService = getBackInTimeDebugService()

service.setConnector(BackInTimeKtorWebSocketConnector(host = "10.0.2.2", port = 50023))
service.startService()

startKoin {
androidLogger()
Expand Down
2 changes: 1 addition & 1 deletion flipper-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@mui/material": "^5.14.18",
"@textea/json-viewer": "^3.2.3",
"backintime-websocket-event": "file:../core/websocket/event/build/dist/js/productionLibrary",
"backintime-tooling-model": "file:../tooling/model/build/dist/js/productionLibrary",
"backintime-tooling-model": "file:../tooling/core/model/build/dist/js/productionLibrary",
"backintime-flipper-lib": "file:../tooling/flipper-lib/build/dist/js/productionLibrary",
"react-icons": "^5.0.1"
}
Expand Down
5 changes: 4 additions & 1 deletion flipper-plugin/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3300,8 +3300,11 @@ babel-preset-jest@^29.6.3:
format-util "^1.0.5"
react "^19.0.0"

"backintime-tooling-model@file:../tooling/model/build/dist/js/productionLibrary":
"backintime-tooling-model@file:../tooling/core/model/build/dist/js/productionLibrary":
version "0.0.1-alpha01"
dependencies:
"@js-joda/core" "3.2.0"
format-util "^1.0.5"

"backintime-websocket-event@file:../core/websocket/event/build/dist/js/productionLibrary":
version "0.0.1-alpha01"
Expand Down
6 changes: 5 additions & 1 deletion gradle-conventions-settings/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
plugins {
`kotlin-dsl`
}
}

dependencies {
compileOnly(libs.jetbrains.compose.gradle.plugin)
}
3 changes: 3 additions & 0 deletions gradle-conventions-settings/src/main/kotlin/util/Project.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ package util
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.getByType
import org.jetbrains.compose.ComposeExtension

val Project.libs get() = extensions.getByType<VersionCatalogsExtension>().named("libs")

val Project.compose get() = extensions.getByType<ComposeExtension>().dependencies
2 changes: 2 additions & 0 deletions gradle-conventions/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ dependencies {
implementation(libs.kotlin.gradle.plugin)
compileOnly(libs.ktlint.gradle)
compileOnly(libs.maven.publish)
implementation(libs.jetbrains.compose.gradle.plugin)
implementation(libs.compose.compiler.gradle.plugin)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import util.libs

plugins {
id("org.jetbrains.kotlin.jvm")
id("org.jetbrains.compose")
id("org.jetbrains.kotlin.plugin.compose")
}

configure<KotlinJvmProjectExtension> {
jvmToolchain(17)
}

repositories {
mavenCentral()
google()
maven("https://packages.jetbrains.team/maven/p/kpm/public/")
}

dependencies {
implementation(project(":tooling:core:ui"))
implementation(project(":tooling:core:model"))
implementation(project(":tooling:core:usecase"))
implementation(libs.findLibrary("jewel").get())
implementation(compose.desktop.currentOs) {
exclude(group = "org.jetbrains.compose.material")
}
}
11 changes: 10 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ include(
":core:websocket:server",
":core:websocket:client",
":core:websocket:event",
":tooling:model",
":tooling:flipper-lib",
":tooling:idea-plugin",
":tooling:core:model",
":tooling:core:database",
":tooling:core:ui",
":tooling:core:usecase",
":tooling:core:shared",
":tooling:app",
":tooling:feature:inspector",
":tooling:feature:settings",
":tooling:feature:log",
)
11 changes: 11 additions & 0 deletions tooling/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
alias(libs.plugins.intelliJComposeFeature)
}

dependencies {
implementation(projects.tooling.core.shared)
implementation(projects.tooling.core.database)
implementation(projects.tooling.feature.log)
implementation(projects.tooling.feature.inspector)
implementation(projects.tooling.feature.settings)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.kitakkun.backintime.tooling.app

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import com.kitakkun.backintime.feature.settings.SettingsScreen
import com.kitakkun.backintime.tooling.core.shared.BackInTimeDebuggerService
import com.kitakkun.backintime.tooling.core.shared.BackInTimeDebuggerSettings
import com.kitakkun.backintime.tooling.core.shared.IDENavigator
import com.kitakkun.backintime.tooling.core.shared.PluginStateService
import com.kitakkun.backintime.tooling.core.ui.component.HorizontalDivider
import com.kitakkun.backintime.tooling.core.ui.compositionlocal.LocalIDENavigator
import com.kitakkun.backintime.tooling.core.ui.compositionlocal.LocalPluginStateService
import com.kitakkun.backintime.tooling.core.ui.compositionlocal.LocalServer
import com.kitakkun.backintime.tooling.core.ui.compositionlocal.LocalSettings
import com.kitakkun.backintime.tooling.core.ui.preview.PreviewContainer
import com.kitakkun.backintime.tooling.feature.log.LogScreen
import com.kitakkun.backintime.tooling.model.Tab
import com.kitakkunl.backintime.feature.inspector.InspectorScreen
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter

@Composable
fun BackInTimeDebuggerApp() {
val server = LocalServer.current
val serverState = server.state
val settings = LocalSettings.current

val pluginStateService = LocalPluginStateService.current
val pluginState by pluginStateService.stateFlow.collectAsState()

LaunchedEffect(Unit) {
if (!serverState.serverIsRunning) {
server.restartServer(settings.getState().serverPort)
}
}

// automatically select the new session if no session is selected.
LaunchedEffect(server, pluginState) {
snapshotFlow { server.state.connections }
.distinctUntilChanged()
.filter { it.isNotEmpty() }
.collect {
if (pluginState.globalState.selectedSessionId == null) {
pluginStateService.updateSessionId(it.first().id)
}
}
}

Column(
modifier = Modifier.fillMaxSize()
) {
HorizontalDivider()
BackInTimeTopBar(
currentTab = pluginState.globalState.activeTab,
onClickInstances = { pluginStateService.updateTab(Tab.Inspector) },
onClickLog = { pluginStateService.updateTab(Tab.Log) },
onClickSettings = { pluginStateService.updateTab(Tab.Settings) },
)
HorizontalDivider()
when (pluginState.globalState.activeTab) {
Tab.Inspector -> InspectorScreen()
Tab.Log -> LogScreen()
Tab.Settings -> SettingsScreen()
}
}
}

@Preview
@Composable
private fun BackInTimeDebuggerAppPreview() {
PreviewContainer {
CompositionLocalProvider(
LocalSettings provides BackInTimeDebuggerSettings.Dummy,
LocalServer provides BackInTimeDebuggerService.Dummy,
LocalPluginStateService provides PluginStateService.Dummy,
LocalIDENavigator provides IDENavigator.Noop,
) {
BackInTimeDebuggerApp()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.kitakkun.backintime.tooling.app

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.kitakkun.backintime.tooling.core.ui.preview.PreviewContainer
import com.kitakkun.backintime.tooling.model.Tab
import org.jetbrains.jewel.ui.component.SelectableIconActionButton
import org.jetbrains.jewel.ui.icons.AllIconsKeys

@Composable
fun BackInTimeTopBar(
currentTab: Tab,
onClickSettings: () -> Unit,
onClickInstances: () -> Unit,
onClickLog: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(modifier = modifier) {
SelectableIconActionButton(
selected = currentTab == Tab.Inspector,
onClick = onClickInstances,
key = AllIconsKeys.Toolwindows.ToolWindowHierarchy,
contentDescription = null,
)
SelectableIconActionButton(
selected = currentTab == Tab.Log,
onClick = onClickLog,
key = AllIconsKeys.Nodes.DataSchema,
contentDescription = null,
)
Spacer(Modifier.weight(1f))
SelectableIconActionButton(
selected = currentTab == Tab.Settings,
onClick = onClickSettings,
key = AllIconsKeys.General.Settings,
contentDescription = null,
)
}
}

@Preview
@Composable
private fun BackInTimeTopBarPreview() {
PreviewContainer {
BackInTimeTopBar(
currentTab = Tab.Inspector,
onClickInstances = {},
onClickLog = {},
onClickSettings = {},
)
}
}
27 changes: 27 additions & 0 deletions tooling/core/database/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
plugins {
alias(libs.plugins.kotlinJvm)
alias(libs.plugins.sqldelight)
alias(libs.plugins.kotlinSerialization)
}

kotlin {
jvmToolchain(17)
}

sqldelight {
databases {
create("Database") {
packageName.set("com.kitakkun.backintime.tooling.core.database")
}
}
}

dependencies {
implementation(projects.core.websocket.event)
implementation(projects.tooling.core.model)
implementation(projects.tooling.core.shared)
implementation(libs.sqldelight.sqlite.driver)
implementation(libs.sqldelight.coroutines.extensions)
implementation(libs.kotlinx.serialization.json)
testImplementation(libs.kotlin.test)
}
Loading
Loading