From b63df137a90a2d349bd669b4e8c61b43e133e497 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Tue, 29 Jul 2025 22:17:36 +0800 Subject: [PATCH 1/6] =?UTF-8?q?nop-xlang-debugger:=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20XLangDebuggerInitializer#createDebugger=20?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E5=8D=95=E7=8B=AC=E5=88=9B=E5=BB=BA=20XLangD?= =?UTF-8?q?ebugger=20=E5=AF=B9=E8=B1=A1=EF=BC=8C=E4=BB=8E=E8=80=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=9C=A8=20nop-idea-plugin=20=E4=B8=AD?= =?UTF-8?q?=E5=AF=B9=E5=85=B6=E8=BF=9B=E8=A1=8C=E5=AE=9A=E5=88=B6=EF=BC=8C?= =?UTF-8?q?=E8=BF=9B=E8=80=8C=E6=94=AF=E6=8C=81=E5=9C=A8=20nop-idea-plugin?= =?UTF-8?q?=20=E4=B8=AD=E5=AF=B9=20Xpl=20=E8=BF=9B=E8=A1=8C=E6=96=AD?= =?UTF-8?q?=E7=82=B9=E8=B7=9F=E8=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xlang/debugger/initialize/XLangDebuggerInitializer.java | 6 +++++- nop-xlang/src/main/java/io/nop/xlang/XLangConfigs.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/nop-xlang-debugger/src/main/java/io/nop/xlang/debugger/initialize/XLangDebuggerInitializer.java b/nop-xlang-debugger/src/main/java/io/nop/xlang/debugger/initialize/XLangDebuggerInitializer.java index f18909130..6d8a44955 100644 --- a/nop-xlang-debugger/src/main/java/io/nop/xlang/debugger/initialize/XLangDebuggerInitializer.java +++ b/nop-xlang-debugger/src/main/java/io/nop/xlang/debugger/initialize/XLangDebuggerInitializer.java @@ -61,7 +61,7 @@ public class XLangDebuggerInitializer implements ICoreInitializer { config.setIdleTimeout(0); server.setServerConfig(config); - debugger = new XLangDebugger(); + debugger = createDebugger(); debugger.setNotifier(new DebugNotifier()); server.addServiceImpl(IDebugger.class, debugger); server.setOnChannelOpen(this::sendBreakpointNotice); @@ -76,6 +76,10 @@ public class XLangDebuggerInitializer implements ICoreInitializer { EvalExprProvider.registerGlobalExecutor(new DebugExpressionExecutor(debugger)); } + protected XLangDebugger createDebugger() { + return new XLangDebugger(); + } + // 通知调试器客户端当前正在处理的断点情况 private void sendBreakpointNotice(String addr) { XLangDebugger debugger = this.debugger; diff --git a/nop-xlang/src/main/java/io/nop/xlang/XLangConfigs.java b/nop-xlang/src/main/java/io/nop/xlang/XLangConfigs.java index 65611bd31..529930fe7 100644 --- a/nop-xlang/src/main/java/io/nop/xlang/XLangConfigs.java +++ b/nop-xlang/src/main/java/io/nop/xlang/XLangConfigs.java @@ -33,7 +33,7 @@ public interface XLangConfigs { @Description("XLang调试器端口") IConfigReference CFG_XLANG_DEBUGGER_PORT = varRef(s_loc,"nop.xlang.debugger.port", Integer.class, 12345); - @Description("XLang调试器端口") + @Description("XLang调试服务传输的最大数据长度") IConfigReference CFG_XLANG_DEBUGGER_MAX_DATA_LEN = varRef(s_loc,"nop.xlang.debugger.max-data-len", Integer.class, 1024 * 1024 * 2); -- Gitee From 6e1124ddd250ff01e06de109d1adf1fac25a879f Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Tue, 29 Jul 2025 22:20:08 +0800 Subject: [PATCH 2/6] =?UTF-8?q?nop-idea-pugin:=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=9C=A8=20gradle=20=E9=A1=B9=E7=9B=AE=E4=B8=AD=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=20XLang=20Debugger=20=E5=B9=B6=E5=AF=B9=20Xpl=20?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E6=96=AD=E7=82=B9=E8=B0=83=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nop-idea-plugin/build.gradle.kts | 2 +- .../debugger/XLangBreakpointHandler.java | 40 +++--- .../plugin/debugger/XLangDebugConnector.java | 6 +- .../plugin/debugger/XLangDebugProcess.java | 71 ++++++----- .../plugin/debugger/XLangDebuggerRunner.java | 118 +++++++++--------- .../XLangRunConfigurationExtension.java | 115 +++++++++++++++++ .../plugin/execution/XLangDebugExecutor.java | 8 +- .../io/nop/idea/plugin/icons/NopIcons.java | 8 +- .../plugin/services/NopProjectService.java | 11 +- .../src/main/resources/META-INF/plugin.xml | 3 + .../resources/actions/xlangDebug-small.svg | 27 ++++ .../messages/NopPluginBundle.properties | 2 + .../messages/NopPluginBundle_zh.properties | 2 + .../idea/plugin/BaseXLangPluginTestCase.java | 47 ++++++- 14 files changed, 327 insertions(+), 133 deletions(-) create mode 100644 nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangRunConfigurationExtension.java create mode 100644 nop-idea-plugin/src/main/resources/actions/xlangDebug-small.svg diff --git a/nop-idea-plugin/build.gradle.kts b/nop-idea-plugin/build.gradle.kts index 27bb061e9..54e9d70ee 100644 --- a/nop-idea-plugin/build.gradle.kts +++ b/nop-idea-plugin/build.gradle.kts @@ -22,7 +22,7 @@ intellij { //version.set("2022.3") type.set("IC") // Target IDE Platform - plugins.set(listOf("java", "org.jetbrains.plugins.yaml")) + plugins.set(listOf("java", "gradle", "org.jetbrains.plugins.yaml")) } diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangBreakpointHandler.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangBreakpointHandler.java index 30ce82b31..d4dff3066 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangBreakpointHandler.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangBreakpointHandler.java @@ -36,18 +36,18 @@ public class XLangBreakpointHandler extends XBreakpointHandler> breakpoints = new HashMap(); - private XLangDebugProcess debugProcess; + private final Map> breakpoints = new HashMap<>(); + private final XLangDebugProcess debugProcess; private boolean muted; - private Map bpMap = new HashMap<>(); + private final Map bpMap = new HashMap<>(); public XLangBreakpointHandler(final XLangDebugProcess debugProcess) { super(XLangBreakpointType.class); this.debugProcess = debugProcess; } - public void breakpointMuted(boolean muted) { + public void breakpointsMuted(boolean muted) { this.muted = muted; IDebuggerAsync debugger = debugProcess.getDebugger(); if (debugger != null) { @@ -55,6 +55,7 @@ public class XLangBreakpointHandler extends XBreakpointHandler breakpoint) { Breakpoint bp = makeBreakpoint(breakpoint); if (bp == null) @@ -62,7 +63,6 @@ public class XLangBreakpointHandler extends XBreakpointHandler breakpoint, final boolean temporary) { + Breakpoint bp = makeBreakpoint(breakpoint); + if (bp == null) + return; + + String key = getKey(bp); + breakpoints.remove(key); + bpMap.remove(key); + + sendBreakpoints(); + } + private String getKey(Breakpoint bp) { return bp.getSourcePath() + ':' + bp.getLine(); } @@ -102,34 +115,19 @@ public class XLangBreakpointHandler extends XBreakpointHandler breakpoint, final boolean temporary) { - Breakpoint bp = makeBreakpoint(breakpoint); - if (bp == null) - return; - - String key = getKey(bp); - breakpoints.remove(key); - - bpMap.remove(key); - sendBreakpoints(); - - } - public void sendBreakpoints() { IDebuggerAsync debugger = debugProcess.getDebugger(); if (debugger != null) { List bps = new ArrayList<>(bpMap.values()); debugger.updateBreakpointsAsync(bps, muted) .exceptionally(e -> { - debugProcess.getSession().reportMessage("update breakpoints fail", MessageType.ERROR); + debugProcess.getSession().reportMessage("Update breakpoints fail", MessageType.ERROR); return null; }); } } public XBreakpoint findBreakPoint(@NotNull StackTraceElement elm) { - if (elm == null) - return null; String path = elm.getSourcePath(); int lineNumber = elm.getLine(); return breakpoints.get(path + ':' + lineNumber); diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugConnector.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugConnector.java index 7acb1a63e..1dbb02e60 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugConnector.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugConnector.java @@ -21,16 +21,18 @@ import java.util.function.Consumer; * 通过socket连接到远程XLang服务器 */ public class XLangDebugConnector implements IDestroyable { - private SimpleRpcClientFactory clientFactory = new SimpleRpcClientFactory<>(); + private final SimpleRpcClientFactory clientFactory = new SimpleRpcClientFactory<>(); public XLangDebugConnector(int debugPort, Consumer> action, Runnable onChannelOpen) { ClientConfig config = new ClientConfig(); config.setReadTimeout(0); config.setPort(debugPort); - RetryPolicy policy = new RetryPolicy(); + + RetryPolicy policy = new RetryPolicy<>(); policy.setRetryDelay(200); policy.setExponentialDelay(true); policy.setMaxRetryDelay(1000); + config.setReconnectPolicy(policy); clientFactory.setClientConfig(config); diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugProcess.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugProcess.java index e80b15757..ab0eab456 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugProcess.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugProcess.java @@ -7,11 +7,11 @@ */ package io.nop.idea.plugin.debugger; +import javax.swing.event.HyperlinkListener; + import com.intellij.debugger.engine.JavaDebugProcess; import com.intellij.debugger.impl.DebuggerSession; import com.intellij.debugger.impl.PrioritizedTask; -import com.intellij.execution.configurations.JavaCommandLineState; -import com.intellij.execution.configurations.RunProfileState; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.progress.ProgressIndicator; @@ -34,6 +34,7 @@ import io.nop.api.debugger.Breakpoint; import io.nop.api.debugger.BreakpointHitMessage; import io.nop.api.debugger.IDebuggerAsync; import io.nop.api.debugger.LineLocation; +import io.nop.commons.lang.impl.Cancellable; import io.nop.core.reflect.bean.BeanTool; import io.nop.idea.plugin.utils.ProjectFileHelper; import org.jetbrains.annotations.NotNull; @@ -41,8 +42,6 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.event.HyperlinkListener; - import static com.intellij.xdebugger.impl.ui.DebuggerUIUtil.invokeLater; /** @@ -51,7 +50,6 @@ import static com.intellij.xdebugger.impl.ui.DebuggerUIUtil.invokeLater; public class XLangDebugProcess extends JavaDebugProcess { static final Logger LOG = LoggerFactory.getLogger(XLangDebugProcess.class); private final XLangBreakpointHandler myBreakPointHandler; - private final JavaCommandLineState state; private final XLangDebuggerEditorsProvider myEditorsProvider; boolean isDisconnected = false; @@ -60,24 +58,23 @@ public class XLangDebugProcess extends JavaDebugProcess { //private final AtomicBoolean breakpointsInitiated = new AtomicBoolean(); private final XLangDebugConnector connector; + private final Cancellable cleanup = new Cancellable(); - public XLangDebugProcess(@NotNull final XDebugSession session, - @NotNull final JavaCommandLineState state, - final DebuggerSession javaSession, - int debugPort + public XLangDebugProcess( + @NotNull final XDebugSession session, final DebuggerSession javaSession, int debugPort ) { super(session, javaSession); - this.state = state; this.myEditorsProvider = new XLangDebuggerEditorsProvider(); this.myBreakPointHandler = new XLangBreakpointHandler(this); + this.connector = new XLangDebugConnector(debugPort, this::debuggerNotification, this::onSocketConnected); javaSession.getProcess().setXDebugProcess(this); session.addSessionListener(new XDebugSessionListener() { @Override public void breakpointsMuted(boolean muted) { - myBreakPointHandler.breakpointMuted(muted); + myBreakPointHandler.breakpointsMuted(muted); } public void sessionResumed() { @@ -106,14 +103,16 @@ public class XLangDebugProcess extends JavaDebugProcess { public void sessionInitialized() { super.sessionInitialized(); - ProgressManager.getInstance().run(new Task.Backgroundable(null, "XLang debugger", true) { + ProgressManager.getInstance().run(new Task.Backgroundable(null, "XLang debugger connector", true) { + @Override public void run(@NotNull final ProgressIndicator indicator) { - indicator.setText("XLang Debugger Connecting..."); + indicator.setText("XLang debugger connecting..."); indicator.setIndeterminate(true); try { - if (!connect()) + if (!connect()) { return; + } startDebugSession(); } catch (final Exception e) { onConnectFail(e.getMessage()); @@ -126,16 +125,19 @@ public class XLangDebugProcess extends JavaDebugProcess { while (true) { try { debugger = connector.connect(); - System.out.println("connected"); + cleanup.appendOnCancelTask(connector::destroy); + + LOG.info("xlang.debugger.connector-connected"); return true; } catch (final Exception e) { try { Thread.sleep(200); - } catch (Exception e2) { + } catch (Exception ignored) { } - if (getProcessHandler().isProcessTerminated()) + if (getProcessHandler().isProcessTerminated()) { return false; + } } } } @@ -147,16 +149,15 @@ public class XLangDebugProcess extends JavaDebugProcess { private void onConnectFail(final String msg) { getProcessHandler().destroyProcess(); invokeLater(() -> { - String text = "XLangDebugger can't connect to DebuggerServer on port " + connector.getDebugPort();//myDebuggerProxy.getPort(); - Messages.showErrorDialog(msg != null ? text + ":\r\n" + msg : text, "XLang debugger"); + String text = "XLangDebugger can't connect to DebuggerServer on port " + + connector.getDebugPort();//myDebuggerProxy.getPort(); + Messages.showErrorDialog(msg != null ? text + ":\r\n" + msg : text, "XLang Debugger"); }); } private void startDebugSession() { //initBreakpointHandlersAndSetBreakpoints(); - ReadAction.run(() -> { - myBreakPointHandler.sendBreakpoints(); - }); + ReadAction.run(myBreakPointHandler::sendBreakpoints); } @@ -177,8 +178,9 @@ public class XLangDebugProcess extends JavaDebugProcess { @Override public void resume(@Nullable XSuspendContext context) { if (context instanceof XLangSuspendContext) { - if (debugger != null) + if (debugger != null) { debugger.resumeAsync(); + } } else { super.resume(context); } @@ -243,22 +245,22 @@ public class XLangDebugProcess extends JavaDebugProcess { } public void stop() { - connector.destroy(); + cleanup.cancel(); super.stop(); isDisconnected = true; - System.out.println("end debug process"); + LOG.info("xlang.debugger.process-stopped"); } @Override - public XBreakpointHandler[] getBreakpointHandlers() { + public XBreakpointHandler @NotNull [] getBreakpointHandlers() { XBreakpointHandler[] handlers = super.getBreakpointHandlers(); XBreakpointHandler[] ret = new XBreakpointHandler[handlers.length + 1]; - for (int i = 0; i < handlers.length; i++) { - ret[i] = handlers[i]; - } + + System.arraycopy(handlers, 0, ret, 0, handlers.length); ret[ret.length - 1] = myBreakPointHandler; + return ret; } @@ -292,8 +294,8 @@ public class XLangDebugProcess extends JavaDebugProcess { this.getDebuggerSession().getProcess().getManagerThread().schedule(PrioritizedTask.Priority.LOW, () -> { BreakpointHitMessage hit = BeanTool.buildBean(response.getData(), BreakpointHitMessage.class); - XBreakpoint breakpoint = myBreakPointHandler.findBreakPoint( - hit.getStackInfo().getTopElement()); + XBreakpoint breakpoint = myBreakPointHandler.findBreakPoint(hit.getStackInfo() + .getTopElement()); XDebugSession session = getSession(); XSuspendContext context = session.getSuspendContext(); @@ -342,9 +344,10 @@ public class XLangDebugProcess extends JavaDebugProcess { } @Override - public void registerAdditionalActions(@NotNull DefaultActionGroup leftToolbar, - @NotNull DefaultActionGroup topToolbar, - @NotNull DefaultActionGroup settings) { + public void registerAdditionalActions( + @NotNull DefaultActionGroup leftToolbar, @NotNull DefaultActionGroup topToolbar, + @NotNull DefaultActionGroup settings + ) { super.registerAdditionalActions(leftToolbar, topToolbar, settings); //topToolbar.remove(ActionManager.getInstance().getAction(XDebuggerActions.RUN_TO_CURSOR)); } diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebuggerRunner.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebuggerRunner.java index 3162fd5de..2051e60c0 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebuggerRunner.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebuggerRunner.java @@ -7,22 +7,24 @@ */ package io.nop.idea.plugin.debugger; +import java.awt.*; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; + import com.intellij.debugger.DebugEnvironment; import com.intellij.debugger.DebuggerManagerEx; -import com.intellij.debugger.DefaultDebugEnvironment; import com.intellij.debugger.impl.DebuggerSession; import com.intellij.debugger.impl.GenericDebuggerRunner; import com.intellij.execution.ExecutionException; import com.intellij.execution.JavaRunConfigurationBase; -import com.intellij.execution.configurations.JavaCommandLineState; -import com.intellij.execution.configurations.JavaParameters; -import com.intellij.execution.configurations.RemoteConnection; +import com.intellij.execution.RunConfigurationExtension; import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.execution.runners.JavaProgramPatcher; import com.intellij.execution.ui.RunContentDescriptor; -import com.intellij.util.net.NetUtils; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Computable; import com.intellij.xdebugger.XDebugProcess; import com.intellij.xdebugger.XDebugProcessStarter; import com.intellij.xdebugger.XDebugSession; @@ -30,30 +32,18 @@ import com.intellij.xdebugger.XDebuggerManager; import io.nop.api.core.exceptions.NopException; import io.nop.api.core.util.FutureHelper; import io.nop.idea.plugin.execution.XLangDebugExecutor; +import io.nop.idea.plugin.messages.NopPluginBundle; +import io.nop.idea.plugin.utils.PsiClassHelper; +import io.nop.xlang.debugger.XLangDebugger; import org.jetbrains.annotations.NotNull; - -import java.awt.*; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; +import org.jetbrains.plugins.gradle.service.execution.GradleRunConfiguration; /** * 选择XLang Runner来执行java代码时自动启动XLang调试器 */ public class XLangDebuggerRunner extends GenericDebuggerRunner { - private static final String XDEBUG = "-Xdebug"; - private static final String JDWP = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address="; - private static final String XLANG_DEBUG_ENABLED = "-Dnop.xlang.debugger.enabled=true"; - private static final String XLANG_DEBUG_PORT = "-Dnop.xlang.debugger.port="; - - private static final String LOCALHOST = "127.0.0.1"; - - private static final String XLANG_DEBUG_RUNNER_ID = "XLangDebugRunner"; - public XLangDebuggerRunner() { - super(); - } - @Override public @NotNull String getRunnerId() { return XLANG_DEBUG_RUNNER_ID; @@ -62,64 +52,68 @@ public class XLangDebuggerRunner extends GenericDebuggerRunner { @Override public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) { return executorId.equals(XLangDebugExecutor.EXECUTOR_ID) // 与 XLangDebugExecutor 绑定 - // 同时支持 Application 和 JUnit 类型的 Run Configuration, - // 即,可对 main() 函数和单元测试进行 XLang 调试 - && profile instanceof JavaRunConfigurationBase; + && ( // 同时支持 Application 和 JUnit 类型的 Run Configuration, + // 即,可对 main() 函数和单元测试进行 XLang 调试 + profile instanceof JavaRunConfigurationBase + // 支持 Gradle 类型的 Run Configuration + || profile instanceof GradleRunConfiguration); } @Override - protected RunContentDescriptor createContentDescriptor(RunProfileState state, ExecutionEnvironment environment) throws ExecutionException { - // FileDocumentManager.getInstance().saveAllDocuments(); - // String debuggerPort = DebuggerUtils.getInstance().findAvailableDebugAddress(true); - int[] ports; - try { - ports = NetUtils.findAvailableSocketPorts(2); - } catch (Exception e) { - throw NopException.adapt(e); + protected RunContentDescriptor createContentDescriptor( + @NotNull RunProfileState state, @NotNull ExecutionEnvironment environment + ) throws ExecutionException { + if (noXLangDebuggerInProject(environment)) { + // Note: 确保 Messages#showErrorDialog 在事件分发线程 (EDT) 中调用, + // 避免出现异常 "Access is allowed from Event Dispatch Thread (EDT) only" + ApplicationManager.getApplication().invokeLater(() -> { + Messages.showErrorDialog(NopPluginBundle.message("xlang.debugger.not-exist"), "XLang Debugger"); + }); + return null; } - JavaCommandLineState javaCommandLineState = (JavaCommandLineState) state; - - JavaParameters javaParameters = javaCommandLineState.getJavaParameters(); - // Making the assumption that it's JVM 7 onwards - javaParameters.getVMParametersList().addParametersString(XDEBUG); - // Debugger port - - String remotePort = JDWP + ports[0]; - javaParameters.getVMParametersList().addParametersString(remotePort); - - // 传入调试开关和调试端口 - javaParameters.getVMParametersList().addParametersString(XLANG_DEBUG_ENABLED); - javaParameters.getVMParametersList().addParametersString(XLANG_DEBUG_PORT + ports[1]); - - JavaProgramPatcher.runCustomPatchers(javaParameters, environment.getExecutor(), environment.getRunProfile()); - - //final ProcessHandler handler = state.execute(environment.getExecutor(),this).getProcessHandler(); - RemoteConnection connection = new RemoteConnection(true, LOCALHOST, String.valueOf(ports[0]), false); - DebugEnvironment debugEnvironment = new DefaultDebugEnvironment(environment, state, connection, 3000L); + XLangRunConfigurationExtension extension = // + RunConfigurationExtension.EP_NAME.findExtensionOrFail(XLangRunConfigurationExtension.class); + int xlangDebugPort = extension.getXLangDebugPort(); + DebugEnvironment debugEnvironment = extension.createDebugEnvironment(state, environment); - return dispatch(() -> { - final DebuggerSession debuggerSession = DebuggerManagerEx.getInstanceEx(environment.getProject()).attachVirtualMachine(debugEnvironment); - + return dispatch(()-> { // 实际上同时启动了java调试器和XLang调试器 - final XDebugSession session = XDebuggerManager.getInstance(environment.getProject()).startSession(environment, new XDebugProcessStarter() { + XDebugSession session = XDebuggerManager.getInstance(environment.getProject()).startSession(environment, new XDebugProcessStarter() { @NotNull @Override - public XDebugProcess start(@NotNull XDebugSession xDebugSession) throws ExecutionException { - debuggerSession.getContextManager().addListener(new XLangDebugContextListener(xDebugSession, debuggerSession)); - XLangDebugProcess process = new XLangDebugProcess(xDebugSession, javaCommandLineState, debuggerSession, - ports[1]); - return process; + public XDebugProcess start(@NotNull XDebugSession debugSession) throws ExecutionException { + DebuggerSession debuggerSession = // + DebuggerManagerEx.getInstanceEx(environment.getProject()) // + .attachVirtualMachine(debugEnvironment); + assert debuggerSession != null; + + debuggerSession.getContextManager() // + .addListener(new XLangDebugContextListener(debugSession, debuggerSession)); + + return new XLangDebugProcess(debugSession, debuggerSession, xlangDebugPort); } }); return session.getRunContentDescriptor(); }); + } + /** 当前项目是否未依赖 {@link XLangDebugger} */ + private boolean noXLangDebuggerInProject(@NotNull ExecutionEnvironment environment) { + return ApplicationManager.getApplication().runReadAction((Computable) () -> { + // Note: 避免出现异常 "Read access is allowed from inside read-action only" + try { + return PsiClassHelper.findClass(environment.getProject(), XLangDebugger.class.getName()) == null; + } catch (Exception ignore) { + // Note: 可能存在索引还未创建完毕的异常,这里直接忽略即可 + return false; + } + }); } - T dispatch(Callable task) { + private T dispatch(Callable task) { if (EventQueue.isDispatchThread()) { try { return task.call(); diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangRunConfigurationExtension.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangRunConfigurationExtension.java new file mode 100644 index 000000000..1d2e6e6a8 --- /dev/null +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangRunConfigurationExtension.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017-2025 Nop Platform. All rights reserved. + * Author: canonical_entropy@163.com + * Blog: https://www.zhihu.com/people/canonical-entropy + * Gitee: https://gitee.com/canonical-entropy/nop-entropy + * Github: https://github.com/entropy-cloud/nop-entropy + */ + +package io.nop.idea.plugin.debugger; + +import com.intellij.debugger.DebugEnvironment; +import com.intellij.debugger.DefaultDebugEnvironment; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.JavaRunConfigurationBase; +import com.intellij.execution.RunConfigurationExtension; +import com.intellij.execution.configurations.JavaParameters; +import com.intellij.execution.configurations.ParametersList; +import com.intellij.execution.configurations.RemoteConnection; +import com.intellij.execution.configurations.RunConfigurationBase; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.RunnerSettings; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.JavaProgramPatcher; +import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunnableState; +import com.intellij.util.net.NetUtils; +import io.nop.api.core.exceptions.NopException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.service.execution.GradleRunConfiguration; + +import static io.nop.xlang.XLangConfigs.CFG_XLANG_DEBUGGER_ENABLED; +import static io.nop.xlang.XLangConfigs.CFG_XLANG_DEBUGGER_PORT; + +/** + * 通过单独的 {@link RunConfigurationExtension} 向 + * {@link ExternalSystemRunnableState#getJvmParametersSetup()} + * 注入 jvm 参数,以支持对 Gradle 项目的调试 + * + * @author flytreeleft + * @date 2025-07-27 + */ +public class XLangRunConfigurationExtension extends RunConfigurationExtension { + private static final String LOCALHOST = "127.0.0.1"; + + private static final String XDEBUG = "-Xdebug"; + private static final String JDWP = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address="; + + private int[] ports; + + public DebugEnvironment createDebugEnvironment( + @NotNull RunProfileState state, @NotNull ExecutionEnvironment environment + ) { + int javaDebugPort = getJavaDebugPort(); + RemoteConnection connection = new RemoteConnection(true, LOCALHOST, String.valueOf(javaDebugPort), false); + + // Note: gradle 的启动时间较长,因此,需设置更长的等待超时时间 + return new DefaultDebugEnvironment(environment, state, connection, DebugEnvironment.LOCAL_START_TIMEOUT); + } + + @Override + public boolean isApplicableFor(@NotNull RunConfigurationBase configuration) { + return configuration instanceof JavaRunConfigurationBase // + || configuration instanceof GradleRunConfiguration; + } + + @Override + public > void updateJavaParameters( + @NotNull T configuration, @NotNull JavaParameters params, RunnerSettings runnerSettings, + @NotNull Executor executor + ) throws ExecutionException { + updateJavaParameters(configuration, params, runnerSettings); + + JavaProgramPatcher.runCustomPatchers(params, executor, configuration); + } + + @Override + public > void updateJavaParameters( + @NotNull T configuration, @NotNull JavaParameters params, @Nullable RunnerSettings runnerSettings + ) throws ExecutionException { + int javaDebugPort = getJavaDebugPort(); + int xlangDebugPort = getXLangDebugPort(); + ParametersList paramsList = params.getVMParametersList(); + + // Making the assumption that it's JVM 7 onwards + paramsList.addParametersString(XDEBUG); + + // Debugger port + String remotePort = JDWP + LOCALHOST + ':' + javaDebugPort; + paramsList.addParametersString(remotePort); + + // 传入调试开关和调试端口 + paramsList.addParametersString("-D" + CFG_XLANG_DEBUGGER_ENABLED.getName() + "=true"); + paramsList.addParametersString("-D" + CFG_XLANG_DEBUGGER_PORT.getName() + '=' + xlangDebugPort); + } + + public int getJavaDebugPort() { + return getPorts()[0]; + } + + public int getXLangDebugPort() { + return getPorts()[1]; + } + + private int[] getPorts() { + if (ports == null) { + try { + ports = NetUtils.findAvailableSocketPorts(2); + } catch (Exception e) { + throw NopException.adapt(e); + } + } + return ports; + } +} diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/execution/XLangDebugExecutor.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/execution/XLangDebugExecutor.java index be6a4fa39..9601b2b42 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/execution/XLangDebugExecutor.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/execution/XLangDebugExecutor.java @@ -7,6 +7,8 @@ */ package io.nop.idea.plugin.execution; +import javax.swing.*; + import com.intellij.execution.Executor; import com.intellij.execution.ExecutorRegistry; import com.intellij.openapi.util.IconLoader; @@ -16,8 +18,6 @@ import io.nop.idea.plugin.icons.NopIcons; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import javax.swing.Icon; - public class XLangDebugExecutor extends Executor { @NonNls public static final String EXECUTOR_ID = "XLangDebug"; @@ -31,13 +31,13 @@ public class XLangDebugExecutor extends Executor { @NotNull @Override public Icon getToolWindowIcon() { - return NopIcons.XLangDebug; + return NopIcons.Tool_XLangDebug; } @Override @NotNull public Icon getIcon() { - return NopIcons.StartXLangDebugger; + return NopIcons.Action_XLangDebug; } @Override diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/icons/NopIcons.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/icons/NopIcons.java index f6d549749..6b5708a83 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/icons/NopIcons.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/icons/NopIcons.java @@ -24,11 +24,13 @@ import static com.intellij.openapi.util.IconLoader.getIcon; * Font : Gotham */ public class NopIcons { - + /** 文件类型图标: 16x16 */ public static final Icon XLangFileType = getIcon("/icons/type.svg", NopIcons.class); - public static final Icon XLangDebug = getIcon("/actions/xlangDebug.svg", NopIcons.class); - public static final Icon StartXLangDebugger = XLangDebug; + /** 底部工具栏的图标: 13x13 */ + public static final Icon Tool_XLangDebug = getIcon("/actions/xlangDebug-small.svg", NopIcons.class); + /** 顶部调试启动按钮图标: 16x16 */ + public static final Icon Action_XLangDebug = getIcon("/actions/xlangDebug.svg", NopIcons.class); private NopIcons() { } diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/services/NopProjectService.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/services/NopProjectService.java index e5fb2f64e..a599e507e 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/services/NopProjectService.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/services/NopProjectService.java @@ -30,7 +30,8 @@ public final class NopProjectService implements Disposable { private final Cancellable cleanup = new Cancellable(); private final ResourceLoadingCache dictCache = new ResourceLoadingCache<>("project-dict-cache", - ProjectDictProvider::loadDictModel, null); + ProjectDictProvider::loadDictModel, + null); public NopProjectService() { } @@ -41,8 +42,9 @@ public final class NopProjectService implements Disposable { } private synchronized void init(Project project) { - if (inited) + if (inited) { return; + } inited = true; ProjectEnv.withProject(project, () -> { @@ -57,8 +59,9 @@ public final class NopProjectService implements Disposable { public static NopProjectService get() { Project project = ProjectEnv.currentProject(); - if (project == null) - throw new IllegalStateException("not in project env"); + if (project == null) { + throw new IllegalStateException("Not in project environment, please call with ProjectEnv#withProject"); + } NopProjectService service = project.getService(NopProjectService.class); service.init(project); diff --git a/nop-idea-plugin/src/main/resources/META-INF/plugin.xml b/nop-idea-plugin/src/main/resources/META-INF/plugin.xml index caaaf1c54..7710c07aa 100644 --- a/nop-idea-plugin/src/main/resources/META-INF/plugin.xml +++ b/nop-idea-plugin/src/main/resources/META-INF/plugin.xml @@ -16,6 +16,7 @@ com.intellij.modules.lang com.intellij.java + com.intellij.gradle org.jetbrains.plugins.yaml diff --git a/nop-idea-plugin/src/main/resources/actions/xlangDebug-small.svg b/nop-idea-plugin/src/main/resources/actions/xlangDebug-small.svg new file mode 100644 index 000000000..219a33b16 --- /dev/null +++ b/nop-idea-plugin/src/main/resources/actions/xlangDebug-small.svg @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties b/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties index 5d0fde440..7cb3c5db0 100644 --- a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties +++ b/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties @@ -35,3 +35,5 @@ xlang.doc.flag.internal = [Internal] xlang.doc.flag.allow-cp-expr = [#{var}] xlang.doc.markdown.link-title = '{'Link'}'{0} xlang.doc.markdown.image-title = '{'Image'}'{0} + +xlang.debugger.not-exist = Current project doesn't depend on 'nop-xlang-debugger', please add dependency 'io.github.entropy-cloud:nop-xlang-debugger' to pom.xml or build.gradle.kts with 'test' scope diff --git a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties b/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties index cf1366f2c..e056e81e2 100644 --- a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties +++ b/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties @@ -35,3 +35,5 @@ xlang.doc.flag.internal = [\u5185\u90E8] xlang.doc.flag.allow-cp-expr = [#{var}] xlang.doc.markdown.link-title = '{'\u94FE\u63A5'}'{0} xlang.doc.markdown.image-title = '{'\u56FE\u7247'}'{0} + +xlang.debugger.not-exist = \u5F53\u524D\u9879\u76EE\u672A\u4F9D\u8D56 'nop-xlang-debugger'\uFF0C\u8BF7\u6DFB\u52A0\u4F9D\u8D56 'io.github.entropy-cloud:nop-xlang-debugger' \u5230 pom.xml \u6216 build.gradle.kts \u6587\u4EF6\u4E2D\uFF0C\u5E76\u5C06 scope \u8BBE\u7F6E\u4E3A test diff --git a/nop-idea-plugin/src/test/java/io/nop/idea/plugin/BaseXLangPluginTestCase.java b/nop-idea-plugin/src/test/java/io/nop/idea/plugin/BaseXLangPluginTestCase.java index 486521358..7144f58c4 100644 --- a/nop-idea-plugin/src/test/java/io/nop/idea/plugin/BaseXLangPluginTestCase.java +++ b/nop-idea-plugin/src/test/java/io/nop/idea/plugin/BaseXLangPluginTestCase.java @@ -38,15 +38,19 @@ import com.intellij.psi.impl.DebugUtil; import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; import com.intellij.testFramework.fixtures.CodeInsightTestFixture; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; +import io.nop.api.core.util.SourceLocation; import io.nop.commons.lang.impl.Cancellable; import io.nop.commons.util.FileHelper; import io.nop.commons.util.IoHelper; +import io.nop.commons.util.StringHelper; import io.nop.core.resource.IResource; import io.nop.core.resource.ResourceHelper; import io.nop.core.resource.impl.ClassPathResource; import io.nop.idea.plugin.lang.XLangFileType; import io.nop.idea.plugin.lang.reference.XLangReference; import io.nop.idea.plugin.services.NopAppListener; +import io.nop.xlang.debugger.XLangDebugger; +import io.nop.xlang.debugger.initialize.XLangDebuggerInitializer; /** * @author flytreeleft @@ -77,10 +81,10 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur // Note: *.xdef 等需显式注册,否则,这类文件会被视为二进制文件, // 在通过 PsiDocumentManager 获取 Document 时,将返回 null FileTypeManager.getInstance().associateExtension(XLangFileType.INSTANCE, "xdef"); - FileTypeManager.getInstance().associateExtension(XLangFileType.INSTANCE, XLANG_EXT); new NopAppListener().appFrameCreated(new ArrayList<>()); + initXLangDebugger(); // Note: 提前将被引用的 vfs 资源添加到 Project 中 addAllNopXDefsToProject(); @@ -98,6 +102,41 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur super.tearDown(); } + private void initXLangDebugger() { + // Note: 在单元测试中,vfs 资源是针对 Project 被复制到单独的 src 目录下的, + // 通过 ProjectVirtualFileSystem 得到的 vfs 资源路径与调试断点的文件路径是不一致的, + // 因此,需要针对测试资源做路径转换,以匹配断点所在的文件路径 + XLangDebuggerInitializer debugger = new XLangDebuggerInitializer() { + @Override + protected XLangDebugger createDebugger() { + return new XLangDebugger() { + @Override + protected String toSourcePath(SourceLocation loc) { + String prefix = "/src/_vfs/"; + String path = super.toSourcePath(loc); + + if (path.startsWith(prefix)) { + String vfsFileName = path.substring(prefix.length()); + File rootDir = new File(getVfsDir(), "../../../.."); + File vfsSrcFile = new File(new File(rootDir, "src/test/resources/_vfs"), vfsFileName); + + if (vfsSrcFile.isFile()) { + path = vfsSrcFile.toURI().toString(); + path = StringHelper.normalizePath(path); + } + } + return path; + } + }; + } + }; + + if (debugger.isEnabled()) { + debugger.initialize(); + cleanup.appendOnCancelTask(debugger::destroy); + } + } + protected PsiFile configureByXLangText(String text) { return myFixture.configureByText("unit." + XLANG_EXT, text); } @@ -123,9 +162,13 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur } } + protected File getVfsDir() { + return new File(getClass().getResource("/_vfs").getFile()); + } + /** 将 vfs 测试资源全部复制到 Project 中 */ protected void addAllTestVfsResourcesToProject() { - File vfsDir = new File(getClass().getResource("/_vfs").getFile()); + File vfsDir = getVfsDir(); FileHelper.walk(vfsDir, (file) -> { if (file.isFile()) { -- Gitee From 18a39d67cd7b7763e31446ae6327ee5351ce60e9 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Wed, 30 Jul 2025 19:50:34 +0800 Subject: [PATCH 3/6] =?UTF-8?q?nop-idea-pugin:=20=E5=9C=A8=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E7=9A=84=E4=BE=9D=E8=B5=96=E4=B8=AD=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=20XLangDebugger=20=E6=98=AF=E5=90=A6=E5=AD=98=E5=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/debugger/XLangDebugProcess.java | 18 ++++++++------- .../plugin/debugger/XLangDebuggerRunner.java | 23 +++++++++++-------- .../messages/NopPluginBundle.properties | 3 ++- .../messages/NopPluginBundle_zh.properties | 3 ++- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugProcess.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugProcess.java index ab0eab456..5cd70f613 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugProcess.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebugProcess.java @@ -9,6 +9,7 @@ package io.nop.idea.plugin.debugger; import javax.swing.event.HyperlinkListener; +import com.intellij.debugger.DebugEnvironment; import com.intellij.debugger.engine.JavaDebugProcess; import com.intellij.debugger.impl.DebuggerSession; import com.intellij.debugger.impl.PrioritizedTask; @@ -36,6 +37,7 @@ import io.nop.api.debugger.IDebuggerAsync; import io.nop.api.debugger.LineLocation; import io.nop.commons.lang.impl.Cancellable; import io.nop.core.reflect.bean.BeanTool; +import io.nop.idea.plugin.messages.NopPluginBundle; import io.nop.idea.plugin.utils.ProjectFileHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -110,11 +112,10 @@ public class XLangDebugProcess extends JavaDebugProcess { indicator.setIndeterminate(true); try { - if (!connect()) { - return; + if (connect()) { + startDebugSession(); } - startDebugSession(); - } catch (final Exception e) { + } catch (Exception e) { onConnectFail(e.getMessage()); } } @@ -129,12 +130,12 @@ public class XLangDebugProcess extends JavaDebugProcess { LOG.info("xlang.debugger.connector-connected"); return true; - } catch (final Exception e) { + } catch (Exception e) { try { Thread.sleep(200); } catch (Exception ignored) { - } + if (getProcessHandler().isProcessTerminated()) { return false; } @@ -149,8 +150,9 @@ public class XLangDebugProcess extends JavaDebugProcess { private void onConnectFail(final String msg) { getProcessHandler().destroyProcess(); invokeLater(() -> { - String text = "XLangDebugger can't connect to DebuggerServer on port " - + connector.getDebugPort();//myDebuggerProxy.getPort(); + String text = NopPluginBundle.message("xlang.debugger.connect-fail", + String.valueOf(connector.getDebugPort())); + Messages.showErrorDialog(msg != null ? text + ":\r\n" + msg : text, "XLang Debugger"); }); } diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebuggerRunner.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebuggerRunner.java index 2051e60c0..00c080ecf 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebuggerRunner.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangDebuggerRunner.java @@ -23,8 +23,10 @@ import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.ui.RunContentDescriptor; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Computable; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.xdebugger.XDebugProcess; import com.intellij.xdebugger.XDebugProcessStarter; import com.intellij.xdebugger.XDebugSession; @@ -63,7 +65,13 @@ public class XLangDebuggerRunner extends GenericDebuggerRunner { protected RunContentDescriptor createContentDescriptor( @NotNull RunProfileState state, @NotNull ExecutionEnvironment environment ) throws ExecutionException { - if (noXLangDebuggerInProject(environment)) { + XLangRunConfigurationExtension extension = // + RunConfigurationExtension.EP_NAME.findExtensionOrFail(XLangRunConfigurationExtension.class); + + int xlangDebugPort = extension.getXLangDebugPort(); + DebugEnvironment debugEnvironment = extension.createDebugEnvironment(state, environment); + + if (notIncludeXLangDebugger(environment.getProject(), debugEnvironment)) { // Note: 确保 Messages#showErrorDialog 在事件分发线程 (EDT) 中调用, // 避免出现异常 "Access is allowed from Event Dispatch Thread (EDT) only" ApplicationManager.getApplication().invokeLater(() -> { @@ -72,12 +80,6 @@ public class XLangDebuggerRunner extends GenericDebuggerRunner { return null; } - XLangRunConfigurationExtension extension = // - RunConfigurationExtension.EP_NAME.findExtensionOrFail(XLangRunConfigurationExtension.class); - - int xlangDebugPort = extension.getXLangDebugPort(); - DebugEnvironment debugEnvironment = extension.createDebugEnvironment(state, environment); - return dispatch(()-> { // 实际上同时启动了java调试器和XLang调试器 XDebugSession session = XDebuggerManager.getInstance(environment.getProject()).startSession(environment, new XDebugProcessStarter() { @@ -100,12 +102,13 @@ public class XLangDebuggerRunner extends GenericDebuggerRunner { }); } - /** 当前项目是否未依赖 {@link XLangDebugger} */ - private boolean noXLangDebuggerInProject(@NotNull ExecutionEnvironment environment) { + /** 当前调试环境中是否未引入 {@link XLangDebugger} */ + private boolean notIncludeXLangDebugger(@NotNull Project project, @NotNull DebugEnvironment environment) { return ApplicationManager.getApplication().runReadAction((Computable) () -> { // Note: 避免出现异常 "Read access is allowed from inside read-action only" + GlobalSearchScope scope = environment.getSearchScope(); try { - return PsiClassHelper.findClass(environment.getProject(), XLangDebugger.class.getName()) == null; + return PsiClassHelper.findClass(project, XLangDebugger.class.getName(), scope) == null; } catch (Exception ignore) { // Note: 可能存在索引还未创建完毕的异常,这里直接忽略即可 return false; diff --git a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties b/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties index 7cb3c5db0..bdd1c9858 100644 --- a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties +++ b/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties @@ -36,4 +36,5 @@ xlang.doc.flag.allow-cp-expr = [#{var}] xlang.doc.markdown.link-title = '{'Link'}'{0} xlang.doc.markdown.image-title = '{'Image'}'{0} -xlang.debugger.not-exist = Current project doesn't depend on 'nop-xlang-debugger', please add dependency 'io.github.entropy-cloud:nop-xlang-debugger' to pom.xml or build.gradle.kts with 'test' scope +xlang.debugger.not-exist = Current module doesn't depend on 'nop-xlang-debugger', please add dependency 'io.github.entropy-cloud:nop-xlang-debugger' to pom.xml or build.gradle.kts with 'test' scope +xlang.debugger.connect-fail = Can''t connect to XLangDebugger on port {0} diff --git a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties b/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties index e056e81e2..42e122225 100644 --- a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties +++ b/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties @@ -36,4 +36,5 @@ xlang.doc.flag.allow-cp-expr = [#{var}] xlang.doc.markdown.link-title = '{'\u94FE\u63A5'}'{0} xlang.doc.markdown.image-title = '{'\u56FE\u7247'}'{0} -xlang.debugger.not-exist = \u5F53\u524D\u9879\u76EE\u672A\u4F9D\u8D56 'nop-xlang-debugger'\uFF0C\u8BF7\u6DFB\u52A0\u4F9D\u8D56 'io.github.entropy-cloud:nop-xlang-debugger' \u5230 pom.xml \u6216 build.gradle.kts \u6587\u4EF6\u4E2D\uFF0C\u5E76\u5C06 scope \u8BBE\u7F6E\u4E3A test +xlang.debugger.not-exist = \u5F53\u524D\u6A21\u5757\u672A\u4F9D\u8D56 'nop-xlang-debugger'\uFF0C\u8BF7\u6DFB\u52A0\u4F9D\u8D56 'io.github.entropy-cloud:nop-xlang-debugger' \u5230 pom.xml \u6216 build.gradle.kts \u6587\u4EF6\u4E2D\uFF0C\u5E76\u5C06 scope \u8BBE\u7F6E\u4E3A test +xlang.debugger.connect-fail = \u65E0\u6CD5\u4E0E XLangDebugger \u7684\u670D\u52A1\u7AEF\u53E3 {0} \u5EFA\u7ACB\u8FDE\u63A5 -- Gitee From 6d93b7e7f61677e6f104b203376fb6f7f4689c42 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Sat, 2 Aug 2025 21:24:09 +0800 Subject: [PATCH 4/6] =?UTF-8?q?nop-idea-pugin:=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E5=88=86=E6=9E=90=20xml=20=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E8=AF=86=E5=88=AB=E5=85=B6=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E4=B8=BA=20XLang=EF=BC=8C=E9=81=BF=E5=85=8D=E7=A9=B7=E4=B8=BE?= =?UTF-8?q?=E5=90=8E=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nop/idea/plugin/lang/XLangFileHelper.java | 29 ------- .../plugin/lang/XLangFileTypeDetector.java | 44 ---------- .../idea/plugin/lang/XLangIconProvider.java | 37 ++++++++ .../plugin/lang/XLangLanguageSubstitutor.java | 85 +++++++++++++++++++ .../src/main/resources/META-INF/plugin.xml | 18 ++-- .../lang/TestXLangFileTypeDetector.java | 82 +++++++++++++----- 6 files changed, 196 insertions(+), 99 deletions(-) delete mode 100644 nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangFileHelper.java delete mode 100644 nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangFileTypeDetector.java create mode 100644 nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangIconProvider.java create mode 100644 nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangLanguageSubstitutor.java diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangFileHelper.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangFileHelper.java deleted file mode 100644 index 6572e0787..000000000 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangFileHelper.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2017-2024 Nop Platform. All rights reserved. - * Author: canonical_entropy@163.com - * Blog: https://www.zhihu.com/people/canonical-entropy - * Gitee: https://gitee.com/canonical-entropy/nop-entropy - * Github: https://github.com/entropy-cloud/nop-entropy - */ -package io.nop.idea.plugin.lang; - -import io.nop.commons.io.stream.CharSequenceReader; -import io.nop.commons.util.CharSequenceHelper; -import io.nop.core.lang.xml.XNode; -import io.nop.core.lang.xml.parse.XRootNodeParser; -import io.nop.xlang.xdsl.XDslKeys; - -public class XLangFileHelper { - public static String getSchemaFromContent(CharSequence content) { - try { - if (!CharSequenceHelper.startsWith(content, "<")) - return null; - - XNode node = new XRootNodeParser().parseFromReader(null, new CharSequenceReader(content)); - XDslKeys keys = XDslKeys.of(node); - return (String) node.getAttr(keys.SCHEMA); - } catch (Exception e) { - return null; - } - } -} diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangFileTypeDetector.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangFileTypeDetector.java deleted file mode 100644 index 5cbbf292c..000000000 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangFileTypeDetector.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2017-2024 Nop Platform. All rights reserved. - * Author: canonical_entropy@163.com - * Blog: https://www.zhihu.com/people/canonical-entropy - * Gitee: https://gitee.com/canonical-entropy/nop-entropy - * Github: https://github.com/entropy-cloud/nop-entropy - */ -package io.nop.idea.plugin.lang; - -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeRegistry; -import com.intellij.openapi.util.io.ByteSequence; -import com.intellij.openapi.vfs.VirtualFile; -import io.nop.commons.util.StringHelper; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class XLangFileTypeDetector implements FileTypeRegistry.FileTypeDetector { - - @Override - public @Nullable FileType detect( - @NotNull VirtualFile file, @NotNull ByteSequence firstBytes, @Nullable CharSequence firstCharsIfText - ) { - if (firstCharsIfText == null) { - return null; - } - - String ext = file.getExtension(); - if (ext == null) { - return null; - } - - if (ext.equals("xdef") || ext.equals("xpl") || ext.equals("xgen") || ext.equals("xrun")) { - return XLangFileType.INSTANCE; - } - - String schema = XLangFileHelper.getSchemaFromContent(firstCharsIfText); - if (!StringHelper.isEmpty(schema)) { - return XLangFileType.INSTANCE; - } - - return null; - } -} diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangIconProvider.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangIconProvider.java new file mode 100644 index 000000000..929be82af --- /dev/null +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangIconProvider.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017-2025 Nop Platform. All rights reserved. + * Author: canonical_entropy@163.com + * Blog: https://www.zhihu.com/people/canonical-entropy + * Gitee: https://gitee.com/canonical-entropy/nop-entropy + * Github: https://github.com/entropy-cloud/nop-entropy + */ + +package io.nop.idea.plugin.lang; + +import javax.swing.*; + +import com.intellij.ide.IconProvider; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * 根据实际的文件语言类型获取文件图标 + *

+ * 适用于对 {@link XLangLanguageSubstitutor} 从 xml 中识别出的 XLang + * 的文件图标进行修改 + * + * @author flytreeleft + * @date 2025-08-02 + */ +public class XLangIconProvider extends IconProvider { + + @Override + public @Nullable Icon getIcon(@NotNull PsiElement element, int flags) { + if (element instanceof PsiFile f && f.getLanguage() == XLangLanguage.INSTANCE) { + return f.getLanguage().getAssociatedFileType().getIcon(); + } + return null; + } +} diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangLanguageSubstitutor.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangLanguageSubstitutor.java new file mode 100644 index 000000000..f1ba54323 --- /dev/null +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangLanguageSubstitutor.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017-2025 Nop Platform. All rights reserved. + * Author: canonical_entropy@163.com + * Blog: https://www.zhihu.com/people/canonical-entropy + * Gitee: https://gitee.com/canonical-entropy/nop-entropy + * Github: https://github.com/entropy-cloud/nop-entropy + */ + +package io.nop.idea.plugin.lang; + +import java.io.InputStreamReader; +import java.io.Reader; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.intellij.lang.Language; +import com.intellij.lang.xml.XMLLanguage; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.ArchiveFileSystem; +import com.intellij.psi.LanguageSubstitutor; +import io.nop.commons.io.stream.FastBufferedReader; +import io.nop.core.lang.xml.XNode; +import io.nop.core.lang.xml.parse.XRootNodeParser; +import io.nop.xlang.xdsl.XDslKeys; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 根据 XML 中的特定结构(即包含 xmlns:x 和 x:schema 属性)动态识别其是否为 XLang + * + * @author flytreeleft + * @date 2025-08-02 + */ +public class XLangLanguageSubstitutor extends LanguageSubstitutor { + private static final Logger LOG = LoggerFactory.getLogger(XLangLanguageSubstitutor.class); + + private static final Language LANG_NULL = new Language("NULL") {}; + + private final Cache cached = Caffeine.newBuilder().maximumSize(500).build(); + + @Override + public @Nullable Language getLanguage(@NotNull VirtualFile file, @NotNull Project project) { + String ext = file.getExtension(); + if ("xsd".equalsIgnoreCase(ext) // + || file.getName().equals("pom.xml") // + ) { + return null; + } + + Language lang; + // Note: 对于只读文件,缓存结果,避免重复读取 + if (file.getFileSystem() instanceof ArchiveFileSystem) { + lang = cached.get(file.hashCode(), (k) -> getLanguage(file)); + } else { + lang = getLanguage(file); + } + + return lang == LANG_NULL ? null : lang; + } + + public Language getLanguage(@NotNull VirtualFile file) { + LOG.debug("Try to detect XLang from file {}", file); + + try (InputStreamReader reader = new InputStreamReader(file.getInputStream(), file.getCharset())) { + return isXLangFile(reader) // + ? XLangLanguage.INSTANCE + // Note: 由于是按内容动态确定,故而,需在非 XLang 时,返回原始语言 + : XMLLanguage.INSTANCE; + } catch (Exception ignore) { + return LANG_NULL; + } + } + + /** 根据 xml 内容做精确判断 */ + private boolean isXLangFile(Reader reader) { + // Note: 仅分析根节点 + XNode node = new XRootNodeParser().parseFromReader(null, new FastBufferedReader(reader)); + XDslKeys keys = XDslKeys.of(node); + + return node.hasAttr(keys.SCHEMA); + } +} diff --git a/nop-idea-plugin/src/main/resources/META-INF/plugin.xml b/nop-idea-plugin/src/main/resources/META-INF/plugin.xml index 7710c07aa..f69b4aa26 100644 --- a/nop-idea-plugin/src/main/resources/META-INF/plugin.xml +++ b/nop-idea-plugin/src/main/resources/META-INF/plugin.xml @@ -76,24 +76,28 @@ - - + + extensions="xdef;xpl;xgen;xui;xlib;xrun;xwf;xmeta;xpage;xrule"/> + + + + - + - + - + diff --git a/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangFileTypeDetector.java b/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangFileTypeDetector.java index 1a96e09e1..26759d3fb 100644 --- a/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangFileTypeDetector.java +++ b/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangFileTypeDetector.java @@ -15,28 +15,72 @@ import org.junit.Assert; public class TestXLangFileTypeDetector extends BaseXLangPluginTestCase { - public void testXLangFileType() { - assertFileType("my.wf.xml", // - """ + public void testDetectXLangByExtension() { + String[] extensions = "xdef;xpl;xgen;xui;xlib;xrun;xwf;xmeta;xpage;xrule".split(";"); + + for (String ext : extensions) { + assertFileType("a." + ext, "", XLangFileType.INSTANCE); + } + } + + public void testDetectXLangFromXml() { + String[] samples = new String[] { + "", // + """ + + """, // + """ + + """, // + """ + + """, // + """ + + """, // + """ + + """, // + """ + + """, // + """ + + """, // + }; + for (String text : samples) { + assertFileType("a.wf.xml", text, XmlFileType.INSTANCE); + } + + samples = new String[] { + """ + + """, // + """ + + """, // + """ """, // - XmlFileType.INSTANCE // - ); - - assertFileType("xlib.register-model.xml", // - """ - - - - - - """, // - XLangFileType.INSTANCE // - ); + """ + + + """, // + """ + + """, // + """ + + + """, // + """ + + """, // + }; + for (String text : samples) { + assertFileType("b.wf.xml", text, XLangFileType.INSTANCE); + } } private void assertFileType(String fileName, String text, LanguageFileType expectedFileType) { -- Gitee From 738f253013dbf82c3a613b09a0fc003822a97ed2 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Sun, 3 Aug 2025 22:01:25 +0800 Subject: [PATCH 5/6] =?UTF-8?q?nop-idea-pugin:=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96=E6=B6=88=E6=81=AF=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/debugger/XLangBreakpointType.java | 2 +- .../idea/plugin/messages/NopPluginBundle.java | 55 +++++-------------- .../messages/NopPluginBundle.properties | 8 ++- .../messages/NopPluginBundle_zh.properties | 8 ++- 4 files changed, 29 insertions(+), 44 deletions(-) rename nop-idea-plugin/src/main/resources/{io/nop/idea/plugin => }/messages/NopPluginBundle.properties (90%) rename nop-idea-plugin/src/main/resources/{io/nop/idea/plugin => }/messages/NopPluginBundle_zh.properties (92%) diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangBreakpointType.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangBreakpointType.java index 5010d280f..e9617d28f 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangBreakpointType.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/debugger/XLangBreakpointType.java @@ -27,7 +27,7 @@ import org.jetbrains.annotations.Nullable; public class XLangBreakpointType extends XLineBreakpointType { public XLangBreakpointType() { - super("xlang-line", NopPluginBundle.message("line.breakpoints.tab.title")); + super("xlang-line", NopPluginBundle.message("xlang.debugger.line.breakpoints.tab.title")); } @Override diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/messages/NopPluginBundle.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/messages/NopPluginBundle.java index 506b9f728..e06a67b4b 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/messages/NopPluginBundle.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/messages/NopPluginBundle.java @@ -7,55 +7,28 @@ */ package io.nop.idea.plugin.messages; -import com.intellij.BundleBase; -import com.intellij.reference.SoftReference; +import java.util.function.Supplier; + +import com.intellij.DynamicBundle; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.PropertyKey; -import java.lang.ref.Reference; -import java.util.ResourceBundle; - public class NopPluginBundle { - @NonNls - private static final String BUNDLE = "io.nop.idea.plugin.messages.NopPluginBundle"; - private static Reference ourBundle; - - public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, @NotNull Object... params) { - return message(getBundle(), key, params); - } - - public static String key(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key) { - return getBundle().getString(key); - } + public static final @NonNls String BUNDLE = "messages.NopPluginBundle"; - private NopPluginBundle() { - } - - @NotNull - private static ResourceBundle getBundle() { - ResourceBundle bundle = SoftReference.dereference(ourBundle); - if (bundle == null) { - bundle = ResourceBundle.getBundle(BUNDLE); - ourBundle = new java.lang.ref.SoftReference(bundle); - } - - return bundle; - } - - public static String messageOrDefault(@Nullable ResourceBundle bundle, @NotNull String key, @Nullable String defaultValue, @NotNull Object... params) { - return BundleBase.messageOrDefault(bundle, key, defaultValue, params); - } + private static final DynamicBundle INSTANCE = new DynamicBundle(NopPluginBundle.class, BUNDLE); - @NotNull - public static String message(@NotNull ResourceBundle bundle, @NotNull String key, @NotNull Object... params) { - return BundleBase.message(bundle, key, params); + public static @NotNull @Nls String message( + @NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params + ) { + return INSTANCE.getMessage(key, params); } - @Nullable - public static String messageOfNull(@NotNull ResourceBundle bundle, @NotNull String key, @NotNull Object... params) { - String value = messageOrDefault(bundle, key, key, params); - return key.equals(value) ? null : value; + public static @NotNull Supplier<@Nls String> messagePointer( + @NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params + ) { + return INSTANCE.getLazyMessage(key, params); } } diff --git a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties b/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties similarity index 90% rename from nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties rename to nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties index bdd1c9858..e9935be2b 100644 --- a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle.properties +++ b/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties @@ -1,4 +1,9 @@ -line.breakpoints.tab.title = XLang line breakpoints +group.NopEntropy.XLang.NewGroup.text = Nop XLang +action.NopEntropy.XLang.NewDef.text = XLang Def +action.NopEntropy.XLang.NewDef.title = New XLang Def +action.NopEntropy.XLang.NewDSL.text = XLang DSL +action.NopEntropy.XLang.NewDSL.title = New XLang DSL + xlang.annotation.attr.not-defined = Undefined attribute ''{0}'' xlang.annotation.attr.value-required = Attribute ''{0}'' can''t have empty value xlang.annotation.tag.not-defined = Undefined tag ''{0}'' @@ -36,5 +41,6 @@ xlang.doc.flag.allow-cp-expr = [#{var}] xlang.doc.markdown.link-title = '{'Link'}'{0} xlang.doc.markdown.image-title = '{'Image'}'{0} +xlang.debugger.line.breakpoints.tab.title = XLang Line Breakpoints xlang.debugger.not-exist = Current module doesn't depend on 'nop-xlang-debugger', please add dependency 'io.github.entropy-cloud:nop-xlang-debugger' to pom.xml or build.gradle.kts with 'test' scope xlang.debugger.connect-fail = Can''t connect to XLangDebugger on port {0} diff --git a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties b/nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties similarity index 92% rename from nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties rename to nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties index 42e122225..a4104325d 100644 --- a/nop-idea-plugin/src/main/resources/io/nop/idea/plugin/messages/NopPluginBundle_zh.properties +++ b/nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties @@ -1,4 +1,9 @@ -line.breakpoints.tab.title = XLang line breakpoints +group.NopEntropy.XLang.NewGroup.text = Nop XLang +action.NopEntropy.XLang.NewDef.text = XLang Def +action.NopEntropy.XLang.NewDef.title = \u65B0\u5EFA XLang Def +action.NopEntropy.XLang.NewDSL.text = XLang DSL +action.NopEntropy.XLang.NewDSL.title = \u65B0\u5EFA XLang DSL + xlang.annotation.attr.not-defined = \u5C5E\u6027 ''{0}'' \u672A\u5B9A\u4E49 xlang.annotation.attr.value-required = \u5C5E\u6027 ''{0}'' \u7684\u503C\u4E0D\u5141\u8BB8\u4E3A\u7A7A xlang.annotation.tag.not-defined = \u6807\u7B7E ''{0}'' \u672A\u5B9A\u4E49 @@ -36,5 +41,6 @@ xlang.doc.flag.allow-cp-expr = [#{var}] xlang.doc.markdown.link-title = '{'\u94FE\u63A5'}'{0} xlang.doc.markdown.image-title = '{'\u56FE\u7247'}'{0} +xlang.debugger.line.breakpoints.tab.title = XLang \u884C\u65AD\u70B9 xlang.debugger.not-exist = \u5F53\u524D\u6A21\u5757\u672A\u4F9D\u8D56 'nop-xlang-debugger'\uFF0C\u8BF7\u6DFB\u52A0\u4F9D\u8D56 'io.github.entropy-cloud:nop-xlang-debugger' \u5230 pom.xml \u6216 build.gradle.kts \u6587\u4EF6\u4E2D\uFF0C\u5E76\u5C06 scope \u8BBE\u7F6E\u4E3A test xlang.debugger.connect-fail = \u65E0\u6CD5\u4E0E XLangDebugger \u7684\u670D\u52A1\u7AEF\u53E3 {0} \u5EFA\u7ACB\u8FDE\u63A5 -- Gitee From 2f0ea069f50312f5daaa78f5fa6f3e34ba1b5ed5 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Wed, 6 Aug 2025 20:50:54 +0800 Subject: [PATCH 6/6] =?UTF-8?q?nop-idea-pugin:=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8C=89=E7=85=A7=E6=A8=A1=E6=9D=BF=E6=96=B0=E5=BB=BA=20xdef?= =?UTF-8?q?=20=E5=92=8C=20xdsl=20=E6=96=87=E4=BB=B6=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E5=8F=AF=E5=9C=A8=E7=A9=BA=E7=99=BD=20XLang=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E8=BE=93=E5=85=A5=20xschema=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E8=87=AA=E5=8A=A8=E5=A1=AB=E5=85=85=E6=A0=B9=E8=8A=82?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/nop/idea/plugin/icons/NopIcons.java | 6 +- .../plugin/lang/XLangLanguageSubstitutor.java | 6 +- .../CreateXLangFileFromTemplateAction.java | 62 +++++++++++++ .../CreateXLangFileFromTemplateHandler.java | 92 ++++++++++++++++++ .../XLangFileLiveTemplateContextType.java | 50 ++++++++++ .../template/XLangRootTagNameMacro.java | 93 +++++++++++++++++++ .../plugin/template/XLangSchemaPathMacro.java | 58 ++++++++++++ .../plugin/utils/LookupElementHelper.java | 4 + .../idea/plugin/utils/ProjectFileHelper.java | 3 +- .../nop/idea/plugin/utils/XmlPsiHelper.java | 20 +++- .../src/main/resources/META-INF/plugin.xml | 26 +++++- .../internal/New_XLang_XDSL.xdsl.ft | 6 ++ .../internal/New_XLang_XDef.xdef.ft | 11 +++ .../resources/icons/{type.svg => xlang.svg} | 0 .../{actions => icons}/xlangDebug-small.svg | 0 .../{actions => icons}/xlangDebug.svg | 0 .../resources/liveTemplates/XLang_File.xml | 17 ++++ .../messages/NopPluginBundle.properties | 9 +- .../messages/NopPluginBundle_zh.properties | 9 +- 19 files changed, 451 insertions(+), 21 deletions(-) create mode 100644 nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/CreateXLangFileFromTemplateAction.java create mode 100644 nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/CreateXLangFileFromTemplateHandler.java create mode 100644 nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangFileLiveTemplateContextType.java create mode 100644 nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangRootTagNameMacro.java create mode 100644 nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangSchemaPathMacro.java create mode 100644 nop-idea-plugin/src/main/resources/fileTemplates/internal/New_XLang_XDSL.xdsl.ft create mode 100644 nop-idea-plugin/src/main/resources/fileTemplates/internal/New_XLang_XDef.xdef.ft rename nop-idea-plugin/src/main/resources/icons/{type.svg => xlang.svg} (100%) rename nop-idea-plugin/src/main/resources/{actions => icons}/xlangDebug-small.svg (100%) rename nop-idea-plugin/src/main/resources/{actions => icons}/xlangDebug.svg (100%) create mode 100644 nop-idea-plugin/src/main/resources/liveTemplates/XLang_File.xml diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/icons/NopIcons.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/icons/NopIcons.java index 6b5708a83..a1c3d2039 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/icons/NopIcons.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/icons/NopIcons.java @@ -25,12 +25,12 @@ import static com.intellij.openapi.util.IconLoader.getIcon; */ public class NopIcons { /** 文件类型图标: 16x16 */ - public static final Icon XLangFileType = getIcon("/icons/type.svg", NopIcons.class); + public static final Icon XLangFileType = getIcon("/icons/xlang.svg", NopIcons.class); /** 底部工具栏的图标: 13x13 */ - public static final Icon Tool_XLangDebug = getIcon("/actions/xlangDebug-small.svg", NopIcons.class); + public static final Icon Tool_XLangDebug = getIcon("/icons/xlangDebug-small.svg", NopIcons.class); /** 顶部调试启动按钮图标: 16x16 */ - public static final Icon Action_XLangDebug = getIcon("/actions/xlangDebug.svg", NopIcons.class); + public static final Icon Action_XLangDebug = getIcon("/icons/xlangDebug.svg", NopIcons.class); private NopIcons() { } diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangLanguageSubstitutor.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangLanguageSubstitutor.java index f1ba54323..957beb0e0 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangLanguageSubstitutor.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangLanguageSubstitutor.java @@ -77,9 +77,13 @@ public class XLangLanguageSubstitutor extends LanguageSubstitutor { /** 根据 xml 内容做精确判断 */ private boolean isXLangFile(Reader reader) { // Note: 仅分析根节点 - XNode node = new XRootNodeParser().parseFromReader(null, new FastBufferedReader(reader)); + XNode node = parseRootNode(reader); XDslKeys keys = XDslKeys.of(node); return node.hasAttr(keys.SCHEMA); } + + public static XNode parseRootNode(Reader reader) { + return new XRootNodeParser().parseFromReader(null, new FastBufferedReader(reader)); + } } diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/CreateXLangFileFromTemplateAction.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/CreateXLangFileFromTemplateAction.java new file mode 100644 index 000000000..2652dfc22 --- /dev/null +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/CreateXLangFileFromTemplateAction.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017-2025 Nop Platform. All rights reserved. + * Author: canonical_entropy@163.com + * Blog: https://www.zhihu.com/people/canonical-entropy + * Gitee: https://gitee.com/canonical-entropy/nop-entropy + * Github: https://github.com/entropy-cloud/nop-entropy + */ + +package io.nop.idea.plugin.template; + +import com.intellij.ide.actions.CreateFileFromTemplateAction; +import com.intellij.ide.actions.CreateFileFromTemplateDialog; +import com.intellij.ide.actions.CreateTemplateInPackageAction; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.NlsContexts; +import com.intellij.psi.PsiDirectory; +import io.nop.idea.plugin.lang.XLangFileType; +import io.nop.idea.plugin.messages.NopPluginBundle; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; + +/** + * 配置 XLang 文件创建窗口 + * + * @author flytreeleft + * @date 2025-08-03 + */ +public class CreateXLangFileFromTemplateAction extends CreateFileFromTemplateAction { + // Note: 模板名不包含后缀 .xdef.ft + public static final String TEMPLATE_XDEF_NAME = "New_XLang_XDef"; + public static final String TEMPLATE_XDSL_NAME = "New_XLang_XDSL"; + + @Override + protected void buildDialog( + @NotNull Project project, @NotNull PsiDirectory directory, + @NotNull CreateFileFromTemplateDialog.Builder builder + ) { + builder.setTitle(NopPluginBundle.message("action.NopEntropy.NewXLang.title")); + + builder.addKind("XLang xdef", XLangFileType.INSTANCE.getIcon(), TEMPLATE_XDEF_NAME); + builder.addKind("XLang DSL", XLangFileType.INSTANCE.getIcon(), TEMPLATE_XDSL_NAME); + } + + @Override + protected @NlsContexts.Command String getActionName( + PsiDirectory directory, @NonNls @NotNull String newName, @NonNls String templateName + ) { + return NopPluginBundle.message("action.NopEntropy.NewXLang.text"); + } + + @Override + protected boolean isAvailable(final DataContext dataContext) { + return CreateTemplateInPackageAction.isAvailable(dataContext, JavaModuleSourceRootTypes.RESOURCES, (d) -> true); + } + + @Override + protected @NotNull PsiDirectory adjustDirectory(@NotNull PsiDirectory directory) { + return CreateTemplateInPackageAction.adjustDirectory(directory, JavaModuleSourceRootTypes.RESOURCES); + } +} diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/CreateXLangFileFromTemplateHandler.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/CreateXLangFileFromTemplateHandler.java new file mode 100644 index 000000000..9ab0ec899 --- /dev/null +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/CreateXLangFileFromTemplateHandler.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2017-2025 Nop Platform. All rights reserved. + * Author: canonical_entropy@163.com + * Blog: https://www.zhihu.com/people/canonical-entropy + * Gitee: https://gitee.com/canonical-entropy/nop-entropy + * Github: https://github.com/entropy-cloud/nop-entropy + */ + +package io.nop.idea.plugin.template; + +import java.util.Map; + +import com.intellij.ide.fileTemplates.DefaultCreateFromTemplateHandler; +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.FileTypeRegistry; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; +import io.nop.commons.util.StringHelper; +import io.nop.idea.plugin.lang.XLangFileType; +import io.nop.idea.plugin.messages.NopPluginBundle; +import org.jetbrains.annotations.NotNull; + +/** + * 用于为模板准备变量,并可控制从模板内容到 {@link com.intellij.psi.PsiElement} 的转换过程 + * + * @author flytreeleft + * @date 2025-08-03 + */ +public class CreateXLangFileFromTemplateHandler extends DefaultCreateFromTemplateHandler { + + @Override + public boolean handlesTemplate(@NotNull FileTemplate template) { + return template.isTemplateOfType(XLangFileType.INSTANCE) // + && ArrayUtil.contains(template.getName(), + CreateXLangFileFromTemplateAction.TEMPLATE_XDSL_NAME, + CreateXLangFileFromTemplateAction.TEMPLATE_XDEF_NAME); + } + + @Override + public @NotNull PsiElement createFromTemplate( + @NotNull Project project, @NotNull PsiDirectory directory, String fileName, @NotNull FileTemplate template, + @NotNull String templateText, @NotNull Map props + ) throws IncorrectOperationException { + FileType type = FileTypeRegistry.getInstance().getFileTypeByFileName(fileName); + if (type != XLangFileType.INSTANCE) { + String ext = StringHelper.fileExt(fileName); + FileTypeManager.getInstance().associateExtension(XLangFileType.INSTANCE, ext); + } + + return super.createFromTemplate(project, directory, fileName, template, templateText, props); + } + + @Override + public void prepareProperties( + @NotNull Map props, String filename, // + @NotNull FileTemplate template, @NotNull Project project + ) { + if (template.getExtension().equals("xdef")) { + String name = StringHelper.removeLastPart(filename, '.'); + if (name.isEmpty()) { + name = filename; + } + name = name.replace('.', '-'); + + props.put("NODE_ROOT_NAME", name); + } + + super.prepareProperties(props, filename, template, project); + } + + @Override + protected String checkAppendExtension(String fileName, @NotNull FileTemplate template) { + if (!StringHelper.isValidFileName(fileName)) { + throw new IncorrectOperationException(NopPluginBundle.message("action.error.invalid-filename")); + } + + if (template.getExtension().equals("xdsl")) { + String ext = StringHelper.fileExt(fileName); + + if (ext.isEmpty()) { + throw new IncorrectOperationException(NopPluginBundle.message("action.error.no-xlang-file-extension")); + } + return fileName; + } + return super.checkAppendExtension(fileName, template); + } +} diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangFileLiveTemplateContextType.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangFileLiveTemplateContextType.java new file mode 100644 index 000000000..8677323d7 --- /dev/null +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangFileLiveTemplateContextType.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017-2025 Nop Platform. All rights reserved. + * Author: canonical_entropy@163.com + * Blog: https://www.zhihu.com/people/canonical-entropy + * Gitee: https://gitee.com/canonical-entropy/nop-entropy + * Github: https://github.com/entropy-cloud/nop-entropy + */ + +package io.nop.idea.plugin.template; + +import com.intellij.codeInsight.template.TemplateActionContext; +import com.intellij.codeInsight.template.TemplateContextType; +import com.intellij.ide.highlighter.XmlFileType; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiErrorElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlDocument; +import io.nop.idea.plugin.lang.XLangFileType; +import org.jetbrains.annotations.NotNull; + +/** + * @author flytreeleft + * @date 2025-08-06 + */ +public class XLangFileLiveTemplateContextType extends TemplateContextType { + + protected XLangFileLiveTemplateContextType() { + super("XLang"); + } + + @Override + public boolean isInContext(@NotNull TemplateActionContext templateActionContext) { + PsiFile file = templateActionContext.getFile(); + int startOffset = templateActionContext.getStartOffset(); + + if (file.getFileType() != XLangFileType.INSTANCE && file.getFileType() != XmlFileType.INSTANCE) { + return false; + } + + PsiElement element = file.findElementAt(startOffset); + if (element != null && element.getParent() instanceof PsiErrorElement e) { + if (e.getParent() instanceof XmlDocument doc) { + return doc.getRootTag() == null // + || (doc.getRootTag().getName().isEmpty() // + && doc.getRootTag().getAttributes().length == 0); + } + } + return false; + } +} diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangRootTagNameMacro.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangRootTagNameMacro.java new file mode 100644 index 000000000..84c5b6b27 --- /dev/null +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangRootTagNameMacro.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017-2025 Nop Platform. All rights reserved. + * Author: canonical_entropy@163.com + * Blog: https://www.zhihu.com/people/canonical-entropy + * Gitee: https://gitee.com/canonical-entropy/nop-entropy + * Github: https://github.com/entropy-cloud/nop-entropy + */ + +package io.nop.idea.plugin.template; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.intellij.codeInsight.template.Expression; +import com.intellij.codeInsight.template.ExpressionContext; +import com.intellij.codeInsight.template.Macro; +import com.intellij.codeInsight.template.Result; +import com.intellij.codeInsight.template.TemplateContextType; +import com.intellij.codeInsight.template.TextResult; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.vfs.VirtualFile; +import io.nop.commons.util.StringHelper; +import io.nop.core.lang.xml.XNode; +import io.nop.core.resource.IResource; +import io.nop.core.resource.VirtualFileSystem; +import io.nop.idea.plugin.lang.XLangFileType; +import io.nop.idea.plugin.lang.XLangLanguageSubstitutor; +import io.nop.idea.plugin.resource.ProjectEnv; +import io.nop.idea.plugin.utils.XmlPsiHelper; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author flytreeleft + * @date 2025-08-06 + */ +public class XLangRootTagNameMacro extends Macro { + private static final Pattern REGEX_SCHEMA = Pattern.compile(".+ \\S+:schema=\"([^\"]+)\".+", + Pattern.DOTALL | Pattern.MULTILINE); + + @Override + public @NonNls String getName() { + return "rootTagName"; + } + + @Override + public @Nullable Result calculateResult(Expression @NotNull [] params, ExpressionContext context) { + Editor editor = context.getEditor(); + VirtualFile file = editor != null ? editor.getVirtualFile() : null; + if (file == null) { + return null; + } + + String text = editor.getDocument().getText(); + Matcher matcher = REGEX_SCHEMA.matcher(text); + if (!matcher.matches()) { + return null; + } + + String vfsPath = matcher.group(1); + if (StringHelper.isBlank(vfsPath) || !vfsPath.endsWith(".xdef")) { + return null; + } + + String resourcePath = XmlPsiHelper.getNopVfsAbsolutePath(vfsPath, file); + String charset = XLangFileType.INSTANCE.getCharset(file, text.getBytes()); + + String tagName = ProjectEnv.withProject(context.getProject(), () -> { + IResource resource = VirtualFileSystem.instance().getResource(resourcePath); + XNode node = XLangLanguageSubstitutor.parseRootNode(resource.getReader(charset)); + if (node == null) { + return null; + } + + String name = node.getTagName(); + if (name.endsWith(":unknown-tag")) { + name = file.getName(); + + int index = name.indexOf('.'); + name = index <= 0 ? name : name.substring(0, index); + } + return name; + }); + + return tagName != null ? new TextResult(tagName) : null; + } + + @Override + public boolean isAcceptableInContext(TemplateContextType context) { + return context instanceof XLangFileLiveTemplateContextType; + } +} diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangSchemaPathMacro.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangSchemaPathMacro.java new file mode 100644 index 000000000..ca9613f8c --- /dev/null +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangSchemaPathMacro.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017-2025 Nop Platform. All rights reserved. + * Author: canonical_entropy@163.com + * Blog: https://www.zhihu.com/people/canonical-entropy + * Gitee: https://gitee.com/canonical-entropy/nop-entropy + * Github: https://github.com/entropy-cloud/nop-entropy + */ + +package io.nop.idea.plugin.template; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.template.Expression; +import com.intellij.codeInsight.template.ExpressionContext; +import com.intellij.codeInsight.template.Macro; +import com.intellij.codeInsight.template.Result; +import com.intellij.codeInsight.template.TemplateContextType; +import com.intellij.openapi.project.Project; +import io.nop.idea.plugin.utils.LookupElementHelper; +import io.nop.idea.plugin.utils.ProjectFileHelper; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author flytreeleft + * @date 2025-08-06 + */ +public class XLangSchemaPathMacro extends Macro { + + @Override + public @NonNls String getName() { + return "schemaPath"; + } + + @Override + public @Nullable Result calculateResult(Expression @NotNull [] params, ExpressionContext context) { + return null; + } + + @Override + public LookupElement @Nullable [] calculateLookupItems(Expression @NotNull [] params, ExpressionContext context) { + // Note: TODO 全项目搜索的性能极差,暂时不启用补全 +// Project project = context.getProject(); +// +// return ProjectFileHelper.findAllNopVfsPaths(project) +// .stream() +// .sorted() +// .filter(path -> path.endsWith(".xdef")) +// .map(LookupElementHelper::lookupString) +// .toArray(LookupElement[]::new); + return LookupElement.EMPTY_ARRAY; + } + + @Override + public boolean isAcceptableInContext(TemplateContextType context) { + return context instanceof XLangFileLiveTemplateContextType; + } +} diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/LookupElementHelper.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/LookupElementHelper.java index 3db8a015f..fd3443d2c 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/LookupElementHelper.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/LookupElementHelper.java @@ -59,6 +59,10 @@ public class LookupElementHelper { ; } + public static LookupElement lookupString(String s) { + return LookupElementBuilder.create(s).withIcon(PlatformIcons.ENUM_ICON); + } + public static StreamEx lookupPsiPackagesStream(StreamEx stream) { return lookupPsiPackagesStream(stream, (p) -> LookupElementBuilder.create(p) diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/ProjectFileHelper.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/ProjectFileHelper.java index 1f4043c8a..d738e4555 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/ProjectFileHelper.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/ProjectFileHelper.java @@ -135,7 +135,8 @@ public class ProjectFileHelper { GlobalSearchScope scope = GlobalSearchScope.allScope(project); FilenameIndex.processFilesByNames(names, true, scope, null, (file) -> { String vfsPath = getNopVfsPath(file); - if (vfsPath != null) { + + if (!file.isDirectory() && vfsPath != null) { vfsPaths.add(vfsPath); } return true; diff --git a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XmlPsiHelper.java b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XmlPsiHelper.java index 1247ff901..e2719c7fe 100644 --- a/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XmlPsiHelper.java +++ b/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XmlPsiHelper.java @@ -54,9 +54,13 @@ public class XmlPsiHelper { } VirtualFile vf = file.getVirtualFile(); + return getNopVfsPath(vf, file); + } + + private static String getNopVfsPath(VirtualFile vf, PsiFile file) { // Note: 在编辑过程中得到的 VirtualFile 可能为 null,需尝试通过 // PsiFile#getOriginalFile 获得 VirtualFile - if (vf == null && file.getOriginalFile() != file) { + if (vf == null && file != null && file.getOriginalFile() != file) { vf = file.getOriginalFile().getVirtualFile(); } @@ -64,8 +68,8 @@ public class XmlPsiHelper { } /** - * 获取 path 的 vfs 绝对路径。 - * 若 path 为相对路径,则视为其相对于 element 所在文件的目录 + * 获取 {@code path} 的 vfs 绝对路径。 + * 若 {@code path} 为相对路径,则视为其相对于 {@code element} 所在文件的目录 */ public static String getNopVfsAbsolutePath(String path, PsiElement element) { String filePath = getNopVfsPath(element); @@ -73,6 +77,16 @@ public class XmlPsiHelper { return StringHelper.absolutePath(filePath, path); } + /** + * 获取 {@code path} 的 vfs 绝对路径。 + * 若 {@code path} 为相对路径,则视为其相对于 {@code vf} 所在文件的目录 + */ + public static String getNopVfsAbsolutePath(String path, VirtualFile vf) { + String filePath = getNopVfsPath(vf, null); + + return StringHelper.absolutePath(filePath, path); + } + public static List findPsiFileList(Project project, String path) { String fileName = StringHelper.fileFullName(path); Collection vfList = FilenameIndex.getVirtualFilesByName(fileName, diff --git a/nop-idea-plugin/src/main/resources/META-INF/plugin.xml b/nop-idea-plugin/src/main/resources/META-INF/plugin.xml index f69b4aa26..aa49b3306 100644 --- a/nop-idea-plugin/src/main/resources/META-INF/plugin.xml +++ b/nop-idea-plugin/src/main/resources/META-INF/plugin.xml @@ -5,8 +5,10 @@ Canonical +

  • 根据 xdef 元模型定义来校验 dsl 文件格式;
  • +
  • 为 Xpl 模板语言提供调试功能;
  • + ]]> com.intellij.modules.lang --> + messages.NopPluginBundle + @@ -44,6 +48,11 @@ + + + + @@ -75,13 +84,24 @@ + + + + + + + + + + + extensions="xdef;xdsl;xpl;xgen;xui;xlib;xrun;xwf;xmeta;xpage;xrule"/> diff --git a/nop-idea-plugin/src/main/resources/fileTemplates/internal/New_XLang_XDSL.xdsl.ft b/nop-idea-plugin/src/main/resources/fileTemplates/internal/New_XLang_XDSL.xdsl.ft new file mode 100644 index 000000000..e08a008b4 --- /dev/null +++ b/nop-idea-plugin/src/main/resources/fileTemplates/internal/New_XLang_XDSL.xdsl.ft @@ -0,0 +1,6 @@ + + + diff --git a/nop-idea-plugin/src/main/resources/fileTemplates/internal/New_XLang_XDef.xdef.ft b/nop-idea-plugin/src/main/resources/fileTemplates/internal/New_XLang_XDef.xdef.ft new file mode 100644 index 000000000..e0581ddfb --- /dev/null +++ b/nop-idea-plugin/src/main/resources/fileTemplates/internal/New_XLang_XDef.xdef.ft @@ -0,0 +1,11 @@ + + + +<${NODE_ROOT_NAME} xmlns:x="/nop/schema/xdsl.xdef" xmlns:xdef="/nop/schema/xdef.xdef" + x:schema="/nop/schema/xdef.xdef" +> + + diff --git a/nop-idea-plugin/src/main/resources/icons/type.svg b/nop-idea-plugin/src/main/resources/icons/xlang.svg similarity index 100% rename from nop-idea-plugin/src/main/resources/icons/type.svg rename to nop-idea-plugin/src/main/resources/icons/xlang.svg diff --git a/nop-idea-plugin/src/main/resources/actions/xlangDebug-small.svg b/nop-idea-plugin/src/main/resources/icons/xlangDebug-small.svg similarity index 100% rename from nop-idea-plugin/src/main/resources/actions/xlangDebug-small.svg rename to nop-idea-plugin/src/main/resources/icons/xlangDebug-small.svg diff --git a/nop-idea-plugin/src/main/resources/actions/xlangDebug.svg b/nop-idea-plugin/src/main/resources/icons/xlangDebug.svg similarity index 100% rename from nop-idea-plugin/src/main/resources/actions/xlangDebug.svg rename to nop-idea-plugin/src/main/resources/icons/xlangDebug.svg diff --git a/nop-idea-plugin/src/main/resources/liveTemplates/XLang_File.xml b/nop-idea-plugin/src/main/resources/liveTemplates/XLang_File.xml new file mode 100644 index 000000000..5ba75a5e9 --- /dev/null +++ b/nop-idea-plugin/src/main/resources/liveTemplates/XLang_File.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties b/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties index e9935be2b..943976c38 100644 --- a/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties +++ b/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties @@ -1,8 +1,7 @@ -group.NopEntropy.XLang.NewGroup.text = Nop XLang -action.NopEntropy.XLang.NewDef.text = XLang Def -action.NopEntropy.XLang.NewDef.title = New XLang Def -action.NopEntropy.XLang.NewDSL.text = XLang DSL -action.NopEntropy.XLang.NewDSL.title = New XLang DSL +action.NopEntropy.NewXLang.text = XLang File +action.NopEntropy.NewXLang.title = New XLang File +action.error.invalid-filename = Invalid file name +action.error.no-xlang-file-extension = XLang DSL file must have extension name, such as 'example.xlib' xlang.annotation.attr.not-defined = Undefined attribute ''{0}'' xlang.annotation.attr.value-required = Attribute ''{0}'' can''t have empty value diff --git a/nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties b/nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties index a4104325d..306feadc2 100644 --- a/nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties +++ b/nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties @@ -1,8 +1,7 @@ -group.NopEntropy.XLang.NewGroup.text = Nop XLang -action.NopEntropy.XLang.NewDef.text = XLang Def -action.NopEntropy.XLang.NewDef.title = \u65B0\u5EFA XLang Def -action.NopEntropy.XLang.NewDSL.text = XLang DSL -action.NopEntropy.XLang.NewDSL.title = \u65B0\u5EFA XLang DSL +action.NopEntropy.NewXLang.text = XLang File +action.NopEntropy.NewXLang.title = \u65B0\u5EFA XLang File +action.error.invalid-filename = \u6587\u4EF6\u540D\u65E0\u6548 +action.error.no-xlang-file-extension = XLang DSL \u6587\u4EF6\u5FC5\u987B\u6307\u5B9A\u540E\u7F00\u540D\uFF0C\u4F8B\u5982 'example.xlib' xlang.annotation.attr.not-defined = \u5C5E\u6027 ''{0}'' \u672A\u5B9A\u4E49 xlang.annotation.attr.value-required = \u5C5E\u6027 ''{0}'' \u7684\u503C\u4E0D\u5141\u8BB8\u4E3A\u7A7A -- Gitee