diff --git a/build.gradle.kts b/build.gradle.kts index dec5ace..c633926 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,6 +33,7 @@ val buildDataList = listOf( group = "com.cppcxy" val emmyluaAnalyzerVersion = "0.6.3" +val emmyDebuggerVersion = "1.8.2" val emmyluaAnalyzerProjectUrl = "https://github.com/CppCXY/EmmyLuaAnalyzer" @@ -44,6 +45,7 @@ val runnerNumber = System.getenv("RUNNER_NUMBER") ?: "Dev" version = "${emmyluaAnalyzerVersion}.${runnerNumber}-IDEA${buildVersion}" + task("download", type = Download::class) { src( arrayOf( @@ -83,6 +85,62 @@ task("install", type = Copy::class) { destinationDir = file("src/main/resources") } +task("downloadEmmyDebugger", type = Download::class) { + src(arrayOf( + "https://github.com/EmmyLua/EmmyLuaDebugger/releases/download/${emmyDebuggerVersion}/darwin-arm64.zip", + "https://github.com/EmmyLua/EmmyLuaDebugger/releases/download/${emmyDebuggerVersion}/darwin-x64.zip", + "https://github.com/EmmyLua/EmmyLuaDebugger/releases/download/${emmyDebuggerVersion}/linux-x64.zip", + "https://github.com/EmmyLua/EmmyLuaDebugger/releases/download/${emmyDebuggerVersion}/win32-x64.zip", + "https://github.com/EmmyLua/EmmyLuaDebugger/releases/download/${emmyDebuggerVersion}/win32-x86.zip" + )) + + dest("temp") +} + +task("unzipEmmyDebugger", type = Copy::class) { + dependsOn("downloadEmmyDebugger") + from(zipTree("temp/win32-x86.zip")) { + into("windows/x86") + } + from(zipTree("temp/win32-x64.zip")) { + into("windows/x64") + } + from(zipTree("temp/darwin-x64.zip")) { + into("mac/x64") + } + from(zipTree("temp/darwin-arm64.zip")) { + into("mac/arm64") + } + from(zipTree("temp/linux-x64.zip")) { + into("linux") + } + destinationDir = file("temp") +} + +task("installEmmyDebugger", type = Copy::class) { + dependsOn("unzipEmmyDebugger") + from("temp/windows/x64/") { + include("emmy_core.dll") + into("debugger/emmy/windows/x64") + } + from("temp/windows/x86/") { + include("emmy_core.dll") + into("debugger/emmy/windows/x86") + } + from("temp/linux/") { + include("emmy_core.so") + into("debugger/emmy/linux") + } + from("temp/mac/x64") { + include("emmy_core.dylib") + into("debugger/emmy/mac/x64") + } + from("temp/mac/arm64") { + include("emmy_core.dylib") + into("debugger/emmy/mac/arm64") + } + destinationDir = file("src/main/resources") +} // Configure Gradle IntelliJ Plugin // Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html @@ -131,7 +189,7 @@ tasks { } buildPlugin { - dependsOn("install") + dependsOn("install", "installEmmyDebugger") } // fix by https://youtrack.jetbrains.com/issue/IDEA-325747/IDE-always-actively-disables-LSP-plugins-if-I-ask-the-plugin-to-return-localized-diagnostic-messages. runIde { @@ -144,6 +202,10 @@ tasks { from("${project.projectDir}/src/main/resources/server") into("${destinationDir.path}/${pluginName.get()}/server") } + copy { + from("${project.projectDir}/src/main/resources/debugger") + into("${destinationDir.path}/${pluginName.get()}/debugger") + } } } } diff --git a/src/main/java/com/tang/intellij/lua/debugger/DebugLogger.kt b/src/main/java/com/tang/intellij/lua/debugger/DebugLogger.kt new file mode 100644 index 0000000..dac31d0 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/DebugLogger.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.openapi.project.Project + +/** + * console logger + * Created by tangzx on 2017/5/1. + */ +interface DebugLogger { + fun print(text: String, consoleType: LogConsoleType, contentType: ConsoleViewContentType) + fun println(text: String, consoleType: LogConsoleType, contentType: ConsoleViewContentType) + fun printHyperlink(text: String, consoleType: LogConsoleType, handler: (project: Project) -> Unit) + fun error(text: String, consoleType: LogConsoleType = LogConsoleType.EMMY) +} + +enum class LogConsoleType { + EMMY, NORMAL +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/DebuggerType.java b/src/main/java/com/tang/intellij/lua/debugger/DebuggerType.java new file mode 100644 index 0000000..ae54628 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/DebuggerType.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger; + +/** + * + * Created by tangzx on 2017/5/7. + */ +public enum DebuggerType { + Attach(1, "Attach Debugger(Not available)"), Mob(2, "Remote Debugger(Mobdebug)"); + private int v; + private String desc; + DebuggerType(int v, String desc) { + this.v = v; + this.desc = desc; + } + public static DebuggerType valueOf(int v) { + switch (v) { + case 1: return Attach; + case 2: return Mob; + default: return null; + } + } + public int value() { return v; } + public String toString() { return desc; } +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/IRemoteConfiguration.kt b/src/main/java/com/tang/intellij/lua/debugger/IRemoteConfiguration.kt new file mode 100644 index 0000000..fa4ec07 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/IRemoteConfiguration.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +/** + * + * Created by tangzx on 2017/5/7. + */ +interface IRemoteConfiguration { + val port: Int +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaCommandLineState.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaCommandLineState.kt new file mode 100644 index 0000000..0244f0e --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaCommandLineState.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.execution.ExecutionException +import com.intellij.execution.Executor +import com.intellij.execution.configurations.CommandLineState +import com.intellij.execution.process.ColoredProcessHandler +import com.intellij.execution.process.ProcessHandler +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.ui.ConsoleView + +/** + * + * Created by TangZX on 2016/12/30. + */ +class LuaCommandLineState(environment: ExecutionEnvironment) : CommandLineState(environment) { + + @Throws(ExecutionException::class) + override fun startProcess(): ProcessHandler { + val runProfile = environment.runProfile as LuaRunConfiguration + return ColoredProcessHandler(runProfile.createCommandLine()!!) + } + + @Throws(ExecutionException::class) + override fun createConsole(executor: Executor): ConsoleView? { + val consoleView = super.createConsole(executor) + consoleView?.addMessageFilter(LuaTracebackFilter(environment.project)) + return consoleView + } +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaConfigurationFactory.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaConfigurationFactory.kt new file mode 100644 index 0000000..4371e8f --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaConfigurationFactory.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.execution.BeforeRunTask +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.execution.configurations.ConfigurationType +import com.intellij.openapi.util.Key + +/** + * base configuration factory + * disable `Make` task + */ +abstract class LuaConfigurationFactory(type: ConfigurationType) : ConfigurationFactory(type) { + override fun configureBeforeRunTaskDefaults(providerID: Key>?, task: BeforeRunTask<*>?) { + if ("Make" == providerID?.toString()) + task?.isEnabled = false + } + + override fun getId(): String { + return name + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaDebugProcess.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaDebugProcess.kt new file mode 100644 index 0000000..aca995b --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaDebugProcess.kt @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.Processor +import com.intellij.util.containers.ContainerUtil +import com.intellij.xdebugger.XDebugProcess +import com.intellij.xdebugger.XDebugSession +import com.intellij.xdebugger.XDebuggerManager +import com.intellij.xdebugger.XSourcePosition +import com.intellij.xdebugger.breakpoints.XBreakpointHandler +import com.intellij.xdebugger.breakpoints.XBreakpointProperties +import com.intellij.xdebugger.breakpoints.XLineBreakpoint +import com.intellij.xdebugger.frame.XSuspendContext +import com.intellij.xdebugger.impl.XDebugSessionImpl +import com.intellij.xdebugger.impl.actions.XDebuggerActions + +/** + * + * Created by tangzx on 2017/5/1. + */ +abstract class LuaDebugProcess protected constructor(session: XDebugSession) : XDebugProcess(session), DebugLogger { + + override fun sessionInitialized() { + super.sessionInitialized() + session.setPauseActionSupported(true) + session.consoleView.addMessageFilter(LuaTracebackFilter(session.project)) + } + + override fun registerAdditionalActions(leftToolbar: DefaultActionGroup, topToolbar: DefaultActionGroup, settings: DefaultActionGroup) { + val actionManager = ActionManager.getInstance() + topToolbar.remove(actionManager.getAction(XDebuggerActions.RUN_TO_CURSOR)) + topToolbar.remove(actionManager.getAction(XDebuggerActions.FORCE_STEP_INTO)) + } + + override fun print(text: String, consoleType: LogConsoleType, contentType: ConsoleViewContentType) { + session.consoleView.print(text, contentType) + } + + override fun println(text: String, consoleType: LogConsoleType, contentType: ConsoleViewContentType) { + print("$text\n", consoleType, contentType) + } + + override fun error(text: String, consoleType: LogConsoleType) { + print("$text\n", consoleType, ConsoleViewContentType.ERROR_OUTPUT) + } + + override fun printHyperlink(text: String, consoleType: LogConsoleType, handler: (project: Project) -> Unit) { + session.consoleView.printHyperlink(text, handler) + } + + override fun resume(context: XSuspendContext?) { + run() + } + + override fun startStepOut(context: XSuspendContext?) { + startStepOver(context) + } + + override fun startForceStepInto(context: XSuspendContext?) { + startStepInto(context) + } + + override fun runToPosition(position: XSourcePosition, context: XSuspendContext?) { + resume(context) + } + + override fun getBreakpointHandlers(): Array> { + return arrayOf(object : XBreakpointHandler>>(LuaLineBreakpointType::class.java) { + override fun registerBreakpoint(breakpoint: XLineBreakpoint>) { + val sourcePosition = breakpoint.sourcePosition + if (sourcePosition != null) { + registerBreakpoint(sourcePosition, breakpoint) + } + } + + override fun unregisterBreakpoint(breakpoint: XLineBreakpoint>, temporary: Boolean) { + val sourcePosition = breakpoint.sourcePosition + if (sourcePosition != null) { + unregisterBreakpoint(sourcePosition, breakpoint) + } + } + }) + } + + protected fun processBreakpoint(processor: Processor>) { + ApplicationManager.getApplication().runReadAction { + val breakpoints = XDebuggerManager.getInstance(session.project) + .breakpointManager + .getBreakpoints(LuaLineBreakpointType::class.java) + ContainerUtil.process(breakpoints, processor) + } + } + + protected open fun registerBreakpoint(sourcePosition: XSourcePosition, breakpoint: XLineBreakpoint<*>) { + } + + protected open fun unregisterBreakpoint(sourcePosition: XSourcePosition, breakpoint: XLineBreakpoint<*>) { + } + + protected fun getBreakpoint(file: VirtualFile, line: Int): XLineBreakpoint<*>? { + var bp:XLineBreakpoint<*>? = null + processBreakpoint(Processor { + val pos = it.sourcePosition + if (file == pos?.file && line == pos.line) { + bp = it + } + true + }) + return bp + } + + fun setStack(stack: LuaExecutionStack) { + val frames = stack.stackFrames + for (topFrame in frames) { + val sourcePosition = topFrame.sourcePosition + if (sourcePosition != null) { + stack.setTopFrame(topFrame) + val breakpoint = getBreakpoint(sourcePosition.file, sourcePosition.line) + if (breakpoint != null) { + ApplicationManager.getApplication().invokeLater { + session.breakpointReached(breakpoint, null, LuaSuspendContext(stack)) + session.showExecutionPoint() + } + } else { + ApplicationManager.getApplication().invokeLater { + val se = session + if (se is XDebugSessionImpl) + se.positionReached(LuaSuspendContext(stack), true) + else + se.positionReached(LuaSuspendContext(stack)) + session.showExecutionPoint() + } + } + return + } + } + + // file and source position not found, run it + run() + } + + protected abstract fun run() +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaDebuggerEditorsProvider.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaDebuggerEditorsProvider.kt new file mode 100644 index 0000000..c849642 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaDebuggerEditorsProvider.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.openapi.fileTypes.FileType +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.xdebugger.evaluation.XDebuggerEditorsProviderBase +import com.tang.intellij.lua.lang.LuaFileType +import com.tang.intellij.lua.psi.LuaExprCodeFragment + +/** + * + * Created by TangZX on 2016/12/30. + */ +class LuaDebuggerEditorsProvider : XDebuggerEditorsProviderBase() { + override fun createExpressionCodeFragment(project: Project, text: String, context: PsiElement?, isPhysical: Boolean): PsiFile { + val fragment = LuaExprCodeFragment(project,"fragment.lua", text, isPhysical) + fragment.context = context + return fragment + } + + override fun getFileType(): FileType { + return LuaFileType.INSTANCE + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaDebuggerEvaluator.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaDebuggerEvaluator.kt new file mode 100644 index 0000000..cdd38b6 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaDebuggerEvaluator.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.openapi.editor.Document +import com.intellij.openapi.project.IndexNotReadyException +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiNameIdentifierOwner +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.xdebugger.XSourcePosition +import com.intellij.xdebugger.evaluation.XDebuggerEvaluator +import com.tang.intellij.lua.psi.* + +/** + * + * Created by tangzx on 2017/5/1. + */ +abstract class LuaDebuggerEvaluator : XDebuggerEvaluator() { + override fun getExpressionRangeAtOffset(project: Project, document: Document, offset: Int, sideEffectsAllowed: Boolean): TextRange? { + var currentRange: TextRange? = null + PsiDocumentManager.getInstance(project).commitAndRunReadAction { + try { + val file = PsiDocumentManager.getInstance(project).getPsiFile(document) ?: return@commitAndRunReadAction + if (currentRange == null) { + val ele = file.findElementAt(offset) + if (ele != null && ele.node.elementType == LuaTypes.ID) { + val parent = ele.parent + when (parent) { + is LuaFuncDef, + is LuaLocalFuncDef -> currentRange = ele.textRange + is LuaClassMethodName, + is PsiNameIdentifierOwner -> currentRange = parent.textRange + } + } + } + + if (currentRange == null) { + val expr = PsiTreeUtil.findElementOfClassAtOffset(file, offset, LuaExpr::class.java, false) + currentRange = when (expr) { + is LuaCallExpr, + is LuaClosureExpr, + is LuaLiteralExpr -> null + else -> expr?.textRange + } + } + } catch (ignored: IndexNotReadyException) { + } + } + return currentRange + } + + override fun evaluate(express: String, xEvaluationCallback: XDebuggerEvaluator.XEvaluationCallback, xSourcePosition: XSourcePosition?) { + var expr = express.trim() + if (!expr.endsWith(')')) { + val lastDot = express.lastIndexOf('.') + val lastColon = express.lastIndexOf(':') + if (lastColon > lastDot) // a:xx -> a.xx + expr = expr.replaceRange(lastColon, lastColon + 1, ".") + } + eval(expr, xEvaluationCallback, xSourcePosition) + } + + protected abstract fun eval(express: String, xEvaluationCallback: XDebuggerEvaluator.XEvaluationCallback, xSourcePosition: XSourcePosition?) +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaExecutionStack.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaExecutionStack.kt new file mode 100644 index 0000000..7410224 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaExecutionStack.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.xdebugger.frame.XExecutionStack +import com.intellij.xdebugger.frame.XStackFrame +import com.intellij.xdebugger.impl.frame.XStackFrameContainerEx + +/** + * + * Created by tangzx on 2016/12/31. + */ +class LuaExecutionStack(private val stackFrameList: List) : XExecutionStack("LuaStack") { + private var _topFrame: XStackFrame? = null + + val stackFrames: Array + get() = stackFrameList.toTypedArray() + + init { + if (stackFrameList.isNotEmpty()) + _topFrame = stackFrameList[0] + } + + override fun getTopFrame() = _topFrame + + fun setTopFrame(frame: XStackFrame) { + _topFrame = frame + } + + override fun computeStackFrames(i: Int, xStackFrameContainer: XExecutionStack.XStackFrameContainer) { + val stackFrameContainerEx = xStackFrameContainer as XStackFrameContainerEx + stackFrameContainerEx.addStackFrames(stackFrameList, topFrame, true) + } +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaLineBreakpointType.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaLineBreakpointType.kt new file mode 100644 index 0000000..de79043 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaLineBreakpointType.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.xdebugger.breakpoints.XLineBreakpointTypeBase +import com.tang.intellij.lua.lang.LuaFileType + +/** + * + * Created by tangzx on 2016/12/30. + */ +class LuaLineBreakpointType : XLineBreakpointTypeBase(ID, NAME, LuaDebuggerEditorsProvider()) { + + override fun canPutAt(file: VirtualFile, line: Int, project: Project): Boolean { + return file.fileType === LuaFileType.INSTANCE + } + + companion object { + + private const val ID = "lua-line" + private const val NAME = "lua-line-breakpoint" + } +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaRunConfiguration.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaRunConfiguration.kt new file mode 100644 index 0000000..08798cc --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaRunConfiguration.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.execution.configuration.AbstractRunConfiguration +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.configurations.RuntimeConfigurationError +import com.intellij.execution.configurations.RuntimeConfigurationException +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModuleRootManager + +/** + * + * Created by tangzx on 2017/6/4. + */ +abstract class LuaRunConfiguration(project: Project, factory: ConfigurationFactory) : AbstractRunConfiguration(project, factory) { + @Throws(RuntimeConfigurationException::class) + protected fun checkSourceRoot() { + var sourceRootExist = false + val modules = ModuleManager.getInstance(project).modules + for (module in modules) { + val sourceRoots = ModuleRootManager.getInstance(module).sourceRoots + if (sourceRoots.isNotEmpty()) { + sourceRootExist = true + break + } + } + + if (!sourceRootExist) { + throw RuntimeConfigurationError("Sources root not found.") + } + } + + open fun createCommandLine(): GeneralCommandLine? { + return null + } +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaRunner.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaRunner.kt new file mode 100644 index 0000000..a47d8d5 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaRunner.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.execution.configurations.RunProfile +import com.intellij.execution.configurations.RunnerSettings +import com.intellij.execution.executors.DefaultDebugExecutor +import com.intellij.execution.executors.DefaultRunExecutor +import com.intellij.execution.runners.GenericProgramRunner + +/** + * lua runner + * Created by tangzx on 2017/6/10. + */ +abstract class LuaRunner : GenericProgramRunner() { + override fun canRun(executorId: String, runProfile: RunProfile): Boolean { + return DefaultDebugExecutor.EXECUTOR_ID == executorId || DefaultRunExecutor.EXECUTOR_ID == executorId + } +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaSuspendContext.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaSuspendContext.kt new file mode 100644 index 0000000..ce40974 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaSuspendContext.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.xdebugger.frame.XSuspendContext + +/** + * + * Created by tangzx on 2016/12/31. + */ +class LuaSuspendContext(private val active: LuaExecutionStack) : XSuspendContext() { + + override fun getActiveExecutionStack() = active +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/LuaTracebackFilter.kt b/src/main/java/com/tang/intellij/lua/debugger/LuaTracebackFilter.kt new file mode 100644 index 0000000..2bd6c46 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/LuaTracebackFilter.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.execution.filters.Filter +import com.intellij.execution.filters.OpenFileHyperlinkInfo +import com.intellij.openapi.project.Project +import com.tang.intellij.lua.psi.LuaFileUtil +import java.util.regex.Pattern + +/** + * + * Created by tangzx on 2017/6/10. + */ +class LuaTracebackFilter(private val project: Project) : Filter { + + override fun applyFilter(line: String, entireLength: Int): Filter.Result? { + //lua.exe: Test.lua:3: attempt to call global 'print1' (a nil value) + //stack traceback: + //Test.lua:3: in function 'a' + //Test.lua:7: in function 'b' + //Test.lua:11: in main chunk + + val pattern = Pattern.compile("\\s*((/+)?[^<>\\\\|:\"*? ]+):(\\d+):") + val matcher = pattern.matcher(line) + if (matcher.find()) { + val fileName = matcher.group(1) + val lineNumber = Integer.parseInt(matcher.group(3)) + val file = LuaFileUtil.findFile(project, fileName) + if (file != null) { + val hyperlink = OpenFileHyperlinkInfo(project, file, lineNumber - 1) + val textStartOffset = entireLength - line.length + val startPos = matcher.start(1) + val endPos = matcher.end(3) + 1 + return Filter.Result(startPos + textStartOffset, endPos + textStartOffset, hyperlink) + } + } + return null + } +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/XValuePresentation.kt b/src/main/java/com/tang/intellij/lua/debugger/XValuePresentation.kt new file mode 100644 index 0000000..9318c2e --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/XValuePresentation.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger + +import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.xdebugger.frame.presentation.XNumericValuePresentation +import com.intellij.xdebugger.frame.presentation.XStringValuePresentation +import com.intellij.xdebugger.frame.presentation.XValuePresentation +import com.tang.intellij.lua.highlighting.LuaHighlightingData + +open class LuaXValuePresentation(val sType: String, val sValue:String, val tkey : TextAttributesKey? = null) : XValuePresentation() { + override fun renderValue(renderer: XValueTextRenderer) { + if (tkey == null) renderer.renderValue(sValue) + else renderer.renderValue(sValue, tkey) + } + + override fun getType() = sType +} + +class LuaXStringPresentation(sValue: String) : XStringValuePresentation(sValue) { + override fun getType() = "string" +} + +class LuaXNumberPresentation(sValue: String) : XNumericValuePresentation(sValue) { + override fun getType() = "number" +} + +class LuaXBoolPresentation(sValue: String) : LuaXValuePresentation("boolean", sValue, LuaHighlightingData.PRIMITIVE_TYPE) \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugConfigurationType.kt b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugConfigurationType.kt new file mode 100644 index 0000000..2561822 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugConfigurationType.kt @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger.emmy + +import com.intellij.execution.Executor +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.execution.configurations.ConfigurationType +import com.intellij.execution.configurations.RunConfiguration +import com.intellij.execution.configurations.RunProfileState +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.runners.RunConfigurationWithSuppressedDefaultRunAction +import com.intellij.openapi.module.Module +import com.intellij.openapi.options.SettingsEditor +import com.intellij.openapi.options.SettingsEditorGroup +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.JDOMExternalizerUtil +import com.tang.intellij.lua.debugger.LuaCommandLineState +import com.tang.intellij.lua.debugger.LuaConfigurationFactory +import com.tang.intellij.lua.debugger.LuaRunConfiguration +import com.tang.intellij.lua.lang.LuaIcons +import org.jdom.Element +import javax.swing.Icon + +class EmmyDebugConfigurationType : ConfigurationType { + override fun getIcon(): Icon { + return LuaIcons.FILE + } + + override fun getConfigurationTypeDescription(): String { + return "Emmy Debugger(NEW)" + } + + override fun getId(): String { + return "lua.emmy.debugger" + } + + override fun getDisplayName(): String { + return "Emmy Debugger(NEW)" + } + + override fun getConfigurationFactories(): Array { + return arrayOf(EmmyDebuggerConfigurationFactory(this)) + } +} + +enum class EmmyDebugTransportType(val desc: String) { + TCP_CLIENT("Tcp ( IDE connect debugger )"), + TCP_SERVER("Tcp ( Debugger connect IDE )"); + + override fun toString(): String { + return desc + } +} + +enum class EmmyWinArch(val desc: String) { + X86("x86"), + X64("x64"); + + override fun toString(): String { + return desc; + } +} + +class EmmyDebuggerConfigurationFactory(val type: EmmyDebugConfigurationType) : LuaConfigurationFactory(type) { + override fun createTemplateConfiguration(project: Project): RunConfiguration { + return EmmyDebugConfiguration(project, this) + } +} + +class EmmyDebugConfiguration(project: Project, factory: EmmyDebuggerConfigurationFactory) : LuaRunConfiguration(project, factory), RunConfigurationWithSuppressedDefaultRunAction { + var type = EmmyDebugTransportType.TCP_CLIENT + + var host = "localhost" + var port = 9966 + var winArch = EmmyWinArch.X64 + var pipeName = "emmy" + + override fun getConfigurationEditor(): SettingsEditor { + val group = SettingsEditorGroup() + group.addEditor("emmy", EmmyDebugSettingsPanel(project)) + return group + } + + override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState { + return LuaCommandLineState(environment) + } + + override fun getValidModules(): Collection { + return emptyList() + } + + override fun writeExternal(element: Element) { + super.writeExternal(element) + JDOMExternalizerUtil.writeField(element, "TYPE", type.ordinal.toString()) + JDOMExternalizerUtil.writeField(element, "HOST", host) + JDOMExternalizerUtil.writeField(element, "PORT", port.toString()) + JDOMExternalizerUtil.writeField(element, "PIPE", pipeName) + JDOMExternalizerUtil.writeField(element, "WIN_ARCH", winArch.ordinal.toString()) + } + + override fun readExternal(element: Element) { + super.readExternal(element) + JDOMExternalizerUtil.readField(element, "HOST")?.let { + host = it + } + JDOMExternalizerUtil.readField(element, "PORT")?.let { + port = it.toInt() + } + JDOMExternalizerUtil.readField(element, "PIPE")?.let { + pipeName = it + } + JDOMExternalizerUtil.readField(element, "TYPE")?.let { value -> + val i = value.toInt() + type = EmmyDebugTransportType.values().find { it.ordinal == i } ?: EmmyDebugTransportType.TCP_SERVER + } + JDOMExternalizerUtil.readField(element, "WIN_ARCH")?.let { value -> + val i = value.toInt() + winArch = EmmyWinArch.values().find { it.ordinal == i } ?: EmmyWinArch.X64 + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugProcess.kt b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugProcess.kt new file mode 100644 index 0000000..7b63c09 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugProcess.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger.emmy + +import com.intellij.xdebugger.XDebugSession + +interface IEvalResultHandler { + fun handleMessage(msg: EvalRsp) +} + +open class EmmyDebugProcess(session: XDebugSession) : EmmyDebugProcessBase(session), ITransportHandler { + private val configuration = session.runProfile as EmmyDebugConfiguration + + override fun setupTransporter() { + val transporter: Transporter = when (configuration.type) { + EmmyDebugTransportType.TCP_CLIENT -> SocketClientTransporter(configuration.host, configuration.port) + EmmyDebugTransportType.TCP_SERVER -> SocketServerTransporter(configuration.host, configuration.port) + } + transporter.handler = this + transporter.logger = this + this.transporter = transporter + try { + transporter.start() + } catch (e: Exception) { + this.error(e.localizedMessage) + this.onDisconnect() + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugProcessBase.kt b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugProcessBase.kt new file mode 100644 index 0000000..69fba87 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugProcessBase.kt @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger.emmy + +import com.google.gson.Gson +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.util.Key +import com.intellij.xdebugger.XDebugSession +import com.intellij.xdebugger.XDebuggerManager +import com.intellij.xdebugger.XSourcePosition +import com.intellij.xdebugger.breakpoints.XLineBreakpoint +import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider +import com.intellij.xdebugger.frame.XSuspendContext +import com.intellij.xdebugger.impl.XDebugSessionImpl +import com.tang.intellij.lua.debugger.* +import com.tang.intellij.lua.psi.LuaFileManager +import com.tang.intellij.lua.psi.LuaFileUtil +import java.io.File + +abstract class EmmyDebugProcessBase(session: XDebugSession) : LuaDebugProcess(session), ITransportHandler { + private val editorsProvider = LuaDebuggerEditorsProvider() + private val evalHandlers = mutableListOf() + private val breakpoints = mutableMapOf() + private var idCounter = 0; + protected var transporter: Transporter? = null + + companion object { + private val ID = Key.create("lua.breakpoint") + } + + override fun sessionInitialized() { + super.sessionInitialized() + ApplicationManager.getApplication().executeOnPooledThread { + setupTransporter() + } + } + + protected abstract fun setupTransporter() + + private fun sendInitReq() { + // send init + val path = LuaFileUtil.getPluginVirtualFile("debugger/emmy/emmyHelper.lua") + if (path != null) { + val code = File(path).readText() + val extList = LuaFileManager.extensions + transporter?.send(InitMessage(code, extList)) + } + // send bps + val breakpoints = XDebuggerManager.getInstance(session.project) + .breakpointManager + .getBreakpoints(LuaLineBreakpointType::class.java) + breakpoints.forEach { breakpoint -> + breakpoint.sourcePosition?.let { position -> + registerBreakpoint(position, breakpoint) + } + } + // send ready + transporter?.send(Message(MessageCMD.ReadyReq)) + } + + override fun onConnect(suc: Boolean) { + if (suc) { + ApplicationManager.getApplication().runReadAction { + sendInitReq() + } + } else stop() + } + + override fun onDisconnect() { + stop() + session?.stop() + } + + override fun onReceiveMessage(cmd: MessageCMD, json: String) { + when (cmd) { + MessageCMD.BreakNotify -> { + val data = Gson().fromJson(json, BreakNotify::class.java) + onBreak(data) + } + + MessageCMD.EvalRsp -> { + val rsp = Gson().fromJson(json, EvalRsp::class.java) + onEvalRsp(rsp) + } + + MessageCMD.LogNotify -> { + val notify = Gson().fromJson(json, LogNotify::class.java) + println(notify.message, LogConsoleType.NORMAL, ConsoleViewContentType.SYSTEM_OUTPUT) + } + + else -> { + println("Unknown message: $cmd") + } + } + } + + override fun registerBreakpoint(sourcePosition: XSourcePosition, breakpoint: XLineBreakpoint<*>) { + val file = sourcePosition.file + val shortPath = file.canonicalPath + if (shortPath != null) { + val newId = idCounter++ + breakpoint.putUserData(ID, newId) + + if (breakpoint.isLogMessage) { + breakpoints[newId] = + BreakPoint(shortPath, breakpoint.line + 1, null, breakpoint.logExpressionObject?.expression) + } else { + breakpoints[newId] = + BreakPoint(shortPath, breakpoint.line + 1, breakpoint.conditionExpression?.expression) + } + val bp = breakpoints.getOrDefault(newId, null) + if (bp != null) { + send(AddBreakPointReq(listOf(bp))) + } + } + } + + override fun unregisterBreakpoint(sourcePosition: XSourcePosition, breakpoint: XLineBreakpoint<*>) { + val file = sourcePosition.file + val shortPath = file.canonicalPath + if (shortPath != null) { + val id = breakpoint.getUserData(ID) + val bp = breakpoints.getOrDefault(id, null) + if (bp != null) { + breakpoints.remove(id) + send(RemoveBreakPointReq(listOf(bp))) + } + } + } + + override fun startPausing() { + send(DebugActionMessage(DebugAction.Break)) + } + + override fun runToPosition(position: XSourcePosition, context: XSuspendContext?) { + send(AddBreakPointReq(listOf(BreakPoint(position.file.path, position.line + 1, null, null, null)))) + } + + private fun onBreak(data: BreakNotify) { + evalHandlers.clear() + val frames = data.stacks.map { EmmyDebugStackFrame(it, this) } + val top = frames.firstOrNull { it.sourcePosition != null } + ?: frames.firstOrNull { it.data.line > 0 } + ?: frames.firstOrNull() + val stack = LuaExecutionStack(frames) + if (top != null) + stack.setTopFrame(top) + val breakpoint = top?.sourcePosition?.let { getBreakpoint(it.file, it.line) } + if (breakpoint != null) { + ApplicationManager.getApplication().invokeLater { + session.breakpointReached(breakpoint, null, LuaSuspendContext(stack)) + session.showExecutionPoint() + } + } else { + ApplicationManager.getApplication().invokeLater { + val se = session + if (se is XDebugSessionImpl) + se.positionReached(LuaSuspendContext(stack), true) + else + se.positionReached(LuaSuspendContext(stack)) + session.showExecutionPoint() + } + } + } + + private fun onEvalRsp(rsp: EvalRsp) { + evalHandlers.forEach { it.handleMessage(rsp) } + } + + override fun run() { + send(DebugActionMessage(DebugAction.Continue)) + } + + override fun stop() { + send(DebugActionMessage(DebugAction.Stop)) + send(StopSign()) + transporter?.close() + transporter = null + } + + override fun startStepOver(context: XSuspendContext?) { + send(DebugActionMessage(DebugAction.StepOver)) + } + + override fun startStepInto(context: XSuspendContext?) { + send(DebugActionMessage(DebugAction.StepIn)) + } + + override fun startStepOut(context: XSuspendContext?) { + send(DebugActionMessage(DebugAction.StepOut)) + } + + override fun getEditorsProvider(): XDebuggerEditorsProvider { + return editorsProvider + } + + fun addEvalResultHandler(handler: IEvalResultHandler) { + evalHandlers.add(handler) + } + + fun removeMessageHandler(handler: IEvalResultHandler) { + evalHandlers.remove(handler) + } + + fun send(msg: IMessage) { + transporter?.send(msg) + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugSettingsPanel.form b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugSettingsPanel.form new file mode 100644 index 0000000..02134bd --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugSettingsPanel.form @@ -0,0 +1,146 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugSettingsPanel.java b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugSettingsPanel.java new file mode 100644 index 0000000..8dbf736 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugSettingsPanel.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger.emmy; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.SystemInfoRt; +import com.tang.intellij.lua.lang.LuaFileType; +import com.tang.intellij.lua.psi.LuaFileUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.PlainDocument; +import java.awt.*; +import java.util.Objects; + +public class EmmyDebugSettingsPanel extends SettingsEditor implements DocumentListener { + private JComboBox typeCombox; + private JLabel type; + private JTextField tcpHostInput; + private JTextField tcpPortInput; + private JLabel tcpHostLabel; + private JLabel tcpPortLabel; + private JTextField pipelineInput; + private JLabel pipeNameLabel; + private JPanel panel; + private JPanel codePanel; + private JCheckBox waitIDECheckBox; + private JCheckBox breakWhenIDEConnectedCheckBox; + + private JRadioButton x64RadioButton; + private JRadioButton x86RadioButton; + private JPanel winArchPanel; + private ButtonGroup winArchGroup; + + private EditorEx editorEx; + + public EmmyDebugSettingsPanel(Project project) { + // type + DefaultComboBoxModel model = new DefaultComboBoxModel<>(); + model.addElement(EmmyDebugTransportType.TCP_CLIENT); + model.addElement(EmmyDebugTransportType.TCP_SERVER); + /*for (EmmyDebugTransportType value : EmmyDebugTransportType.values()) { + model.addElement(value); + }*/ + typeCombox.addActionListener(e -> { + setType((EmmyDebugTransportType) typeCombox.getSelectedItem()); + onChanged(); + }); + typeCombox.setModel(model); + // tcp + tcpHostInput.setText("localhost"); + tcpHostInput.getDocument().addDocumentListener(this); + tcpPortInput.setText("9966"); + tcpPortInput.setDocument(new IntegerDocument()); + tcpPortInput.getDocument().addDocumentListener(this); + // pipe + pipelineInput.setText("emmylua"); + pipelineInput.getDocument().addDocumentListener(this); + + waitIDECheckBox.addActionListener(e -> onChanged()); + breakWhenIDEConnectedCheckBox.addActionListener(e -> onChanged()); + + // arch + winArchGroup = new ButtonGroup(); + winArchPanel.setVisible(SystemInfoRt.isWindows); + winArchGroup.add(x64RadioButton); + winArchGroup.add(x86RadioButton); + x64RadioButton.addChangeListener(e -> onChanged()); + x86RadioButton.addChangeListener(e -> onChanged()); + + // editor + editorEx = createEditorEx(project); + codePanel.add(editorEx.getComponent(), BorderLayout.CENTER); + + updateCode(); + } + + private void onChanged() { + if (isClient()) { + breakWhenIDEConnectedCheckBox.setEnabled(waitIDECheckBox.isSelected()); + } else { + breakWhenIDEConnectedCheckBox.setEnabled(true); + } + fireEditorStateChanged(); + updateCode(); + } + + @Override + protected void resetEditorFrom(@NotNull EmmyDebugConfiguration configuration) { + typeCombox.setSelectedItem(configuration.getType()); + setType(configuration.getType()); + + tcpHostInput.setText(configuration.getHost()); + tcpPortInput.setText(String.valueOf(configuration.getPort())); + + pipelineInput.setText(configuration.getPipeName()); + + if (SystemInfoRt.isWindows) { + if (configuration.getWinArch() == EmmyWinArch.X64) { + x64RadioButton.setSelected(true); + } else { + x86RadioButton.setSelected(true); + } + } + } + + @Override + protected void applyEditorTo(@NotNull EmmyDebugConfiguration configuration) { + EmmyDebugTransportType type = (EmmyDebugTransportType) typeCombox.getSelectedItem(); + assert type != null; + configuration.setType(type); + + configuration.setHost(tcpHostInput.getText()); + configuration.setPort(Integer.parseInt(tcpPortInput.getText())); + + configuration.setPipeName(pipelineInput.getText()); + if (SystemInfoRt.isWindows) { + configuration.setWinArch(x64RadioButton.isSelected() ? EmmyWinArch.X64 : EmmyWinArch.X86); + } + } + + protected void setType(EmmyDebugTransportType type) { + boolean isTCP = type == EmmyDebugTransportType.TCP_CLIENT || type == EmmyDebugTransportType.TCP_SERVER; + tcpHostLabel.setVisible(isTCP); + tcpPortLabel.setVisible(isTCP); + tcpHostInput.setVisible(isTCP); + tcpPortInput.setVisible(isTCP); + + pipeNameLabel.setVisible(!isTCP); + pipelineInput.setVisible(!isTCP); + + waitIDECheckBox.setVisible(isClient()); + } + + private boolean isClient() { + EmmyDebugTransportType type = getType(); + return type == EmmyDebugTransportType.TCP_CLIENT || type == EmmyDebugTransportType.PIPE_CLIENT; + } + + private EmmyDebugTransportType getType() { + return (EmmyDebugTransportType) typeCombox.getSelectedItem(); + } + + private String getHost() { + return tcpHostInput.getText(); + } + + private int getPort() { + int port = 0; + try { + port = Integer.parseInt(tcpPortInput.getText()); + } catch (Exception ignored) { + } + return port; + } + + private String getPipeName() { + return pipelineInput.getText(); + } + + @NotNull + @Override + protected JComponent createEditor() { + return panel; + } + + private EditorEx createEditorEx(Project project) { + EditorFactory editorFactory = EditorFactory.getInstance(); + Document editorDocument = editorFactory.createDocument(""); + return (EditorEx)editorFactory.createEditor(editorDocument, project, LuaFileType.INSTANCE, false); + } + + private void updateCode() { + ApplicationManager.getApplication().runWriteAction(this::updateCodeImpl); + } + + private String getDebuggerFolder() { + if (SystemInfoRt.isWindows) + return LuaFileUtil.INSTANCE.getPluginVirtualFile("debugger/emmy/windows"); + if (SystemInfoRt.isMac) + return LuaFileUtil.INSTANCE.getPluginVirtualFile("debugger/emmy/mac"); + return LuaFileUtil.INSTANCE.getPluginVirtualFile("debugger/emmy/linux"); + } + + private void updateCodeImpl() { + StringBuilder sb = new StringBuilder(); + if (SystemInfoRt.isWindows) { + EmmyWinArch arch = x64RadioButton.isSelected() ? EmmyWinArch.X64 : EmmyWinArch.X86; + sb.append("package.cpath = package.cpath .. ';") + .append(getDebuggerFolder()) + .append("/") + .append(arch.getDesc()) + .append("/?.dll'\n"); + } else if (SystemInfoRt.isMac) { + sb.append("package.cpath = package.cpath .. ';") + .append(getDebuggerFolder()) + .append("/") + .append(Objects.equals(System.getProperty("os.arch"), "arm64") ? "arm64": "x64") + .append("/?.dylib'\n"); + } else { + sb.append("package.cpath = package.cpath .. ';") + .append(getDebuggerFolder()) + .append("/?.so'\n"); + } + sb.append("local dbg = require('emmy_core')\n"); + EmmyDebugTransportType type = getType(); + if (type == EmmyDebugTransportType.PIPE_CLIENT) { + sb.append("dbg.pipeListen('").append(getPipeName()).append("')\n"); + } + else if (type == EmmyDebugTransportType.PIPE_SERVER) { + sb.append("dbg.pipeConnect('").append(getPipeName()).append("')\n"); + } + else if (type == EmmyDebugTransportType.TCP_CLIENT) { + sb.append("dbg.tcpListen('").append(getHost()).append("', ").append(getPort()).append(")\n"); + } + else if (type == EmmyDebugTransportType.TCP_SERVER) { + sb.append("dbg.tcpConnect('").append(getHost()).append("', ").append(getPort()).append(")\n"); + } + + if (isClient()) { + if (waitIDECheckBox.isSelected()) { + sb.append("dbg.waitIDE()\n"); + if (breakWhenIDEConnectedCheckBox.isSelected()) { + sb.append("dbg.breakHere()\n"); + } + } + } else { + if (breakWhenIDEConnectedCheckBox.isSelected()) { + sb.append("dbg.breakHere()\n"); + } + } + editorEx.getDocument().setText(sb.toString()); + } + + @Override + public void insertUpdate(DocumentEvent e) { + onChanged(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + onChanged(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + onChanged(); + } + + static class IntegerDocument extends PlainDocument { + public void insertString(int offset, String s, AttributeSet attributeSet) throws BadLocationException { + try { + Integer.parseInt(s); + } catch (Exception ex) { + return; + } + super.insertString(offset, s, attributeSet); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugStackFrame.kt b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugStackFrame.kt new file mode 100644 index 0000000..246411d --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebugStackFrame.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger.emmy + +import com.intellij.ui.ColoredTextContainer +import com.intellij.ui.SimpleTextAttributes +import com.intellij.xdebugger.XSourcePosition +import com.intellij.xdebugger.frame.XCompositeNode +import com.intellij.xdebugger.frame.XStackFrame +import com.intellij.xdebugger.frame.XValueChildrenList +import com.intellij.xdebugger.impl.XSourcePositionImpl +import com.tang.intellij.lua.debugger.emmy.value.LuaXValue +import com.tang.intellij.lua.psi.LuaFileUtil + +class EmmyDebugStackFrame(val data: Stack, val process: EmmyDebugProcessBase) : XStackFrame() { + private val values = XValueChildrenList() + private var evaluator: EmmyEvaluator? = null + private val sourcePosition by lazy { + val file = LuaFileUtil.findFile(process.session.project, data.file) + if (file == null) null else XSourcePositionImpl.create(file, data.line - 1) + } + + init { + data.localVariables.forEach { + addValue(LuaXValue.create(it, this)) + } + data.upvalueVariables.forEach { + addValue(LuaXValue.create(it, this)) + } + } + + override fun getEvaluator(): EmmyEvaluator? { + if (evaluator == null) + evaluator = EmmyEvaluator(this, process) + return evaluator + } + + override fun customizePresentation(component: ColoredTextContainer) { + component.append("${data.file}:${data.functionName}:${data.line}", SimpleTextAttributes.REGULAR_ATTRIBUTES) + } + + private fun addValue(node: LuaXValue) { + values.add(node.name, node) + } + + override fun computeChildren(node: XCompositeNode) { + node.addChildren(values, true) + } + + override fun getSourcePosition(): XSourcePosition? { + return sourcePosition + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebuggerRunner.kt b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebuggerRunner.kt new file mode 100644 index 0000000..29e0dba --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyDebuggerRunner.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger.emmy + +import com.intellij.execution.configurations.RunProfile +import com.intellij.execution.configurations.RunProfileState +import com.intellij.execution.executors.DefaultDebugExecutor +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.ui.RunContentDescriptor +import com.intellij.xdebugger.XDebugProcess +import com.intellij.xdebugger.XDebugProcessStarter +import com.intellij.xdebugger.XDebugSession +import com.intellij.xdebugger.XDebuggerManager +import com.tang.intellij.lua.debugger.LuaRunner + +class EmmyDebuggerRunner : LuaRunner() { + companion object { + const val ID = "lua.emmy.runner" + } + override fun getRunnerId() = ID + + override fun canRun(executorId: String, runProfile: RunProfile): Boolean { + return DefaultDebugExecutor.EXECUTOR_ID == executorId && runProfile is EmmyDebugConfiguration + } + + override fun doExecute(state: RunProfileState, environment: ExecutionEnvironment): RunContentDescriptor { + val manager = XDebuggerManager.getInstance(environment.project) + val session = manager.startSession(environment, object : XDebugProcessStarter() { + override fun start(session: XDebugSession): XDebugProcess { + return EmmyDebugProcess(session) + } + }) + return session.runContentDescriptor + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyEvaluator.kt b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyEvaluator.kt new file mode 100644 index 0000000..1e7f2ba --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/EmmyEvaluator.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger.emmy + +import com.intellij.xdebugger.XSourcePosition +import com.tang.intellij.lua.debugger.LuaDebuggerEvaluator +import com.tang.intellij.lua.debugger.emmy.value.LuaXValue + +class EmmyEvaluator(val frame: EmmyDebugStackFrame, val process: EmmyDebugProcessBase) : LuaDebuggerEvaluator(), IEvalResultHandler { + + private val callbackMap = mutableMapOf() + + init { + process.addEvalResultHandler(this) + } + + override fun handleMessage(msg: EvalRsp) { + val callback = callbackMap[msg.seq] + if (callback != null) { + if (msg.success) + callback.evaluated(LuaXValue.create(msg.value!!, frame)) + else + callback.errorOccurred(msg.error ?: "unknown error") + callbackMap.remove(msg.seq) + } + } + + fun eval(express: String, cacheId: Int, xEvaluationCallback: XEvaluationCallback, depth: Int = 1) { + val req = EvalReq(express, frame.data.level, cacheId, depth) + process.send(req) + callbackMap[req.seq] = xEvaluationCallback + } + + override fun eval(express: String, xEvaluationCallback: XEvaluationCallback, xSourcePosition: XSourcePosition?) { + eval(express, 0, xEvaluationCallback) + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/Transporter.kt b/src/main/java/com/tang/intellij/lua/debugger/emmy/Transporter.kt new file mode 100644 index 0000000..a609456 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/Transporter.kt @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger.emmy + +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.openapi.application.ApplicationManager +import com.tang.intellij.lua.debugger.DebugLogger +import com.tang.intellij.lua.debugger.LogConsoleType +import java.io.* +import java.net.InetAddress +import java.net.InetSocketAddress +import java.nio.ByteBuffer +import java.nio.channels.ServerSocketChannel +import java.nio.channels.SocketChannel +import java.util.concurrent.LinkedBlockingQueue + +class StopSign : Message(MessageCMD.Unknown) + +interface ITransportHandler { + fun onReceiveMessage(cmd: MessageCMD, json: String) + fun onDisconnect() + fun onConnect(suc: Boolean) +} + +abstract class Transporter { + + var handler: ITransportHandler? = null + + var logger: DebugLogger? = null + + protected val messageQueue = LinkedBlockingQueue() + + protected var stopped = false + + open fun start() { + + } + + open fun close() { + stopped = true + } + + fun send(msg: IMessage) { + messageQueue.put(msg) + } + + protected fun onConnect(suc: Boolean) { + handler?.onConnect(suc) + if (suc) { + logger?.println("Connected.", LogConsoleType.NORMAL, ConsoleViewContentType.SYSTEM_OUTPUT) + } + } + + protected open fun onDisconnect() { + logger?.println("Disconnected.", LogConsoleType.NORMAL, ConsoleViewContentType.SYSTEM_OUTPUT) + } + + protected fun onReceiveMessage(type: MessageCMD, json: String) { + try { + handler?.onReceiveMessage(type, json) + } catch (e: Exception) { + println(e) + } + } +} + +abstract class SocketChannelTransporter : Transporter() { + + protected var socket: SocketChannel? = null + + protected fun run() { + ApplicationManager.getApplication().executeOnPooledThread { + doReceive() + } + ApplicationManager.getApplication().executeOnPooledThread { + doSend() + } + } + + protected open fun getInputStream(): InputStream? { + return socket?.socket()?.getInputStream() + } + + protected open fun write(ba: ByteArray) { + socket?.write(ByteBuffer.wrap(ba)) + } + + private fun doReceive() { + val iss = getInputStream() ?: return + val reader = BufferedReader(InputStreamReader(iss, "UTF-8")) + while (true) { + try { + val cmdValue = reader.readLine() + val cmd = cmdValue.toInt() + val json = reader.readLine() + val type = MessageCMD.values().find { it.ordinal == cmd } + onReceiveMessage(type ?: MessageCMD.Unknown, json) + } catch (e: Exception) { + onDisconnect() + break + } + } + send(StopSign()) + println(">>> stop receive") + } + + private fun doSend() { + while(true) { + val msg = messageQueue.take() + if (msg is StopSign) + break + try { + val json = msg.toJSON() + write("${msg.cmd}\n$json\n".toByteArray()) + } catch (e: IOException) { + break + } + } + println(">>> stop send") + } + + override fun close() { + super.close() + socket?.close() + socket = null + } + + override fun onDisconnect() { + super.onDisconnect() + socket = null + } +} + +class SocketClientTransporter(val host: String, val port: Int) : SocketChannelTransporter() { + + private var server: SocketChannel? = null + + override fun start() { + logger?.println("Try connect $host:$port ...", LogConsoleType.NORMAL, ConsoleViewContentType.SYSTEM_OUTPUT) + val server = SocketChannel.open() + val address = InetAddress.getByName(host) + var connected = false + if (server.connect(InetSocketAddress(address,port))) { + this.server = server + this.socket = server + run() + connected = true + } + onConnect(connected) + } + + override fun onDisconnect() { + super.onDisconnect() + handler?.onDisconnect() + } +} + +class SocketServerTransporter(val host: String, val port: Int) : SocketChannelTransporter() { + private var server = ServerSocketChannel.open() + + override fun start() { + server.bind(InetSocketAddress(InetAddress.getByName(host), port)) + logger?.println("Server($host:$port) open successfully, wait for connection...", LogConsoleType.NORMAL, ConsoleViewContentType.SYSTEM_OUTPUT) + ApplicationManager.getApplication().executeOnPooledThread { + while (!stopped) { + val channel = try { + server.accept() + } catch (e: Exception) { + continue + } + if (socket != null) { + try { + channel.close() + } catch (e: Exception) { + e.printStackTrace() + } + } else { + socket = channel + run() + onConnect(true) + } + } + } + } + + override fun close() { + super.close() + server.close() + } +} diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/proto.kt b/src/main/java/com/tang/intellij/lua/debugger/emmy/proto.kt new file mode 100644 index 0000000..d6332b6 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/proto.kt @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("MemberVisibilityCanBePrivate", "UNUSED_PARAMETER") + +package com.tang.intellij.lua.debugger.emmy + +import com.google.gson.Gson + +enum class MessageCMD { + Unknown, + + InitReq, + InitRsp, + + ReadyReq, + ReadyRsp, + + AddBreakPointReq, + AddBreakPointRsp, + + RemoveBreakPointReq, + RemoveBreakPointRsp, + + ActionReq, + ActionRsp, + + EvalReq, + EvalRsp, + + // lua -> ide + BreakNotify, + AttachedNotify, + + StartHookReq, + StartHookRsp, + + LogNotify, +} + +interface IMessage { + val cmd: Int + fun toJSON(): String +} + +open class Message(cmdName: MessageCMD) : IMessage { + override val cmd = cmdName.ordinal + + override fun toJSON(): String { + return Gson().toJson(this) + } + + companion object { + private var seqCount = 0 + + fun makeSeq(): Int { + return seqCount++ + } + } +} + +class InitMessage(val emmyHelper: String, val ext: Array) : Message(MessageCMD.InitReq) + +enum class DebugAction { + Break, + Continue, + StepOver, + StepIn, + StepOut, + Stop, +} + +class DebugActionMessage(actionName: DebugAction) : Message(MessageCMD.ActionReq) { + val action = actionName.ordinal +} + +enum class LuaValueType { + TNIL, + TBOOLEAN, + TLIGHTUSERDATA, + TNUMBER, + TSTRING, + TTABLE, + TFUNCTION, + TUSERDATA, + TTHREAD, + + GROUP, +} + +class VariableValue(val name: String, + val nameType: Int, + val value: String, + val valueType: Int, + val valueTypeName: String, + val cacheId: Int, + val children: List?) { + val nameTypeValue: LuaValueType get() { + return LuaValueType.values().find { it.ordinal == nameType } ?: LuaValueType.TSTRING + } + + val nameValue: String get() { + if (nameTypeValue == LuaValueType.TSTRING) + return name + return "[$name]" + } + + val valueTypeValue: LuaValueType get() { + return LuaValueType.values().find { it.ordinal == valueType } ?: LuaValueType.TSTRING + } + + val fake: Boolean get() { + return valueTypeValue > LuaValueType.TTHREAD + } +} + +class Stack( + val file: String, + val line: Int, + val functionName: String, + val level: Int, + val localVariables: List, + val upvalueVariables: List +) + +class BreakNotify(val stacks: List) + +class EvalReq(val expr: String, val stackLevel: Int, val cacheId: Int, val depth: Int) : Message(MessageCMD.EvalReq) { + val seq = makeSeq() +} + +class EvalRsp(val seq: Int, val success: Boolean, val error: String?, val value: VariableValue?) + +class BreakPoint(val file: String, val line: Int, val condition: String? = null, val logMessage: String? = null, hitCondition: String? = null, runToHere: Boolean = false) + +class AddBreakPointReq(val breakPoints: List) : Message(MessageCMD.AddBreakPointReq) + +class RemoveBreakPointReq(val breakPoints: List) : Message(MessageCMD.RemoveBreakPointReq) + +class LogNotify(val type: Int, val message: String) + +class AttachedNotify(val state: Long) \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/debugger/emmy/value/LuaXValue.kt b/src/main/java/com/tang/intellij/lua/debugger/emmy/value/LuaXValue.kt new file mode 100644 index 0000000..9feabce --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/debugger/emmy/value/LuaXValue.kt @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.debugger.emmy.value + +import com.intellij.icons.AllIcons +import com.intellij.xdebugger.evaluation.XDebuggerEvaluator +import com.intellij.xdebugger.frame.* +import com.tang.intellij.lua.debugger.LuaXBoolPresentation +import com.tang.intellij.lua.debugger.LuaXNumberPresentation +import com.tang.intellij.lua.debugger.LuaXStringPresentation +import com.tang.intellij.lua.debugger.emmy.EmmyDebugStackFrame +import com.tang.intellij.lua.debugger.emmy.LuaValueType +import com.tang.intellij.lua.debugger.emmy.VariableValue +import com.tang.intellij.lua.lang.LuaIcons +import java.util.* + +abstract class LuaXValue(val value: VariableValue) : XValue() { + companion object { + fun create(v: VariableValue, frame: EmmyDebugStackFrame): LuaXValue { + return when(v.valueTypeValue) { + LuaValueType.TSTRING -> StringXValue(v) + LuaValueType.TNUMBER -> NumberXValue(v) + LuaValueType.TBOOLEAN -> BoolXValue(v) + LuaValueType.TUSERDATA, + LuaValueType.TTABLE -> TableXValue(v, frame) + LuaValueType.GROUP -> GroupXValue(v, frame) + else -> AnyXValue(v) + } + } + } + + val name: String get() { + return value.nameValue + } + + var parent: LuaXValue? = null +} + +private object VariableComparator : Comparator { + override fun compare(o1: VariableValue, o2: VariableValue): Int { + val w1 = if (o1.fake) 0 else 1 + val w2 = if (o2.fake) 0 else 1 + if (w1 != w2) + return w1.compareTo(w2) + return o1.nameValue.compareTo(o2.nameValue) + } +} + +class StringXValue(v: VariableValue) : LuaXValue(v) { + override fun computePresentation(xValueNode: XValueNode, place: XValuePlace) { + xValueNode.setPresentation(null, LuaXStringPresentation(value.value), false) + } +} + +class NumberXValue(v: VariableValue) : LuaXValue(v) { + override fun computePresentation(xValueNode: XValueNode, place: XValuePlace) { + xValueNode.setPresentation(null, LuaXNumberPresentation(value.value), false) + } +} + +class BoolXValue(val v: VariableValue) : LuaXValue(v) { + override fun computePresentation(xValueNode: XValueNode, place: XValuePlace) { + xValueNode.setPresentation(null, LuaXBoolPresentation(v.value), false) + } +} + +class AnyXValue(val v: VariableValue) : LuaXValue(v) { + override fun computePresentation(xValueNode: XValueNode, place: XValuePlace) { + xValueNode.setPresentation(null, v.valueTypeName, v.value, false) + } +} + +class GroupXValue(v: VariableValue, val frame: EmmyDebugStackFrame) : LuaXValue(v) { + private val children = mutableListOf() + + init { + value.children?. + sortedWith(VariableComparator)?. + forEach { + children.add(create(it, frame)) + } + } + + override fun computePresentation(xValueNode: XValueNode, place: XValuePlace) { + xValueNode.setPresentation(AllIcons.Nodes.UpLevel, value.valueTypeName, value.value, true) + } + + override fun computeChildren(node: XCompositeNode) { + val cl = XValueChildrenList() + children.forEach { + it.parent = this + cl.add(it.name, it) + } + node.addChildren(cl, true) + } +} + +class TableXValue(v: VariableValue, val frame: EmmyDebugStackFrame) : LuaXValue(v) { + + private val children = mutableListOf() + + init { + value.children?. + sortedWith(VariableComparator)?. + forEach { + children.add(create(it, frame)) + } + } + + override fun computePresentation(xValueNode: XValueNode, place: XValuePlace) { + var icon = AllIcons.Json.Object + if (value.valueTypeName == "C#") { + icon = LuaIcons.CSHARP + } + else if (value.valueTypeName == "C++") { + icon = LuaIcons.CPP + } + xValueNode.setPresentation(icon, value.valueTypeName, value.value, true) + } + + override fun computeChildren(node: XCompositeNode) { + val ev = this.frame.evaluator + if (ev != null) { + ev.eval(evalExpr, value.cacheId, object : XDebuggerEvaluator.XEvaluationCallback { + override fun errorOccurred(err: String) { + node.setErrorMessage(err) + } + + override fun evaluated(value: XValue) { + if (value is TableXValue) { + val cl = XValueChildrenList() + children.clear() + children.addAll(value.children) + children.forEach { + it.parent = this@TableXValue + cl.add(it.name, it) + } + node.addChildren(cl, true) + } + else { // todo: table is nil? + node.setErrorMessage("nil") + } + } + + }, 2) + } + else super.computeChildren(node) + } + + private val evalExpr: String + get() { + var name = name + val properties = ArrayList() + var parent = this.parent + while (parent != null) { + if (!parent.value.fake) { + properties.add(name) + name = parent.name + } + parent = parent.parent + } + + val sb = StringBuilder(name) + for (i in properties.indices.reversed()) { + val parentName = properties[i] + if (parentName.startsWith("[")) + sb.append(parentName) + else + sb.append(String.format("[\"%s\"]", parentName)) + } + return sb.toString() + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/lang/LuaIcons.kt b/src/main/java/com/tang/intellij/lua/lang/LuaIcons.kt index 3a3d50e..0aebd99 100644 --- a/src/main/java/com/tang/intellij/lua/lang/LuaIcons.kt +++ b/src/main/java/com/tang/intellij/lua/lang/LuaIcons.kt @@ -30,6 +30,8 @@ object LuaIcons { return getIcon(path, LuaIcons::class.java) } + val CPP = getIcon("/icons/cpp.png") + val CSHARP = getIcon("/icons/csharp.png") val FILE = getIcon("/icons/lua.png") val CLASS = AllIcons.Nodes.Class val Alias = AllIcons.Nodes.AbstractClass diff --git a/src/main/java/com/tang/intellij/lua/psi/LuaExprCodeFragment.kt b/src/main/java/com/tang/intellij/lua/psi/LuaExprCodeFragment.kt new file mode 100644 index 0000000..0c72d99 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/psi/LuaExprCodeFragment.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.psi + +import com.intellij.openapi.project.Project +import com.intellij.psi.FileViewProvider +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.SingleRootFileViewProvider +import com.intellij.psi.impl.PsiManagerEx +import com.intellij.psi.impl.source.tree.FileElement +import com.intellij.testFramework.LightVirtualFile +import com.tang.intellij.lua.lang.LuaLanguage + + +class LuaExprCodeFragment(project: Project, name: String, text: CharSequence, var myPhysical: Boolean) : LuaPsiFile( + PsiManagerEx.getInstanceEx(project).fileManager.createFileViewProvider( + LightVirtualFile( + name, + LuaLanguage.INSTANCE, + text + ), myPhysical + ) +), PsiFile { + init { + (viewProvider as SingleRootFileViewProvider).forceCachedPsi(this) + } + + private var myViewProvider: FileViewProvider? = null + private var myContext: PsiElement? = null + + fun setContext(context: PsiElement?) { + myContext = context + } + + override fun getContext(): PsiElement? { + val mc = myContext + if (mc != null && mc.isValid) + return mc + return super.getContext() + } + + override fun clone(): LuaExprCodeFragment { + val clone = cloneImpl(calcTreeElement().clone() as FileElement) as LuaExprCodeFragment + copyCopyableDataTo(clone) + clone.myPhysical = false + clone.myOriginalFile = this + val fileMgr = (manager as PsiManagerEx).fileManager + val cloneViewProvider = + fileMgr.createFileViewProvider(LightVirtualFile(name, language, text), false) as SingleRootFileViewProvider + cloneViewProvider.forceCachedPsi(clone) + clone.myViewProvider = cloneViewProvider + return clone + } + + override fun isPhysical(): Boolean { + return myPhysical + } + + override fun getViewProvider(): FileViewProvider { + return myViewProvider ?: super.getViewProvider() + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/psi/LuaFileManager.kt b/src/main/java/com/tang/intellij/lua/psi/LuaFileManager.kt new file mode 100644 index 0000000..88257aa --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/psi/LuaFileManager.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.psi + +import com.intellij.openapi.fileTypes.FileTypeEvent +import com.intellij.openapi.fileTypes.FileTypeListener +import com.intellij.openapi.fileTypes.FileTypeManager +import com.tang.intellij.lua.lang.LuaFileType + +class LuaFileManager : FileTypeListener { + + companion object { + private var myExtensions = mutableListOf() + private var dirty = true + + val extensions: Array get() { + if (dirty) { + dirty = false + val all = FileTypeManager.getInstance().getAssociations(LuaFileType.INSTANCE).mapNotNull { + // *.lua -> .lua + // *.lua.txt -> .lua.txt + if (it.presentableString.startsWith("*.")) + it.presentableString.substring(1) + else null + } + myExtensions.clear() + myExtensions.addAll(all) + myExtensions.add("") + } + return myExtensions.toTypedArray() + } + } + + override fun fileTypesChanged(event: FileTypeEvent) { + dirty = true + } +} \ No newline at end of file diff --git a/src/main/java/com/tang/intellij/lua/psi/LuaFileUtil.kt b/src/main/java/com/tang/intellij/lua/psi/LuaFileUtil.kt new file mode 100644 index 0000000..e693fc3 --- /dev/null +++ b/src/main/java/com/tang/intellij/lua/psi/LuaFileUtil.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017. tangzx(love.tangzx@qq.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tang.intellij.lua.psi + +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager +import java.io.File + +/** + * + * Created by tangzx on 2017/1/4. + */ +object LuaFileUtil { + + private val pluginVirtualDirectory: VirtualFile? + get() { + val descriptor = PluginManagerCore.getPlugin(PluginId.getId("com.tang")) + if (descriptor != null) { + return VirtualFileManager.getInstance().findFileByNioPath(descriptor.pluginPath) + } + + return null + } + + fun findFile(project: Project, shortUrl: String?): VirtualFile? { + var fixedShortUrl = shortUrl ?: return null + + // Check if the path is absolute + if (File(fixedShortUrl).isAbsolute) { + val virtualFile = VfsUtil.findFileByIoFile(File(fixedShortUrl), true) + if (virtualFile != null && virtualFile.exists()) { + return virtualFile + } + return null + } + + // "./x.lua" => "x.lua" + if (fixedShortUrl.startsWith("./") || fixedShortUrl.startsWith(".\\")) { + fixedShortUrl = fixedShortUrl.substring(2) + } + // Check if the fixedShortUrl already has an extension + val hasExtension = fixedShortUrl.contains(".") + if (hasExtension) { + val virtualFile = VfsUtil.findRelativeFile(fixedShortUrl, project.baseDir) + if (virtualFile != null && virtualFile.exists()) { + return virtualFile + } + return null + } + else { + val extensions = LuaFileManager.extensions + for (extension in extensions) { + val fileName = if (extension.isEmpty()) fixedShortUrl else "$fixedShortUrl.$extension" + val virtualFile = VfsUtil.findRelativeFile(fileName, project.baseDir) + if (virtualFile != null && virtualFile.exists()) { + return virtualFile + } + } + } + return null + } + + fun getPluginVirtualFile(path: String): String? { + val directory = pluginVirtualDirectory + if (directory != null) { + var fullPath = directory.path + "/classes/" + path + if (File(fullPath).exists()) + return fullPath + fullPath = directory.path + "/" + path + if (File(fullPath).exists()) + return fullPath + } + return null + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index b91465f..a53cd1d 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -63,6 +63,10 @@ This plugin is based on [EmmyLuaAnalyzer](https://github.com/CppCXY/EmmyLuaAnaly + + + +