Skip to content

Commit d66cbd9

Browse files
committed
feat(debugger): implement Shire debugging features including breakpoints and variable snapshots #379
1 parent d8fab1d commit d66cbd9

23 files changed

+1261
-7
lines changed

core/src/main/kotlin/cc/unitmesh/devti/sketch/ui/code/EditorFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class EditorPadding(private val editor: Editor, pad: Int) :
3636
class EditorFragment(
3737
var editor: EditorEx,
3838
private val editorLineThreshold: Int = EDITOR_LINE_THRESHOLD,
39-
private val previewEditor: FileEditor?
39+
private val previewEditor: FileEditor? = null,
4040
) {
4141
private val expandCollapseTextLabel: JBLabel = JBLabel("", 0).apply {
4242
isOpaque = true

core/src/main/kotlin/cc/unitmesh/devti/sketch/ui/code/EditorUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ object EditorUtil {
6161
return createCodeViewerEditor(project, file, document, disposable, isShowLineNo)
6262
}
6363

64-
private fun createCodeViewerEditor(
64+
fun createCodeViewerEditor(
6565
project: Project,
6666
file: VirtualFile,
6767
document: Document,

exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/ast/action/PatternActionProcessor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package cc.unitmesh.devti.language.ast.action
33
import cc.unitmesh.devti.language.ast.HobbitHole
44
import cc.unitmesh.devti.language.ast.VariableTransform
55
import cc.unitmesh.devti.language.ast.shireql.ShireQLProcessor
6-
import cc.unitmesh.devti.language.ast.snapshot.VariableSnapshotRecorder
76
import cc.unitmesh.devti.language.compiler.searcher.PatternSearcher
7+
import cc.unitmesh.devti.language.debugger.snapshot.VariableSnapshotRecorder
88
import cc.unitmesh.devti.language.middleware.post.PostProcessorContext
99
import com.intellij.openapi.project.Project
1010

exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/ast/variable/resolver/UserCustomVariableResolver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package cc.unitmesh.devti.language.ast.variable.resolver
33
import cc.unitmesh.devti.language.ast.action.PatternActionProcessor
44
import cc.unitmesh.devti.language.ast.variable.resolver.base.VariableResolver
55
import cc.unitmesh.devti.language.ast.variable.resolver.base.VariableResolverContext
6-
import cc.unitmesh.devti.language.ast.snapshot.VariableSnapshotRecorder
6+
import cc.unitmesh.devti.language.debugger.snapshot.VariableSnapshotRecorder
77

88
class UserCustomVariableResolver(
99
private val context: VariableResolverContext,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package cc.unitmesh.devti.language.debugger
2+
3+
import cc.unitmesh.devti.language.DevInFileType
4+
import com.intellij.openapi.fileTypes.FileTypeRegistry
5+
import com.intellij.openapi.project.Project
6+
import com.intellij.openapi.vfs.VirtualFile
7+
import com.intellij.xdebugger.breakpoints.XLineBreakpoint
8+
9+
class ShireBreakpointHandler(val process: ShireDebugProcess) :
10+
com.intellij.xdebugger.breakpoints.XBreakpointHandler<XLineBreakpoint<ShireBpProperties>>(ShireLineBreakpointType::class.java) {
11+
override fun registerBreakpoint(breakpoint: XLineBreakpoint<ShireBpProperties>) {
12+
process.addBreakpoint(breakpoint)
13+
}
14+
15+
override fun unregisterBreakpoint(breakpoint: XLineBreakpoint<ShireBpProperties>, temporary: Boolean) {
16+
process.removeBreakpoint(breakpoint)
17+
}
18+
}
19+
20+
class ShireBpProperties : com.intellij.xdebugger.breakpoints.XBreakpointProperties<ShireBpProperties>() {
21+
override fun getState(): ShireBpProperties = this
22+
override fun loadState(state: ShireBpProperties) {}
23+
}
24+
25+
class ShireLineBreakpointType : com.intellij.xdebugger.breakpoints.XLineBreakpointType<ShireBpProperties>(ID, TITLE) {
26+
override fun canPutAt(file: VirtualFile, line: Int, project: Project): Boolean {
27+
return canPutAt(project, file, line)
28+
}
29+
30+
override fun createBreakpointProperties(file: VirtualFile, line: Int): ShireBpProperties = ShireBpProperties()
31+
32+
33+
fun canPutAt(project: Project, file: VirtualFile, line: Int): Boolean {
34+
// val shireFile = PsiManager.getInstance(project).findFile(file) as? ShireFile ?: return false
35+
// val findLeafElementAt = shireFile.node.findLeafElementAt(line)?.elementType
36+
// findLeafElementAt?.let {
37+
// return true
38+
// }
39+
//
40+
// return false
41+
return (FileTypeRegistry.getInstance().isFileOfType(file, DevInFileType.INSTANCE))
42+
}
43+
44+
companion object {
45+
private const val ID = "the-shire-line"
46+
private const val TITLE = "Shire Breakpoints"
47+
}
48+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package cc.unitmesh.devti.language.debugger
2+
3+
import cc.unitmesh.devti.language.psi.DevInFile
4+
import cc.unitmesh.devti.language.run.DevInsConfiguration
5+
import cc.unitmesh.devti.language.run.DevInsRunConfigurationProfileState
6+
import cc.unitmesh.devti.language.run.ShireProcessAdapter
7+
import cc.unitmesh.devti.language.run.runner.ShireConsoleView
8+
import cc.unitmesh.devti.language.run.runner.ShireRunner
9+
import cc.unitmesh.devti.language.run.runner.ShireRunnerContext
10+
import cc.unitmesh.devti.language.status.DevInsRunListener
11+
import cc.unitmesh.devti.util.AutoDevCoroutineScope
12+
import com.intellij.execution.process.ProcessEvent
13+
import com.intellij.execution.runners.ExecutionEnvironment
14+
import com.intellij.execution.ui.ConsoleViewContentType
15+
import com.intellij.execution.ui.ExecutionConsole
16+
import com.intellij.execution.ui.RunnerLayoutUi
17+
import com.intellij.execution.ui.layout.PlaceInGrid
18+
import com.intellij.icons.AllIcons
19+
import com.intellij.openapi.Disposable
20+
import com.intellij.openapi.application.ApplicationManager
21+
import com.intellij.ui.content.Content
22+
import com.intellij.util.Alarm
23+
import com.intellij.xdebugger.XDebugProcess
24+
import com.intellij.xdebugger.XDebugSession
25+
import com.intellij.xdebugger.XSourcePosition
26+
import com.intellij.xdebugger.breakpoints.XBreakpointHandler
27+
import com.intellij.xdebugger.breakpoints.XLineBreakpoint
28+
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider
29+
import com.intellij.xdebugger.frame.XSuspendContext
30+
import com.intellij.xdebugger.ui.XDebugTabLayouter
31+
import kotlinx.coroutines.launch
32+
import kotlinx.coroutines.runBlocking
33+
34+
class ShireDebugProcess(private val session: XDebugSession, private val environment: ExecutionEnvironment) :
35+
XDebugProcess(session), Disposable {
36+
private val debuggableConfiguration: DevInsConfiguration get() = session.runProfile as DevInsConfiguration
37+
private val runProfileState =
38+
debuggableConfiguration.getState(environment.executor, environment) as DevInsRunConfigurationProfileState
39+
40+
private val connection = ApplicationManager.getApplication().messageBus.connect(this)
41+
private val myRequestsScheduler: Alarm
42+
43+
init {
44+
myRequestsScheduler = Alarm(Alarm.ThreadToUse.POOLED_THREAD, this)
45+
}
46+
47+
private val breakpointHandlers = arrayOf<XBreakpointHandler<*>>(
48+
ShireBreakpointHandler(
49+
this
50+
)
51+
)
52+
53+
override fun getBreakpointHandlers(): Array<XBreakpointHandler<*>> = breakpointHandlers
54+
override fun createTabLayouter(): XDebugTabLayouter = ShireDebugTabLayouter(runProfileState.console)
55+
override fun createConsole(): ExecutionConsole = runProfileState.console
56+
57+
var shireRunnerContext: ShireRunnerContext? = null
58+
59+
fun start() {
60+
AutoDevCoroutineScope.scope(session.project).launch {
61+
runBlocking {
62+
val psiFile: DevInFile = DevInFile.lookup(session.project, debuggableConfiguration.getScriptPath())
63+
?: return@runBlocking
64+
65+
shireRunnerContext = ShireRunner.compileOnly(session.project, psiFile, mapOf(), null)
66+
session.positionReached(ShireSuspendContext(this@ShireDebugProcess, session.project))
67+
}
68+
}
69+
70+
val processAdapter = ShireProcessAdapter(debuggableConfiguration, runProfileState.console)
71+
processHandler.addProcessListener(processAdapter)
72+
runProfileState.console.print("Waiting for resume...", ConsoleViewContentType.NORMAL_OUTPUT)
73+
}
74+
75+
override fun resume(context: XSuspendContext?) {
76+
connection.subscribe(DevInsRunListener.TOPIC, object : DevInsRunListener {
77+
override fun runFinish(
78+
allOutput: String,
79+
llmOutput: String,
80+
event: ProcessEvent,
81+
scriptPath: String,
82+
consoleView: ShireConsoleView?,
83+
) {
84+
this@ShireDebugProcess.stop()
85+
}
86+
})
87+
88+
runProfileState.execute(environment.executor, environment.runner)
89+
}
90+
91+
override fun runToPosition(position: XSourcePosition, context: XSuspendContext?) {
92+
runProfileState.execute(environment.executor, environment.runner)
93+
}
94+
95+
override fun startStepOut(context: XSuspendContext?) {
96+
runProfileState.execute(environment.executor, environment.runner)
97+
}
98+
99+
override fun startStepInto(context: XSuspendContext?) {
100+
runProfileState.execute(environment.executor, environment.runner)
101+
}
102+
103+
override fun startStepOver(context: XSuspendContext?) {
104+
runProfileState.execute(environment.executor, environment.runner)
105+
}
106+
107+
override fun startForceStepInto(context: XSuspendContext?) {
108+
runProfileState.execute(environment.executor, environment.runner)
109+
}
110+
111+
override fun startPausing() {
112+
113+
}
114+
115+
override fun stop() {
116+
connection.disconnect()
117+
processHandler.destroyProcess()
118+
session.stop()
119+
}
120+
121+
override fun dispose() {
122+
connection.disconnect()
123+
}
124+
125+
fun addBreakpoint(breakpoint: XLineBreakpoint<ShireBpProperties>) {
126+
127+
}
128+
129+
fun removeBreakpoint(breakpoint: XLineBreakpoint<ShireBpProperties>) {
130+
131+
}
132+
133+
override fun getEditorsProvider(): XDebuggerEditorsProvider {
134+
return ShireDebuggerEditorsProvider()
135+
}
136+
}
137+
138+
class ShireDebugTabLayouter(val console: ShireConsoleView) : XDebugTabLayouter() {
139+
override fun registerConsoleContent(ui: RunnerLayoutUi, console: ExecutionConsole): Content {
140+
val content = ui
141+
.createContent(
142+
"DebuggedConsoleContent", console.component, "Shire Debugged Console",
143+
AllIcons.Debugger.Console, console.preferredFocusableComponent
144+
)
145+
146+
content.isCloseable = false
147+
ui.addContent(content, 1, PlaceInGrid.bottom, false)
148+
return content
149+
}
150+
}
151+
152+
153+
val RUNNER_ID: String = "ShireProgramRunner"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cc.unitmesh.devti.language.debugger
2+
3+
import cc.unitmesh.devti.language.run.DevInsConfiguration
4+
import com.intellij.execution.configurations.RunProfile
5+
import com.intellij.execution.configurations.RunProfileState
6+
import com.intellij.execution.configurations.RunnerSettings
7+
import com.intellij.execution.executors.DefaultDebugExecutor
8+
import com.intellij.execution.runners.ExecutionEnvironment
9+
import com.intellij.execution.runners.GenericProgramRunner
10+
import com.intellij.execution.ui.RunContentDescriptor
11+
import com.intellij.xdebugger.XDebugProcess
12+
import com.intellij.xdebugger.XDebugProcessStarter
13+
import com.intellij.xdebugger.XDebugSession
14+
import com.intellij.xdebugger.XDebuggerManager
15+
16+
/// refs to: https://github.com/KronicDeth/intellij-elixir/pull/643/files#diff-b1ba5c87ca6f66a455e4c1539cb2d99a62722d067a3d9e8043b290426cea5470
17+
class ShireDebugRunner : GenericProgramRunner<RunnerSettings>() {
18+
override fun canRun(executorId: String, profile: RunProfile): Boolean {
19+
return (executorId == DefaultDebugExecutor.EXECUTOR_ID) && profile is DevInsConfiguration
20+
}
21+
22+
override fun getRunnerId(): String = RUNNER_ID
23+
24+
override fun doExecute(state: RunProfileState, environment: ExecutionEnvironment): RunContentDescriptor? {
25+
val xDebuggerManager = XDebuggerManager.getInstance(environment.project)
26+
return xDebuggerManager.startSession(environment, object : XDebugProcessStarter() {
27+
override fun start(session: XDebugSession): XDebugProcess {
28+
val shireDebugProcess = ShireDebugProcess(session, environment)
29+
shireDebugProcess.start()
30+
return shireDebugProcess
31+
}
32+
}).runContentDescriptor
33+
}
34+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package cc.unitmesh.devti.language.debugger
2+
3+
import com.intellij.openapi.Disposable
4+
import com.intellij.openapi.options.Configurable
5+
import com.intellij.openapi.options.ConfigurableUi
6+
import com.intellij.openapi.options.SimpleConfigurable
7+
import com.intellij.openapi.util.Getter
8+
import com.intellij.ui.components.JBCheckBox
9+
import com.intellij.ui.dsl.builder.Panel
10+
import com.intellij.ui.dsl.builder.panel
11+
import com.intellij.util.xmlb.XmlSerializerUtil
12+
import com.intellij.xdebugger.settings.DebuggerSettingsCategory
13+
import com.intellij.xdebugger.settings.XDebuggerSettings
14+
import javax.swing.JComponent
15+
16+
class ShireDebugSettings : XDebuggerSettings<ShireDebugSettings>("shire"), Getter<ShireDebugSettings> {
17+
override fun get(): ShireDebugSettings = this
18+
override fun getState()= this
19+
override fun loadState(state: ShireDebugSettings) {
20+
XmlSerializerUtil.copyBean(state, this)
21+
}
22+
23+
var breakOnPanic: Boolean = true
24+
override fun createConfigurables(category: DebuggerSettingsCategory): MutableCollection<out Configurable> {
25+
val config = SimpleConfigurable.create(
26+
"ShireDebugSettings",
27+
"Shire Debugger",
28+
ShireDebugSettingsConfigurableUi::class.java,
29+
this
30+
)
31+
return mutableListOf(config)
32+
}
33+
34+
companion object {
35+
@JvmStatic
36+
fun getInstance(): ShireDebugSettings = getInstance(ShireDebugSettings::class.java)
37+
}
38+
}
39+
40+
class ShireDebugSettingsConfigurableUi : ConfigurableUi<ShireDebugSettings>, Disposable {
41+
private val components: List<ShireDebuggerUiComponent> = run {
42+
val components = mutableListOf<ShireDebuggerUiComponent>()
43+
components.add(RsBreakOnPanicConfigurableUi())
44+
components
45+
}
46+
47+
override fun isModified(settings: ShireDebugSettings): Boolean = components.any { it.isModified(settings) }
48+
49+
override fun reset(settings: ShireDebugSettings) {
50+
components.forEach { it.reset(settings) }
51+
}
52+
53+
override fun apply(settings: ShireDebugSettings) {
54+
components.forEach { it.apply(settings) }
55+
}
56+
57+
override fun getComponent(): JComponent {
58+
return panel {
59+
for (component in components) {
60+
component.buildUi(this)
61+
}
62+
}
63+
}
64+
65+
override fun dispose() {
66+
components.forEach { it.dispose() }
67+
}
68+
}
69+
70+
abstract class ShireDebuggerUiComponent: ConfigurableUi<ShireDebugSettings>, Disposable {
71+
abstract fun buildUi(panel: Panel)
72+
73+
override fun getComponent(): JComponent {
74+
return panel {
75+
buildUi(this)
76+
}
77+
}
78+
79+
override fun dispose() {}
80+
}
81+
82+
class RsBreakOnPanicConfigurableUi : ShireDebuggerUiComponent() {
83+
private val breakOnPanicCheckBox: JBCheckBox = JBCheckBox("Debug", ShireDebugSettings.getInstance().breakOnPanic)
84+
85+
override fun reset(settings: ShireDebugSettings) {
86+
breakOnPanicCheckBox.isSelected = settings.breakOnPanic
87+
}
88+
89+
override fun isModified(settings: ShireDebugSettings): Boolean
90+
= settings.breakOnPanic != breakOnPanicCheckBox.isSelected
91+
92+
override fun apply(settings: ShireDebugSettings) {
93+
settings.breakOnPanic = breakOnPanicCheckBox.isSelected
94+
}
95+
96+
override fun buildUi(panel: Panel) {
97+
with(panel) {
98+
row { cell(breakOnPanicCheckBox) }
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)