diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9c087ac --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + target-branch: "master" + schedule: + interval: "daily" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2bd1b9e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,47 @@ + +## 0.2.0 - 230120 + +### Added + +- Added first version of inspector - so far only read-only. +To access, start interactive debugging and then click on any local variable. +- Macro expand. When you hover over a symbol that is a macro call in a form, +it will macro expand it in the documentation. Due to async notion, +you need to hover again to see it. +- Basic completion suggestion working + +### Fixes + +- Changed internal environment to be more decoupled +- Fixed code highlight for methods +- Fixed bad package when package does not exist +- Fixed lisp parser, `REFERENCE_LABEL` requiring `datum`, now it is stand alone +- Fixed line comment highlight color +- Fixed highlight on braces + - no longer using standard BracePair but instead use internal brace matcher to prevent auto brace inserting + +## 0.1.1 - 230115 + +### Fixes + +- Fixed bug in build without certificates (oops!) +- Fixed package detector to work with `#:` packages +- Fixed settings to correctly restart on change +- Fixed settings to save quicklisp path + +## 0.1.0 - 230115 + +Initial release + +### Added + +- Basic language parsing and lexing +- REPL + - Click on `+` icon to create REPL +- Interactive debugger +- Eval actions - right click menu in editor + - Eval in region + - Eval previous/next/current form + - Eval file of current editor +- Eval file action +- Some basic `.cl`, `.lisp` and `.asdf` templates \ No newline at end of file diff --git a/README.md b/README.md index 524ee45..46e681b 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ [![License: APACHE](https://badgen.net/github/license/enerccio/SLT?color=green)](LICENSE) [![0.1.0](https://badgen.net/github/milestones/enerccio/SLT/1)](https://github.com/enerccio/SLT/milestone/1) [![0.2.0](https://badgen.net/github/milestones/enerccio/SLT/2)](https://github.com/enerccio/SLT/milestone/2) +[![0.3.0](https://badgen.net/github/milestones/enerccio/SLT/4)](https://github.com/enerccio/SLT/milestone/4) + +![Image](src/main/resources/logo/logo.svg) **THIS PLUGIN IS EXPERIMENTAL and can crash at any time! Please report all bugs!** @@ -14,7 +17,7 @@ IDE capabilities for Common Lisp. ## Requirements -1) Intellij based IDE - tested on `Intellij Idea Community/Ultimate` but should workd on all major IDEs +1) Intellij based IDE - tested on `Intellij Idea Community/Ultimate` but should work on all major IDEs 1) Versions supported are from 2022.2 and upwards 2) [Steel Bank Common Lisp](https://www.sbcl.org/) installed 3) [Quicklisp](https://www.quicklisp.org/beta/) @@ -23,7 +26,8 @@ IDE capabilities for Common Lisp. Download plugin for your IDE from releases and install it via file. -_ie_ File->Settings->Plugin, click on gear icon and then 'Install plugin from disk' +_ie_ File->Settings->Plugin, click on gear icon and then 'Install plugin from disk' +and then select the downloaded zip (do not unzip the zip) To find out which release applies to you check this table: @@ -72,26 +76,38 @@ You can also open this as a project in Intellij Idea. ## Planned features / goals * [ ] Upload to marketplace when it has enough features +* [ ] Automatic indentation * [x] REPL * [x] Interactive debugging +* [ ] Inspection + * [x] Basic inspection + * [ ] Actions + * [ ] Inspection eval * [ ] Walkable debugger without actions * [ ] Breakpoints * [x] Documentation -* [ ] Macro expand in documentation +* [x] Macro expand in documentation + * Macro expand requires you to hover element twice for now * [x] Find function by symbol name * [ ] Search for symbols * [ ] Back references * [ ] Refactoring * [ ] List of quicklisp installed packages / ASDF packages * [ ] List of modified top level forms that are yet to be evaluated +* [ ] Actually make an IDE, ie just plugin with dependencies as one application, not a plugin ### Far futures / possible goals -* [ ] Virtual Environment à la pycharm so you can specify which interpret instance you want +* [ ] SDK Support * [ ] Automatic download of lisp interpret and quicklisp * [ ] Different lisp interpreter support * [ ] Remote connections to interpreters +* [ ] Rewrite everything into ABCL just for purity sake lol ## License -This project is licensed under [Apache License v2](LICENSE.txt). \ No newline at end of file +This project is licensed under [Apache License v2](LICENSE.txt). + +### What does SLT even mean? + +SLT - Speech Language Therapy. Only cure for LISP! \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index dc3ddb0..36d6ee5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "com.en_circle.slt" -version = "0.1.1" +version = "0.2.0" repositories { mavenCentral() @@ -13,6 +13,7 @@ repositories { dependencies { implementation("org.awaitility:awaitility:4.2.0") implementation("org.watertemplate:watertemplate-engine:1.2.2") + implementation("com.google.guava:guava:30.0-jre") } sourceSets { @@ -26,7 +27,7 @@ sourceSets { // Configure Gradle IntelliJ Plugin // Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html intellij { - version.set("2022.1") + version.set("2022.2") pluginName.set("slt") var ide = System.getenv("TARGET_IDE") if (ide == null || "" == ide) @@ -41,12 +42,12 @@ intellij { tasks { // Set the JVM compatibility versions withType { - sourceCompatibility = "11" - targetCompatibility = "11" + sourceCompatibility = "17" + targetCompatibility = "17" } patchPluginXml { - sinceBuild.set("221") + sinceBuild.set("222") untilBuild.set("231.*") } diff --git a/contributing.md b/contributing.md index f1a8a40..2f41d7f 100644 --- a/contributing.md +++ b/contributing.md @@ -1,3 +1,3 @@ Report any bugs please! -I will think of what to write here at later date when it's bit bigger project. \ No newline at end of file +I will think of what to write here at later date when it's a bit bigger project. \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 80959e1..2057f59 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-XX:MaxHeapSize=512m -Xms512m -Xmx1g # CL GO IC IU PS PY PC RD -targetIDE=IC \ No newline at end of file +targetIDE=IU \ No newline at end of file diff --git a/src/main/gen/com/en_circle/slt/plugin/lisp/LispParser.java b/src/main/gen/com/en_circle/slt/plugin/lisp/LispParser.java index ce9c7c1..d3fd7fa 100644 --- a/src/main/gen/com/en_circle/slt/plugin/lisp/LispParser.java +++ b/src/main/gen/com/en_circle/slt/plugin/lisp/LispParser.java @@ -94,7 +94,7 @@ private static boolean compound_symbol_0(PsiBuilder b, int l) { } /* ********************************************************** */ - // tested | evaled | pathname | UNDEFINED_SEQUENCE | BIT_ARRAY | CHARACTER + // tested | evaled | pathname | UNDEFINED_SEQUENCE | BIT_ARRAY | CHARACTER | REFERENCE_LABEL // | number | real_pair // | compound_symbol // | string | vector | array | structure | list | pair @@ -108,6 +108,7 @@ public static boolean datum(PsiBuilder b, int l) { if (!r) r = consumeToken(b, UNDEFINED_SEQUENCE); if (!r) r = consumeToken(b, BIT_ARRAY); if (!r) r = consumeToken(b, CHARACTER); + if (!r) r = consumeToken(b, REFERENCE_LABEL); if (!r) r = number(b, l + 1); if (!r) r = real_pair(b, l + 1); if (!r) r = compound_symbol(b, l + 1); @@ -122,13 +123,12 @@ public static boolean datum(PsiBuilder b, int l) { } /* ********************************************************** */ - // REFERENCE_SET | REFERENCE_LABEL | TEST_SUCCESS | COMMA | BACKQUOTE | QUOTE | FUNCTION + // REFERENCE_SET | TEST_SUCCESS | COMMA | BACKQUOTE | QUOTE | FUNCTION public static boolean enhancement(PsiBuilder b, int l) { if (!recursion_guard_(b, l, "enhancement")) return false; boolean r; Marker m = enter_section_(b, l, _NONE_, ENHANCEMENT, ""); r = consumeToken(b, REFERENCE_SET); - if (!r) r = consumeToken(b, REFERENCE_LABEL); if (!r) r = consumeToken(b, TEST_SUCCESS); if (!r) r = consumeToken(b, COMMA); if (!r) r = consumeToken(b, BACKQUOTE); diff --git a/src/main/java/com/en_circle/slt/plugin/SltCommonLispLanguage.java b/src/main/java/com/en_circle/slt/plugin/SltCommonLispLanguage.java index f6c7dfe..30fe23b 100644 --- a/src/main/java/com/en_circle/slt/plugin/SltCommonLispLanguage.java +++ b/src/main/java/com/en_circle/slt/plugin/SltCommonLispLanguage.java @@ -1,6 +1,5 @@ package com.en_circle.slt.plugin; -import com.en_circle.slt.plugin.swank.SwankServer; import com.intellij.lang.Language; public class SltCommonLispLanguage extends Language { diff --git a/src/main/java/com/en_circle/slt/plugin/SltCompletionContributor.java b/src/main/java/com/en_circle/slt/plugin/SltCompletionContributor.java new file mode 100644 index 0000000..f5112f3 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/SltCompletionContributor.java @@ -0,0 +1,14 @@ +package com.en_circle.slt.plugin; + +import com.en_circle.slt.plugin.autocomplete.HeadCompletionProvider; +import com.en_circle.slt.plugin.lisp.psi.SltPatterns; +import com.intellij.codeInsight.completion.CompletionContributor; +import com.intellij.codeInsight.completion.CompletionType; + +public class SltCompletionContributor extends CompletionContributor { + + public SltCompletionContributor() { + extend(CompletionType.BASIC, SltPatterns.getHeadPattern(), new HeadCompletionProvider()); + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/SltDocumentationProvider.java b/src/main/java/com/en_circle/slt/plugin/SltDocumentationProvider.java index ddad593..e74281f 100644 --- a/src/main/java/com/en_circle/slt/plugin/SltDocumentationProvider.java +++ b/src/main/java/com/en_circle/slt/plugin/SltDocumentationProvider.java @@ -1,9 +1,13 @@ package com.en_circle.slt.plugin; +import com.en_circle.slt.plugin.SymbolState.SymbolBinding; import com.en_circle.slt.plugin.lisp.LispParserUtil; +import com.en_circle.slt.plugin.lisp.psi.LispList; import com.en_circle.slt.plugin.lisp.psi.LispSymbol; import com.en_circle.slt.plugin.lisp.psi.impl.LispPsiImplUtil; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; import com.intellij.lang.documentation.AbstractDocumentationProvider; +import com.intellij.openapi.util.text.HtmlBuilder; import com.intellij.openapi.util.text.HtmlChunk; import com.intellij.psi.PsiElement; import org.apache.commons.lang3.StringUtils; @@ -17,7 +21,7 @@ public class SltDocumentationProvider extends AbstractDocumentationProvider { String text = LispPsiImplUtil.getSExpressionHead(element); if (text != null) { String packageName = LispParserUtil.getPackage(element); - SymbolState state = SltSBCL.getInstance().refreshSymbolFromServer(packageName, text, element); + SymbolState state = LispEnvironmentService.getInstance(element.getProject()).refreshSymbolFromServer(packageName, text, element); switch (state.binding) { case NONE: return SltBundle.message("slt.documentation.types.symbol") + " " + text; @@ -33,6 +37,10 @@ public class SltDocumentationProvider extends AbstractDocumentationProvider { return SltBundle.message("slt.documentation.types.specvariable") + " " + text; case KEYWORD: return SltBundle.message("slt.documentation.types.keyword") + " " + text; + case CLASS: + return SltBundle.message("slt.documentation.types.class") + " " + text; + case METHOD: + return SltBundle.message("slt.documentation.types.method") + " " + text; } } return null; @@ -43,16 +51,35 @@ public class SltDocumentationProvider extends AbstractDocumentationProvider { if (element instanceof LispSymbol) { String text = element.getText(); String packageName = LispParserUtil.getPackage(element); - SymbolState state = SltSBCL.getInstance().refreshSymbolFromServer(packageName, text, element); - return asHtml(state.documentation); + SymbolState state = LispEnvironmentService.getInstance(element.getProject()).refreshSymbolFromServer(packageName, text, element); + return asHtml(state, packageName, element, originalElement); } return null; } - private String asHtml(String documentation) { - if (documentation == null) - return null; + private String asHtml(SymbolState state, String packageName, PsiElement element, @Nullable PsiElement originalElement) { + HtmlBuilder builder = new HtmlBuilder(); + String documentation = StringUtils.replace(StringUtils.replace(state.documentation, " ", " "), + "\n", HtmlChunk.br().toString()); + builder.append(documentation == null ? HtmlChunk.raw("") : HtmlChunk.raw(documentation)); - return StringUtils.replace(StringUtils.replace(documentation, " ", " "), "\n", HtmlChunk.br().toString()); + LispList form = LispParserUtil.getIfHead(element); + if (form != null && state.binding == SymbolBinding.MACRO) { + String macroExpand = LispEnvironmentService.getInstance(element.getProject()).macroexpand(form, packageName); + if (macroExpand != null) { + macroExpand = StringUtils.replace(StringUtils.replace(macroExpand, " ", " "), + "\n", HtmlChunk.br().toString()); + builder.append(HtmlChunk.hr()); + builder.append(HtmlChunk.text(SltBundle.message("slt.documentation.macroexpand"))); + builder.append(HtmlChunk.br()); + builder.append(HtmlChunk.raw(macroExpand)); + } else { + builder.append(HtmlChunk.hr()); + builder.append(HtmlChunk.text(SltBundle.message("slt.documentation.macroexpand.generating"))); + } + } + + String doc = builder.toString(); + return StringUtils.isBlank(doc) ? null : doc; } } diff --git a/src/main/java/com/en_circle/slt/plugin/SltSBCL.java b/src/main/java/com/en_circle/slt/plugin/SltSBCL.java deleted file mode 100644 index 51cb74e..0000000 --- a/src/main/java/com/en_circle/slt/plugin/SltSBCL.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.en_circle.slt.plugin; - -import com.en_circle.slt.plugin.swank.*; -import com.en_circle.slt.plugin.swank.SlimeListener.DebugInterface; -import com.en_circle.slt.plugin.swank.SlimeListener.RequestResponseLogger; -import com.en_circle.slt.plugin.swank.SwankServer.SwankServerListener; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class SltSBCL { - - private static final SltSBCL currentState = new SltSBCL(); - - public static SltSBCL getInstance() { - return currentState; - } - - private SwankClient client; - private SlimeListener slimeListener; - private Project project; - private RequestResponseLogger logger; - private DebugInterface debugInterface; - private final List serverListeners = Collections.synchronizedList(new ArrayList<>()); - - public void start() throws Exception { - for (SBCLServerListener listener : serverListeners) { - listener.onPreStart(); - } - - SwankServerConfiguration c = new SwankServerConfiguration.Builder() - .setExecutable(SltState.getInstance().sbclExecutable) - .setPort(SltState.getInstance().port) - .setQuicklispStartScriptPath(SltState.getInstance().quicklispStartScript) - .setProjectDirectory(project.getBasePath()) - .setListener((output, newData) -> { - for (SBCLServerListener listener : serverListeners) { - listener.onOutputChanged(output, newData); - } - }) - .build(); - SwankServer.startSbcl(c); - - slimeListener = new SlimeListener(project, true, logger, debugInterface); - client = new SwankClient("127.0.0.1", SltState.getInstance().port, slimeListener); - - for (SBCLServerListener listener : serverListeners) { - listener.onPostStart(); - } - } - - public void setRequestResponseLogger(RequestResponseLogger logger) { - this.logger = logger; - } - - public void setDebugInterface(DebugInterface debugInterface) { - this.debugInterface = debugInterface; - } - - public void sendToSbcl(SlimeRequest request) throws Exception { - sendToSbcl(request, true); - } - - public void sendToSbcl(SlimeRequest request, boolean startServer) throws Exception { - if (startServer && !SwankServer.INSTANCE.isActive()) { - start(); - } - if (!SwankServer.INSTANCE.isActive()) { - if (!startServer) - return; // ignored - throw new IOException("server offline"); - } - - if (slimeListener != null) { - slimeListener.call(request, client); - } - } - - public void stop() throws Exception { - for (SBCLServerListener listener : serverListeners) { - listener.onPreStop(); - } - try { - if (client != null) - client.close(); - } finally { - SltSBCLSymbolCache.INSTANCE.clear(); - SwankServer.stop(); - } - - for (SBCLServerListener listener : serverListeners) { - listener.onPostStop(); - } - } - - public void addServerListener(SBCLServerListener listener) { - serverListeners.add(listener); - } - - public Project getProject() { - return project; - } - - public void setProject(Project project) { - this.project = project; - } - - public String getGlobalPackage() { - return "cl-user"; - } - - public SymbolState refreshSymbolFromServer(String packageName, String symbolName, PsiElement element) { - return SltSBCLSymbolCache.INSTANCE.refreshSymbolFromServer(packageName, symbolName, element); - } - - public boolean hasEventsSet() { - return logger != null; - } - - public interface SBCLServerListener extends SwankServerListener { - - void onPreStart(); - void onPostStart(); - void onPreStop(); - void onPostStop(); - - } - -} diff --git a/src/main/java/com/en_circle/slt/plugin/SltUIConstants.java b/src/main/java/com/en_circle/slt/plugin/SltUIConstants.java index 8dc03d4..9eb5cad 100644 --- a/src/main/java/com/en_circle/slt/plugin/SltUIConstants.java +++ b/src/main/java/com/en_circle/slt/plugin/SltUIConstants.java @@ -10,4 +10,8 @@ public class SltUIConstants { public static final JBColor DEBUG_FRAMES_COLOR = new JBColor(new Color(230, 245, 95), new Color(5, 51, 12)); public static final JBColor DEBUG_FRAMES_SELECTED_COLOR = new JBColor(new Color(230, 145, 95), new Color(5, 51, 72)); + public static String colorToHex(JBColor color) { + int rgba = (color.getRGB() << 8) | color.getAlpha(); + return String.format("#%08X", rgba); + } } diff --git a/src/main/java/com/en_circle/slt/plugin/SltWindowFactory.java b/src/main/java/com/en_circle/slt/plugin/SltWindowFactory.java index 72ef420..35b7255 100644 --- a/src/main/java/com/en_circle/slt/plugin/SltWindowFactory.java +++ b/src/main/java/com/en_circle/slt/plugin/SltWindowFactory.java @@ -15,7 +15,7 @@ public class SltWindowFactory implements ToolWindowFactory { public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { { SltCoreWindow sltCoreWindow = new SltCoreWindow(toolWindow); - ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); + ContentFactory contentFactory = ContentFactory.getInstance(); Content content = contentFactory.createContent(sltCoreWindow.getContent(), SltBundle.message("slt.ui.process.title"), false); toolWindow.getContentManager().addContent(content); @@ -23,7 +23,7 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo { SltDebuggers debugger = new SltDebuggers(toolWindow); - ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); + ContentFactory contentFactory = ContentFactory.getInstance(); Content content = contentFactory.createContent(debugger.getContent(), SltBundle.message("slt.ui.debuggers.title"), false); debugger.setSelf(content); diff --git a/src/main/java/com/en_circle/slt/plugin/SymbolState.java b/src/main/java/com/en_circle/slt/plugin/SymbolState.java index d2ba2b2..63717b3 100644 --- a/src/main/java/com/en_circle/slt/plugin/SymbolState.java +++ b/src/main/java/com/en_circle/slt/plugin/SymbolState.java @@ -26,11 +26,6 @@ public SymbolState(String name, String packageName, String symbolName) { this.symbolName = symbolName; } - public enum SymbolBinding { - NONE, FUNCTION, MACRO, SPECIAL_FORM, - CONSTANT, KEYWORD, SPECIAL_VARIABLE - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -50,4 +45,16 @@ public int hashCode() { result = 31 * result + (symbolName != null ? symbolName.hashCode() : 0); return result; } + + public enum SymbolBinding { + NONE, + FUNCTION, + MACRO, + SPECIAL_FORM, + CONSTANT, + KEYWORD, + SPECIAL_VARIABLE, + CLASS, + METHOD + } } diff --git a/src/main/java/com/en_circle/slt/plugin/actions/EvalActionBase.java b/src/main/java/com/en_circle/slt/plugin/actions/EvalActionBase.java index 5f0dfc2..2a0d225 100644 --- a/src/main/java/com/en_circle/slt/plugin/actions/EvalActionBase.java +++ b/src/main/java/com/en_circle/slt/plugin/actions/EvalActionBase.java @@ -2,18 +2,21 @@ import com.en_circle.slt.plugin.SltBundle; import com.en_circle.slt.plugin.SltCommonLispFileType; -import com.en_circle.slt.plugin.SltSBCL; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.swank.requests.Eval; +import com.en_circle.slt.plugin.swank.requests.EvalFromVirtualFile; import com.en_circle.slt.plugin.swank.requests.LoadFile; -import com.en_circle.slt.plugin.swank.requests.SltEval; -import com.en_circle.slt.plugin.swank.requests.SwankEvalFromVirtualFile; +import com.intellij.openapi.actionSystem.ActionUpdateThread; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; +import com.intellij.util.FileContentUtilCore; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,14 +34,14 @@ public void update(@NotNull AnActionEvent event) { if (editor != null && event.getProject() != null) { PsiFile file = PsiDocumentManager.getInstance(Objects.requireNonNull(editor.getProject())).getPsiFile(editor.getDocument()); if (file != null && SltCommonLispFileType.INSTANCE.equals(file.getFileType())) { - event.getPresentation().setEnabledAndVisible(SltSBCL.getInstance().hasEventsSet()); + event.getPresentation().setEnabledAndVisible(true); } } } protected void evaluate(Project project, String buffer, String packageName, Runnable callback) { try { - SltSBCL.getInstance().sendToSbcl(SltEval.eval(buffer, packageName, result -> callback.run()), true); + LispEnvironmentService.getInstance(project).sendToLisp(Eval.eval(buffer, packageName, result -> callback.run()), true); } catch (Exception e) { log.warn(SltBundle.message("slt.error.sbclstart"), e); Messages.showErrorDialog(project, e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); @@ -47,7 +50,7 @@ protected void evaluate(Project project, String buffer, String packageName, Runn protected void evaluateRegion(Project project, String buffer, String packageName, String filename, int bufferPosition, int lineno, int charno, Runnable callback) { try { - SltSBCL.getInstance().sendToSbcl(SwankEvalFromVirtualFile + LispEnvironmentService.getInstance(project).sendToLisp(EvalFromVirtualFile .eval(buffer, filename, bufferPosition, lineno, charno, packageName, result -> callback.run()), true); } catch (Exception e) { log.warn(SltBundle.message("slt.error.sbclstart"), e); @@ -55,13 +58,18 @@ protected void evaluateRegion(Project project, String buffer, String packageName } } - protected void evaluateFile(Project project, String filename) { + protected void evaluateFile(Project project, String filename, VirtualFile virtualFile) { try { - SltSBCL.getInstance().sendToSbcl(LoadFile.loadFile(filename), true); + LispEnvironmentService.getInstance(project).sendToLisp(LoadFile.loadFile(filename), true); + FileContentUtilCore.reparseFiles(virtualFile); } catch (Exception e) { log.warn(SltBundle.message("slt.error.sbclstart"), e); Messages.showErrorDialog(project, e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); } } + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.EDT; + } } diff --git a/src/main/java/com/en_circle/slt/plugin/actions/EvalFile.java b/src/main/java/com/en_circle/slt/plugin/actions/EvalFile.java index e63602d..51267d6 100644 --- a/src/main/java/com/en_circle/slt/plugin/actions/EvalFile.java +++ b/src/main/java/com/en_circle/slt/plugin/actions/EvalFile.java @@ -2,7 +2,6 @@ import com.en_circle.slt.plugin.SltBundle; import com.en_circle.slt.plugin.SltCommonLispFileType; -import com.en_circle.slt.plugin.SltSBCL; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.fileEditor.FileEditorManager; @@ -18,7 +17,7 @@ public class EvalFile extends EvalActionBase { public void actionPerformed(@NotNull AnActionEvent event) { VirtualFile vf = event.getData(CommonDataKeys.VIRTUAL_FILE); if (vf != null) { - evaluateFile(event.getProject(), vf.getPath()); + evaluateFile(event.getProject(), vf.getPath(), vf); for (VirtualFile openedFiles : FileEditorManager.getInstance(Objects.requireNonNull(event.getProject())) .getOpenFiles()) { FileContentUtilCore.reparseFiles(openedFiles); @@ -35,7 +34,7 @@ public void update(@NotNull AnActionEvent event) { VirtualFile vf = event.getData(CommonDataKeys.VIRTUAL_FILE); if (vf != null) { if (vf.getFileType().equals(SltCommonLispFileType.INSTANCE)) { - event.getPresentation().setEnabledAndVisible(SltSBCL.getInstance().hasEventsSet()); + event.getPresentation().setEnabledAndVisible(true); } } } diff --git a/src/main/java/com/en_circle/slt/plugin/actions/EvalFileFromEditor.java b/src/main/java/com/en_circle/slt/plugin/actions/EvalFileFromEditor.java index f307a33..c8a442c 100644 --- a/src/main/java/com/en_circle/slt/plugin/actions/EvalFileFromEditor.java +++ b/src/main/java/com/en_circle/slt/plugin/actions/EvalFileFromEditor.java @@ -6,8 +6,11 @@ import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; import org.jetbrains.annotations.NotNull; +import java.util.Objects; + public class EvalFileFromEditor extends EvalActionBase { @Override @@ -21,9 +24,14 @@ public void update(@NotNull AnActionEvent event) { public void actionPerformed(@NotNull AnActionEvent event) { EditorEx editor = (EditorEx) event.getData(CommonDataKeys.EDITOR); if (editor != null) { + PsiDocumentManager psiMgr = PsiDocumentManager.getInstance(Objects.requireNonNull(editor.getProject())); + psiMgr.commitDocument(editor.getDocument()); + FileDocumentManager.getInstance().saveDocument(editor.getDocument()); + VirtualFile vf = FileDocumentManager.getInstance().getFile(editor.getDocument()); if (vf != null) { - evaluateFile(editor.getProject(), vf.getPath()); + + evaluateFile(editor.getProject(), vf.getPath(), vf); } } } diff --git a/src/main/java/com/en_circle/slt/plugin/actions/EvalRegionAction.java b/src/main/java/com/en_circle/slt/plugin/actions/EvalRegionAction.java index ada83ba..53e025d 100644 --- a/src/main/java/com/en_circle/slt/plugin/actions/EvalRegionAction.java +++ b/src/main/java/com/en_circle/slt/plugin/actions/EvalRegionAction.java @@ -1,8 +1,8 @@ package com.en_circle.slt.plugin.actions; import com.en_circle.slt.plugin.SltBundle; -import com.en_circle.slt.plugin.SltSBCL; import com.en_circle.slt.plugin.lisp.LispParserUtil; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.editor.Caret; @@ -59,7 +59,7 @@ public void actionPerformed(@NotNull AnActionEvent event) { int offset = editor.getSelectionModel().getSelectionStart(); evaluate(editor.getProject(), selectedText, LispParserUtil.getPackage(psiFile, offset), () -> { }); } else { - evaluate(editor.getProject(), selectedText, SltSBCL.getInstance().getGlobalPackage(), () -> { }); + evaluate(editor.getProject(), selectedText, LispEnvironmentService.getInstance(editor.getProject()).getGlobalPackage(), () -> { }); } } } @@ -84,7 +84,7 @@ private void evalEachCaret(Editor editor, String filename, List carets) { PsiDocumentManager psiMgr = PsiDocumentManager.getInstance(Objects.requireNonNull(editor.getProject())); psiMgr.commitDocument(editor.getDocument()); PsiFile psiFile = psiMgr.getPsiFile(editor.getDocument()); - String packageName = SltSBCL.getInstance().getGlobalPackage(); + String packageName = LispEnvironmentService.getInstance(editor.getProject()).getGlobalPackage(); if (psiFile != null) { packageName = LispParserUtil.getPackage(psiFile, offset); } diff --git a/src/main/java/com/en_circle/slt/plugin/autocomplete/HeadCompletionProvider.java b/src/main/java/com/en_circle/slt/plugin/autocomplete/HeadCompletionProvider.java new file mode 100644 index 0000000..8a2d538 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/autocomplete/HeadCompletionProvider.java @@ -0,0 +1,70 @@ +package com.en_circle.slt.plugin.autocomplete; + +import com.en_circle.slt.plugin.SymbolState; +import com.en_circle.slt.plugin.SymbolState.SymbolBinding; +import com.en_circle.slt.plugin.lisp.LispParserUtil; +import com.en_circle.slt.plugin.lisp.lisp.LispContainer; +import com.en_circle.slt.plugin.lisp.lisp.LispElement; +import com.en_circle.slt.plugin.lisp.lisp.LispString; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService.LispEnvironmentState; +import com.en_circle.slt.plugin.swank.requests.SimpleCompletion; +import com.en_circle.slt.tools.SltApplicationUtils; +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionProvider; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.icons.AllIcons.Nodes; +import com.intellij.openapi.project.Project; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class HeadCompletionProvider extends CompletionProvider { + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) { + Project project = parameters.getEditor().getProject(); + assert project != null; + + if (LispEnvironmentService.getInstance(project).getState() == LispEnvironmentState.READY) { + String startedSymbol = result.getPrefixMatcher().getPrefix(); + String packageName = LispParserUtil.getPackage(parameters.getOriginalFile(), parameters.getOffset()); + List builderList = SltApplicationUtils.getAsyncResultNoThrow(project, finishRequest -> SimpleCompletion + .simpleCompletion(startedSymbol, packageName, lr -> { + List builders = new ArrayList<>(); + try { + if (lr instanceof LispContainer container) { + if (container.getItems().get(0) instanceof LispContainer innerList) { + for (LispElement element : innerList.getItems()) { + if (element instanceof LispString str) { + SymbolState state = LispEnvironmentService.getInstance(project) + .refreshSymbolFromServer(null, str.getValue(), null); + LookupElementBuilder builder = LookupElementBuilder.create(str.getValue()); + if (state.binding == SymbolBinding.MACRO) { + builder = builder.withIcon(Nodes.Template); + } else if (state.binding == SymbolBinding.FUNCTION) { + builder = builder.withIcon(Nodes.Function); + } else if (state.binding == SymbolBinding.METHOD) { + builder = builder.withIcon(Nodes.Method); + } else { + builder = builder.withIcon(Nodes.Lambda); + } + builders.add(builder); + } + } + } + } + } finally { + finishRequest.accept(builders); + } + })); + if (builderList != null) { + for (LookupElementBuilder builder : builderList) { + result.addElement(builder); + } + } + } + } +} diff --git a/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironment.java b/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironment.java new file mode 100644 index 0000000..9da6ada --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironment.java @@ -0,0 +1,21 @@ +package com.en_circle.slt.plugin.environment; + +public interface SltLispEnvironment { + + void start(SltLispEnvironmentConfiguration configuration) throws SltProcessException; + void stop() throws SltProcessException; + + boolean isActive(); + SltLispProcessInformation getInformation(); + + interface SltLispOutputChangedListener { + + void onOutputChanged(SltOutput output, String newData); + + } + + enum SltOutput { + STDOUT, STDERR + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironmentBase.java b/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironmentBase.java new file mode 100644 index 0000000..4152fe5 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironmentBase.java @@ -0,0 +1,5 @@ +package com.en_circle.slt.plugin.environment; + +public abstract class SltLispEnvironmentBase implements SltLispEnvironment { + +} diff --git a/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironmentConfiguration.java b/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironmentConfiguration.java new file mode 100644 index 0000000..8764b5e --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironmentConfiguration.java @@ -0,0 +1,17 @@ +package com.en_circle.slt.plugin.environment; + +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltLispOutputChangedListener; + +public interface SltLispEnvironmentConfiguration { + + SltLispOutputChangedListener getListener(); + + interface Builder, RESULT extends SltLispEnvironmentConfiguration> { + + Builder setListener(SltLispOutputChangedListener listener); + + RESULT build(); + + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironmentProcess.java b/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironmentProcess.java new file mode 100644 index 0000000..b53c5ac --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/environment/SltLispEnvironmentProcess.java @@ -0,0 +1,79 @@ +package com.en_circle.slt.plugin.environment; + +import com.en_circle.slt.plugin.environment.SltProcessStreamGobbler.ProcessInitializationWaiter; +import com.intellij.openapi.application.ApplicationManager; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public abstract class SltLispEnvironmentProcess extends SltLispEnvironmentBase { + + protected Process process; + protected SltProcessStreamGobbler errorController; + protected SltProcessStreamGobbler outputController; + protected List listeners = new ArrayList<>(); + + protected abstract Object prepareProcessEnvironment(SltLispEnvironmentProcessConfiguration configuration) throws SltProcessException; + protected abstract File getProcessWorkDirectory(SltLispEnvironmentProcessConfiguration configuration, Object environment) throws SltProcessException; + protected abstract String[] getProcessCommand(SltLispEnvironmentProcessConfiguration configuration, Object environment) throws SltProcessException; + protected abstract ProcessInitializationWaiter waitForFullInitialization(SltLispEnvironmentProcessConfiguration configuration, Object environment) throws SltProcessException; + + @Override + public boolean isActive() { + return process != null && process.isAlive(); + } + + public void start(SltLispEnvironmentConfiguration configuration) throws SltProcessException { + if (process != null) + return; + if (!(configuration instanceof SltLispEnvironmentProcessConfiguration processConfiguration)) + throw new SltProcessException("Configuration incorrect"); + + try { + Object environment = prepareProcessEnvironment(processConfiguration); + + ProcessBuilder pb = new ProcessBuilder(getProcessCommand(processConfiguration, environment)); + pb.directory(getProcessWorkDirectory(processConfiguration, environment)); + process = pb.start(); + + errorController = new SltProcessStreamGobbler(process.getErrorStream()); + outputController = new SltProcessStreamGobbler(process.getInputStream()); + + ProcessInitializationWaiter waiter = waitForFullInitialization(processConfiguration, environment); + + SltLispOutputChangedListener listener = processConfiguration.getListener(); + if (listener != null) { + if (ApplicationManager.getApplication() != null) { + errorController.addUpdateListener(data -> + ApplicationManager.getApplication().invokeLater(() -> listener.onOutputChanged(SltOutput.STDERR, data))); + outputController.addUpdateListener(data -> + ApplicationManager.getApplication().invokeLater(() -> listener.onOutputChanged(SltOutput.STDOUT, data))); + } else { + errorController.addUpdateListener(data -> listener.onOutputChanged(SltOutput.STDERR, data)); + outputController.addUpdateListener(data -> listener.onOutputChanged(SltOutput.STDOUT, data)); + } + } + + errorController.start(); + outputController.start(); + + waiter.awaitFor(process); + } catch (SltProcessException e) { + throw e; + } catch (Exception e) { + throw new SltProcessException(e); + } + } + public void stop() throws SltProcessException { + if (process != null) { + process.destroy(); + process = null; + } + } + + public interface SltLispEnvironmentProcessConfiguration extends SltLispEnvironmentConfiguration { + + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/environment/SltLispProcessInformation.java b/src/main/java/com/en_circle/slt/plugin/environment/SltLispProcessInformation.java new file mode 100644 index 0000000..a93f037 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/environment/SltLispProcessInformation.java @@ -0,0 +1,7 @@ +package com.en_circle.slt.plugin.environment; + +public interface SltLispProcessInformation { + + String getPid(); + +} diff --git a/src/main/java/com/en_circle/slt/plugin/environment/SltProcessException.java b/src/main/java/com/en_circle/slt/plugin/environment/SltProcessException.java new file mode 100644 index 0000000..25d2a64 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/environment/SltProcessException.java @@ -0,0 +1,24 @@ +package com.en_circle.slt.plugin.environment; + +public class SltProcessException extends Exception { + + public SltProcessException() { + } + + public SltProcessException(String message) { + super(message); + } + + public SltProcessException(String message, Throwable cause) { + super(message, cause); + } + + public SltProcessException(Throwable cause) { + super(cause); + } + + public SltProcessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/swank/SwankStreamController.java b/src/main/java/com/en_circle/slt/plugin/environment/SltProcessStreamGobbler.java similarity index 80% rename from src/main/java/com/en_circle/slt/plugin/swank/SwankStreamController.java rename to src/main/java/com/en_circle/slt/plugin/environment/SltProcessStreamGobbler.java index 0e27488..7d6d5cf 100644 --- a/src/main/java/com/en_circle/slt/plugin/swank/SwankStreamController.java +++ b/src/main/java/com/en_circle/slt/plugin/environment/SltProcessStreamGobbler.java @@ -1,4 +1,4 @@ -package com.en_circle.slt.plugin.swank; +package com.en_circle.slt.plugin.environment; import org.awaitility.Awaitility; import org.awaitility.pollinterval.FixedPollInterval; @@ -11,18 +11,18 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -public class SwankStreamController extends Thread { +public class SltProcessStreamGobbler extends Thread { private final InputStream inputStream; - private final List updateListeners = Collections.synchronizedList(new ArrayList<>()); + private final List updateListeners = Collections.synchronizedList(new ArrayList<>()); - public SwankStreamController(InputStream inputStream) { + public SltProcessStreamGobbler(InputStream inputStream) { this.inputStream = inputStream; - setName("SwanStreamController reader " + inputStream); + setName("SltProcessStreamGobbler " + inputStream); setDaemon(true); } - public void addUpdateListener(SwankStreamControllerUpdateListener listener) { + public void addUpdateListener(SltProcessStreamControllerUpdateListener listener) { updateListeners.add(listener); } @@ -43,7 +43,7 @@ public void run() { if (readSize > 0) { String data = new String(buffer, 0, readSize, StandardCharsets.UTF_8); - for (SwankStreamControllerUpdateListener listener : updateListeners) { + for (SltProcessStreamControllerUpdateListener listener : updateListeners) { listener.onDataRead(data); } } @@ -53,13 +53,18 @@ public void run() { } } - public interface SwankStreamControllerUpdateListener { + public interface SltProcessStreamControllerUpdateListener { void onDataRead(String data); } - public static class WaitForOccurrence implements SwankStreamControllerUpdateListener { + public interface ProcessInitializationWaiter { + boolean awaitFor(Process process); + + } + + public static class WaitForOccurrence implements ProcessInitializationWaiter, SltProcessStreamControllerUpdateListener { private final String occurrence; private final String[] occurrenceStrings; diff --git a/src/main/java/com/en_circle/slt/plugin/environment/SltSBCLEnvironment.java b/src/main/java/com/en_circle/slt/plugin/environment/SltSBCLEnvironment.java new file mode 100644 index 0000000..9971f0f --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/environment/SltSBCLEnvironment.java @@ -0,0 +1,121 @@ +package com.en_circle.slt.plugin.environment; + +import com.en_circle.slt.plugin.environment.SltProcessStreamGobbler.ProcessInitializationWaiter; +import com.en_circle.slt.plugin.environment.SltProcessStreamGobbler.WaitForOccurrence; +import com.en_circle.slt.templates.SltScriptTemplate; +import com.intellij.openapi.util.io.FileUtil; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.watertemplate.Template; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +public class SltSBCLEnvironment extends SltLispEnvironmentProcess { + + @Override + public SltLispProcessInformation getInformation() { + return new SltSBCLLispProcessInformation(); + } + + @Override + protected Object prepareProcessEnvironment(SltLispEnvironmentProcessConfiguration configuration) throws SltProcessException { + SltSBCLEnvironmentConfiguration c = getConfiguration(configuration); + SBCLEnvironment e = new SBCLEnvironment(); + try { + File tempDir = FileUtil.createTempDirectory("SLTinit", ""); + + e.sltCore = new File(tempDir, "slt.cl"); + e.sltCore.deleteOnExit(); + String sltScriptTemplate = new SltScriptTemplate().render(); + FileUtils.write(e.sltCore, sltScriptTemplate, StandardCharsets.UTF_8); + + e.serverStartSetup = new File(tempDir, "startServer.cl"); + e.serverStartSetup.deleteOnExit(); + String sltCorePath = e.sltCore.getAbsolutePath(); + if (sltCorePath.contains("//")) { + sltCorePath = StringUtils.replace(sltCorePath, "\\", "\\\\"); + } + String startScriptTemplate = new SBCLInitScriptTemplate(c, sltCorePath).render(); + FileUtils.write(e.serverStartSetup, startScriptTemplate, StandardCharsets.UTF_8); + + tempDir.deleteOnExit(); + } catch (Exception ex) { + throw new SltProcessException(ex); + } + return e; + } + + @Override + protected File getProcessWorkDirectory(SltLispEnvironmentProcessConfiguration configuration, Object environment) throws SltProcessException { + SltSBCLEnvironmentConfiguration c = getConfiguration(configuration); + SBCLEnvironment e = getEnvironment(environment); + + return e.serverStartSetup.getParentFile(); + } + + @Override + protected String[] getProcessCommand(SltLispEnvironmentProcessConfiguration configuration, Object environment) throws SltProcessException { + SltSBCLEnvironmentConfiguration c = getConfiguration(configuration); + SBCLEnvironment e = getEnvironment(environment); + + return new String[]{ + c.getExecutablePath(), + "--load", + e.serverStartSetup.getName() + }; + } + + @Override + protected ProcessInitializationWaiter waitForFullInitialization(SltLispEnvironmentProcessConfiguration configuration, Object environment) throws SltProcessException { + SltSBCLEnvironmentConfiguration c = getConfiguration(configuration); + SBCLEnvironment e = getEnvironment(environment); + + WaitForOccurrence wait = new WaitForOccurrence("Swank started at port"); + errorController.addUpdateListener(wait); + + return wait; + } + + private SltSBCLEnvironmentConfiguration getConfiguration(SltLispEnvironmentProcessConfiguration configuration) throws SltProcessException { + if (!(configuration instanceof SltSBCLEnvironmentConfiguration)) + throw new SltProcessException("Configuration must be SltSBCLEnvironmentConfiguration"); + return (SltSBCLEnvironmentConfiguration) configuration; + } + + private SBCLEnvironment getEnvironment(Object environment) { + assert (environment instanceof SBCLEnvironment); + + return (SBCLEnvironment) environment; + } + + private class SltSBCLLispProcessInformation implements SltLispProcessInformation { + + @Override + public String getPid() { + return "" + process.pid(); + } + } + + private static class SBCLEnvironment { + + File sltCore; + File serverStartSetup; + + } + + private static class SBCLInitScriptTemplate extends Template { + + public SBCLInitScriptTemplate(SltSBCLEnvironmentConfiguration configuration, String sltCoreScript) { + add("qlpath", configuration.getQuicklispStartScript()); + add("port", "" + configuration.getPort()); + add("cwd", configuration.getProjectDirectory()); + add("sbclcorefile", sltCoreScript); + } + + @Override + protected String getFilePath() { + return "initscript.cl"; + } + } +} diff --git a/src/main/java/com/en_circle/slt/plugin/swank/SwankServerConfiguration.java b/src/main/java/com/en_circle/slt/plugin/environment/SltSBCLEnvironmentConfiguration.java similarity index 64% rename from src/main/java/com/en_circle/slt/plugin/swank/SwankServerConfiguration.java rename to src/main/java/com/en_circle/slt/plugin/environment/SltSBCLEnvironmentConfiguration.java index 5fe6200..32baf87 100644 --- a/src/main/java/com/en_circle/slt/plugin/swank/SwankServerConfiguration.java +++ b/src/main/java/com/en_circle/slt/plugin/environment/SltSBCLEnvironmentConfiguration.java @@ -1,16 +1,18 @@ -package com.en_circle.slt.plugin.swank; +package com.en_circle.slt.plugin.environment; -import com.en_circle.slt.plugin.swank.SwankServer.SwankServerListener; -public class SwankServerConfiguration { +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltLispOutputChangedListener; +import com.en_circle.slt.plugin.environment.SltLispEnvironmentProcess.SltLispEnvironmentProcessConfiguration; + +public class SltSBCLEnvironmentConfiguration implements SltLispEnvironmentProcessConfiguration { private String executablePath = "sbcl"; private String quicklispStartScript = "~/quicklisp/setup.lisp"; private int port = 4005; private String projectDirectory = "/tmp"; - private SwankServerListener listener = null; + private SltLispOutputChangedListener listener = null; - private SwankServerConfiguration() { + private SltSBCLEnvironmentConfiguration() { } @@ -30,13 +32,14 @@ public String getProjectDirectory() { return projectDirectory; } - public SwankServerListener getListener() { + @Override + public SltLispOutputChangedListener getListener() { return listener; } - public static class Builder { + public static class Builder implements SltLispEnvironmentConfiguration.Builder { - private final SwankServerConfiguration c = new SwankServerConfiguration(); + private final SltSBCLEnvironmentConfiguration c = new SltSBCLEnvironmentConfiguration(); private boolean built = false; public Builder setExecutable(String executable) { @@ -68,14 +71,16 @@ public Builder setProjectDirectory(String projectDirectory) { return this; } - public Builder setListener(SwankServerListener listener) { + @Override + public Builder setListener(SltLispOutputChangedListener listener) { checkNotBuilt(); c.listener = listener; return this; } - public SwankServerConfiguration build() { + @Override + public SltSBCLEnvironmentConfiguration build() { checkNotBuilt(); built = true; return c; diff --git a/src/main/java/com/en_circle/slt/plugin/highlights/CommonLispBraceMatcher.java b/src/main/java/com/en_circle/slt/plugin/highlights/CommonLispBraceMatcher.java deleted file mode 100644 index 013bbc9..0000000 --- a/src/main/java/com/en_circle/slt/plugin/highlights/CommonLispBraceMatcher.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.en_circle.slt.plugin.highlights; - -import com.en_circle.slt.plugin.lisp.psi.LispTypes; -import com.intellij.lang.BracePair; -import com.intellij.lang.PairedBraceMatcher; -import com.intellij.psi.PsiFile; -import com.intellij.psi.tree.IElementType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class CommonLispBraceMatcher implements PairedBraceMatcher { - - private static final BracePair[] PAIRS = new BracePair[] { - new BracePair(LispTypes.LPAREN, LispTypes.RPAREN, true), - }; - - @Override - public BracePair @NotNull [] getPairs() { - return PAIRS; - } - - @Override - public boolean isPairedBracesAllowedBeforeType(@NotNull IElementType lbraceType, @Nullable IElementType contextType) { - return false; - } - - @Override - public int getCodeConstructStart(PsiFile file, int openingBraceOffset) { - return openingBraceOffset; - } -} diff --git a/src/main/java/com/en_circle/slt/plugin/highlights/SltBraceHighlighter.java b/src/main/java/com/en_circle/slt/plugin/highlights/SltBraceHighlighter.java new file mode 100644 index 0000000..32d5bf2 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/highlights/SltBraceHighlighter.java @@ -0,0 +1,510 @@ +package com.en_circle.slt.plugin.highlights; + +import com.en_circle.slt.plugin.SltCommonLispFileType; +import com.en_circle.slt.plugin.SltCommonLispLanguage; +import com.en_circle.slt.plugin.lisp.psi.LispTypes; +import com.intellij.codeInsight.daemon.impl.IdentifierHighlighterPassFactory; +import com.intellij.codeInsight.highlighting.BraceHighlightingHandler; +import com.intellij.codeInsight.highlighting.BraceMatchingUtil; +import com.intellij.codeInsight.highlighting.BraceMatchingUtil.BraceHighlightingAndNavigationContext; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; +import com.intellij.codeInsight.template.impl.TemplateState; +import com.intellij.injected.editor.EditorWindow; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.colors.CodeInsightColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.editor.event.*; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.editor.markup.HighlighterLayer; +import com.intellij.openapi.editor.markup.HighlighterTargetArea; +import com.intellij.openapi.editor.markup.RangeHighlighter; +import com.intellij.openapi.extensions.ExtensionPointUtil; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.fileTypes.BinaryFileTypeDecompilers; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupActivity; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiBinaryFile; +import com.intellij.psi.PsiCompiledFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.Alarm; +import com.intellij.util.containers.Stack; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +// Copied from IJ source to implement highlight WITHOUT smart insertion! + +public class SltBraceHighlighter implements StartupActivity.DumbAware { + private static final Key> BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY = Key.create("BraceHighlighter.BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY"); + private static final Key LINE_MARKER_IN_EDITOR_KEY = Key.create("BraceHighlighter.LINE_MARKER_IN_EDITOR_KEY"); + + private final Alarm alarm = new Alarm(); + + @Override + public void runActivity(@NotNull Project project) { + if (ApplicationManager.getApplication().isHeadlessEnvironment() && !ApplicationManager.getApplication().isUnitTestMode() || + !IdentifierHighlighterPassFactory.isEnabled()) { + return; + } + + Disposable activityDisposable = ExtensionPointUtil.createExtensionDisposable(this, StartupActivity.POST_STARTUP_ACTIVITY); + // lul they have this in code with warning... + Disposer.register(project, activityDisposable); + registerListeners(project, activityDisposable); + } + + private void registerListeners(@NotNull Project project, @NotNull Disposable parentDisposable) { + EditorEventMulticaster eventMulticaster = EditorFactory.getInstance().getEventMulticaster(); + + eventMulticaster.addCaretListener(new CaretListener() { + @Override + public void caretPositionChanged(@NotNull CaretEvent e) { + if (e.getCaret() != e.getEditor().getCaretModel().getPrimaryCaret()) return; + onCaretUpdate(e.getEditor(), project); + } + + @Override + public void caretAdded(@NotNull CaretEvent e) { + if (e.getCaret() != e.getEditor().getCaretModel().getPrimaryCaret()) return; + onCaretUpdate(e.getEditor(), project); + } + + @Override + public void caretRemoved(@NotNull CaretEvent e) { + onCaretUpdate(e.getEditor(), project); + } + }, parentDisposable); + + SelectionListener selectionListener = new SelectionListener() { + @Override + public void selectionChanged(@NotNull SelectionEvent e) { + alarm.cancelAllRequests(); + Editor editor = e.getEditor(); + if (editor.getProject() != project) { + return; + } + if (isNotCLFile(editor)) { + return; + } + + TextRange oldRange = e.getOldRange(); + TextRange newRange = e.getNewRange(); + if (oldRange != null && newRange != null && oldRange.isEmpty() == newRange.isEmpty()) { + return; + } + updateHighlighted(project, editor); + } + }; + eventMulticaster.addSelectionListener(selectionListener, parentDisposable); + + DocumentListener documentListener = new DocumentListener() { + @Override + public void documentChanged(@NotNull DocumentEvent e) { + alarm.cancelAllRequests(); + EditorFactory.getInstance().editors(e.getDocument(), project).forEach(editor -> updateHighlighted(project, editor)); + } + }; + eventMulticaster.addDocumentListener(documentListener, parentDisposable); + + project.getMessageBus().connect(parentDisposable) + .subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { + @Override + public void selectionChanged(@NotNull FileEditorManagerEvent e) { + alarm.cancelAllRequests(); + FileEditor oldEditor = e.getOldEditor(); + if (oldEditor instanceof TextEditor) { + clearBraces(((TextEditor) oldEditor).getEditor()); + } + FileEditor newEditor = e.getNewEditor(); + if (newEditor instanceof TextEditor) { + updateHighlighted(project, ((TextEditor) newEditor).getEditor()); + } + } + }); + } + + private boolean isNotCLFile(Editor editor) { + PsiFile file = PsiDocumentManager.getInstance(Objects.requireNonNull(editor.getProject())).getPsiFile(editor.getDocument()); + return file == null || !file.getFileType().equals(SltCommonLispFileType.INSTANCE); + } + + private void clearBraces(Editor editor) { + List highlighters = getHighlightersList(editor); + for (RangeHighlighter highlighter : highlighters) { + highlighter.dispose(); + } + highlighters.clear(); + } + + private void updateHighlighted(Project project, Editor editor) { + ApplicationManager.getApplication().assertIsDispatchThread(); + if (editor.getDocument().isInBulkUpdate()) { + return; + } + + clearBraces(editor); + + PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project); + if (psiFile == null) return; + if (psiFile instanceof PsiCompiledFile) { + psiFile = ((PsiCompiledFile) psiFile).getDecompiledPsiFile(); + } + if (psiFile instanceof PsiBinaryFile && BinaryFileTypeDecompilers.getInstance().forFileType(psiFile.getFileType()) == null) { + return; + } + + if (editor instanceof EditorEx) { + removeLineMarkers((EditorEx) editor); + } + + if (editor.getSelectionModel().hasSelection()) return; + + if (editor.getSoftWrapModel().isInsideOrBeforeSoftWrap(editor.getCaretModel().getVisualPosition())) return; + + TemplateState state = TemplateManagerImpl.getTemplateState(editor); + if (state != null && !state.isFinished()) return; + + int offset = editor.getCaretModel().getOffset(); + CharSequence chars = editor.getDocument().getCharsSequence(); + + alarm.cancelAllRequests(); + BraceHighlightingAndNavigationContext context = computeHighlightingAndNavigationContext(editor, psiFile, offset); + + if (context != null) { + doHighlight(editor, project, psiFile, context.currentBraceOffset, context.isCaretAfterBrace); + offset = context.currentBraceOffset; + } else if (offset > 0 && offset < chars.length()) { + // There is a possible case that there are paired braces nearby the caret position and the document contains only white + // space symbols between them. We want to highlight such braces as well. + // Example: + // public void test() { + // } + char c = chars.charAt(offset); + boolean searchForward = c != '\n'; + + // Try to find matched brace backwards. + int backwardNonSpaceEndOffset = CharArrayUtil.shiftBackward(chars, offset - 1, "\t ") + 1; + if (backwardNonSpaceEndOffset > 0 && backwardNonSpaceEndOffset < offset) { + context = computeHighlightingAndNavigationContext(editor, psiFile, backwardNonSpaceEndOffset); + if (context != null) { + doHighlight(editor, project, psiFile, context.currentBraceOffset, true); + offset = context.currentBraceOffset; + searchForward = false; + } + } + + // Try to find matched brace forward. + if (searchForward) { + int nextNonSpaceCharOffset = CharArrayUtil.shiftForward(chars, offset, "\t "); + if (nextNonSpaceCharOffset > offset) { + context = computeHighlightingAndNavigationContext(editor, psiFile, nextNonSpaceCharOffset); + if (context != null) { + doHighlight(editor, project, psiFile, context.currentBraceOffset, true); + offset = context.currentBraceOffset; + } + } + } + } + } + + private void doHighlight(Editor editor, Project project, PsiFile psiFile, int offset, boolean isAdjustedPosition) { + if (editor.getFoldingModel().isOffsetCollapsed(offset)) return; + Document document = editor.getDocument(); + + HighlighterIterator iterator = BraceHighlightingHandler.getLazyParsableHighlighterIfAny(project, editor, psiFile).createIterator(offset); + CharSequence chars = document.getCharsSequence(); + + FileType fileType = getFileTypeByOffset(psiFile, offset); + + if (isLBraceToken(iterator, chars, fileType)) { + highlightLeftBrace(editor, iterator, false, fileType, document); + + if (offset > 0 && !isAdjustedPosition && !editor.getSettings().isBlockCursor()) { + iterator = BraceHighlightingHandler.getLazyParsableHighlighterIfAny(project, editor, psiFile).createIterator(offset - 1); + if (isRBraceToken(iterator, chars, fileType)) { + highlightRightBrace(editor, iterator, fileType, document); + } + } + } else if (isRBraceToken(iterator, chars, fileType)) { + highlightRightBrace(editor, iterator, fileType, document); + } + } + + private void highlightRightBrace(Editor editor, @NotNull HighlighterIterator iterator, @NotNull FileType fileType, Document document) { + TextRange brace1 = TextRange.create(iterator.getStart(), iterator.getEnd()); + + boolean matched = matchBrace(document.getCharsSequence(), fileType, iterator, false); + + TextRange brace2 = iterator.atEnd() ? null : TextRange.create(iterator.getStart(), iterator.getEnd()); + + highlightBraces(editor, brace2, brace1, matched, false, fileType); + } + + private void highlightLeftBrace(Editor editor, @NotNull HighlighterIterator iterator, boolean scopeHighlighting, @NotNull FileType fileType, Document document) { + TextRange brace1Start = TextRange.create(iterator.getStart(), iterator.getEnd()); + boolean matched = matchBrace(document.getCharsSequence(), fileType, iterator, true); + + TextRange brace2End = iterator.atEnd() ? null : TextRange.create(iterator.getStart(), iterator.getEnd()); + + highlightBraces(editor, brace1Start, brace2End, matched, scopeHighlighting, fileType); + } + + private void highlightBraces(Editor editor, @Nullable TextRange lBrace, + @Nullable TextRange rBrace, boolean matched, boolean scopeHighlighting, @NotNull FileType fileType) { + if (!matched && fileType == FileTypes.PLAIN_TEXT) { + return; + } + + if (rBrace != null && !scopeHighlighting) { + highlightBrace(editor, rBrace, matched); + } + + if (lBrace != null && !scopeHighlighting) { + highlightBrace(editor, lBrace, matched); + } + } + + private void highlightBrace(Editor editor, @NotNull TextRange braceRange, boolean matched) { + TextAttributesKey attributesKey = matched ? CodeInsightColors.MATCHED_BRACE_ATTRIBUTES : CodeInsightColors.UNMATCHED_BRACE_ATTRIBUTES; + RangeHighlighter rbraceHighlighter = + editor.getMarkupModel().addRangeHighlighter(attributesKey, braceRange.getStartOffset(), braceRange.getEndOffset(), + HighlighterLayer.LAST + 1, HighlighterTargetArea.EXACT_RANGE); + rbraceHighlighter.setGreedyToLeft(false); + rbraceHighlighter.setGreedyToRight(false); + registerHighlighter(editor, rbraceHighlighter); + } + + private void registerHighlighter(Editor editor, @NotNull RangeHighlighter highlighter) { + getHighlightersList(editor).add(highlighter); + } + + @NotNull + private List getHighlightersList(Editor myEditor) { + // braces are highlighted across the whole editor, not in each injected editor separately + Editor editor = myEditor instanceof EditorWindow ? ((EditorWindow) myEditor).getDelegate() : myEditor; + List highlighters = editor.getUserData(BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY); + if (highlighters == null) { + highlighters = new ArrayList<>(); + editor.putUserData(BRACE_HIGHLIGHTERS_IN_EDITOR_VIEW_KEY, highlighters); + } + return highlighters; + } + + @NotNull + private static FileType getFileTypeByOffset(PsiFile psiFile, int offset) { + return PsiUtilBase.getPsiFileAtOffset(psiFile, offset).getFileType(); + } + + private static void removeLineMarkers(@NotNull EditorEx editor) { + ApplicationManager.getApplication().assertIsDispatchThread(); + RangeHighlighter marker = editor.getUserData(LINE_MARKER_IN_EDITOR_KEY); + if (marker != null && editor.getMarkupModel().containsHighlighter(marker)) { + marker.dispose(); + } + editor.putUserData(LINE_MARKER_IN_EDITOR_KEY, null); + } + + private void onCaretUpdate(Editor editor, Project project) { + alarm.cancelAllRequests(); + SelectionModel selectionModel = editor.getSelectionModel(); + if (editor.getProject() != project || selectionModel.hasSelection()) { + return; + } + if (isNotCLFile(editor)) { + return; + } + updateHighlighted(project, editor); + } + + @Nullable + private static BraceMatchingUtil.BraceHighlightingAndNavigationContext computeHighlightingAndNavigationContext(@NotNull Editor editor, + @NotNull PsiFile file, + int offset) { + EditorHighlighter highlighter = BraceHighlightingHandler.getLazyParsableHighlighterIfAny(file.getProject(), editor, file); + CharSequence text = editor.getDocument().getCharsSequence(); + + HighlighterIterator iterator = highlighter.createIterator(offset); + FileType fileType = iterator.atEnd() ? null : getFileType(file, iterator.getStart()); + + boolean isBeforeOrInsideLeftBrace = fileType != null && isLBraceToken(iterator, text, fileType); + boolean isBeforeOrInsideRightBrace = !isBeforeOrInsideLeftBrace && fileType != null && isRBraceToken(iterator, text, fileType); + boolean isInsideBrace = (isBeforeOrInsideLeftBrace || isBeforeOrInsideRightBrace) && iterator.getStart() < offset; + + HighlighterIterator preOffsetIterator = offset > 0 && !isInsideBrace ? highlighter.createIterator(offset - 1) : null; + FileType preOffsetFileType = preOffsetIterator != null ? getFileType(file, preOffsetIterator.getStart()) : null; + + boolean isAfterLeftBrace = preOffsetIterator != null && + isLBraceToken(preOffsetIterator, text, preOffsetFileType); + boolean isAfterRightBrace = !isAfterLeftBrace && preOffsetIterator != null && + isRBraceToken(preOffsetIterator, text, preOffsetFileType); + + int offsetTokenStart = iterator.atEnd() ? -1 : iterator.getStart(); + int preOffsetTokenStart = preOffsetIterator == null || preOffsetIterator.atEnd() ? -1 : preOffsetIterator.getStart(); + + if (editor.getSettings().isBlockCursor()) { + if (isBeforeOrInsideLeftBrace && matchBrace(text, fileType, iterator, true)) { + return new BraceHighlightingAndNavigationContext(offsetTokenStart, iterator.getStart(), isInsideBrace); + } else if (isBeforeOrInsideRightBrace && matchBrace(text, fileType, iterator, false)) { + return new BraceHighlightingAndNavigationContext(offsetTokenStart, iterator.getStart(), isInsideBrace); + } else if (isAfterRightBrace && matchBrace(text, preOffsetFileType, preOffsetIterator, false)) { + return new BraceHighlightingAndNavigationContext(preOffsetTokenStart, preOffsetIterator.getStart(), true); + } else if (isAfterLeftBrace && matchBrace(text, preOffsetFileType, preOffsetIterator, true)) { + return new BraceHighlightingAndNavigationContext(preOffsetTokenStart, preOffsetIterator.getStart(), true); + } + } else { + if (isAfterRightBrace && matchBrace(text, preOffsetFileType, preOffsetIterator, false)) { + return new BraceHighlightingAndNavigationContext(preOffsetTokenStart, preOffsetIterator.getStart(), true); + } else if (isBeforeOrInsideLeftBrace && matchBrace(text, fileType, iterator, true)) { + return new BraceHighlightingAndNavigationContext(offsetTokenStart, iterator.getEnd(), isInsideBrace); + } else if (isAfterLeftBrace && matchBrace(text, preOffsetFileType, preOffsetIterator, true)) { + return new BraceHighlightingAndNavigationContext(preOffsetTokenStart, preOffsetIterator.getEnd(), true); + } else if (isBeforeOrInsideRightBrace && matchBrace(text, fileType, iterator, false)) { + return new BraceHighlightingAndNavigationContext(offsetTokenStart, iterator.getStart(), isInsideBrace); + } + } + return null; + } + + private static boolean isLBraceToken(@NotNull HighlighterIterator iterator, @NotNull CharSequence fileText, @NotNull FileType fileType) { + IElementType tokenType = iterator.getTokenType(); + return tokenType.equals(LispTypes.LPAREN); + } + + private static boolean isRBraceToken(@NotNull HighlighterIterator iterator, @NotNull CharSequence fileText, @NotNull FileType fileType) { + IElementType tokenType = iterator.getTokenType(); + return tokenType.equals(LispTypes.RPAREN); + } + + @NotNull + private static FileType getFileType(PsiFile file, int offset) { + return PsiUtilBase.getPsiFileAtOffset(file, offset).getFileType(); + } + + private static class MatchBraceContext { + private final CharSequence fileText; + private final FileType fileType; + private final HighlighterIterator iterator; + private final boolean forward; + + private final IElementType brace1Token; + private final int group; + + private final Stack myBraceStack = new Stack<>(); + + MatchBraceContext(@NotNull CharSequence fileText, + @NotNull FileType fileType, + @NotNull HighlighterIterator iterator, + boolean forward) { + this.fileText = fileText; + this.fileType = fileType; + this.iterator = iterator; + this.forward = forward; + + brace1Token = this.iterator.getTokenType(); + group = getTokenGroup(brace1Token, this.fileType); + } + + private boolean doBraceMatch() { + myBraceStack.clear(); + myBraceStack.push(brace1Token); + boolean matched = false; + while (true) { + advance(iterator, forward); + if (iterator.atEnd()) { + break; + } + + IElementType tokenType = iterator.getTokenType(); + + if (getTokenGroup(tokenType, fileType) != group) { + continue; + } + + if (isBraceToken(iterator, fileText, fileType, !forward)) { + myBraceStack.push(tokenType); + } else if (isBraceToken(iterator, fileText, fileType, forward)) { + IElementType topTokenType = myBraceStack.pop(); + + IElementType baseType = getOppositeBraceTokenType(tokenType); + if (myBraceStack.contains(baseType)) { + while (!isPairBraces(topTokenType, tokenType, fileType) && !myBraceStack.empty()) { + topTokenType = myBraceStack.pop(); + } + } + + if (!isPairBraces(topTokenType, tokenType, fileType)) { + break; + } + + if (myBraceStack.isEmpty()) { + matched = true; + break; + } + } + } + return matched; + } + + private IElementType getOppositeBraceTokenType(IElementType tokenType) { + if (tokenType.equals(LispTypes.LPAREN)) + return LispTypes.RPAREN; + else if (tokenType.equals(LispTypes.RPAREN)) + return LispTypes.LPAREN; + return null; + } + } + + private static synchronized boolean matchBrace(@NotNull CharSequence fileText, + @NotNull FileType fileType, + @NotNull HighlighterIterator iterator, + boolean forward) { + return new MatchBraceContext(fileText, fileType, iterator, forward).doBraceMatch(); + } + + + private static boolean isPairBraces(@NotNull IElementType tokenType1, @NotNull IElementType tokenType2, @NotNull FileType fileType) { + return (tokenType1.equals(LispTypes.LPAREN) && tokenType2.equals(LispTypes.RPAREN)) || (tokenType1.equals(LispTypes.RPAREN) && tokenType2.equals(LispTypes.LPAREN)); + } + + private static int getTokenGroup(@Nullable IElementType tokenType, FileType fileType) { + return LispTypes.LPAREN.equals(tokenType) || LispTypes.RPAREN.equals(tokenType) ? SltCommonLispLanguage.INSTANCE.hashCode() : -1; + } + + private static boolean isBraceToken(@NotNull HighlighterIterator iterator, + @NotNull CharSequence fileText, + @NotNull FileType fileType, + boolean searchingForRight) { + return searchingForRight ? isRBraceToken(iterator, fileText, fileType) + : isLBraceToken(iterator, fileText, fileType); + } + + private static void advance(@NotNull HighlighterIterator iterator, boolean forward) { + if (forward) { + iterator.advance(); + } else { + iterator.retreat(); + } + } +} diff --git a/src/main/java/com/en_circle/slt/plugin/highlights/SltColorSettingsPage.java b/src/main/java/com/en_circle/slt/plugin/highlights/SltColorSettingsPage.java index a2b8ac7..4136409 100644 --- a/src/main/java/com/en_circle/slt/plugin/highlights/SltColorSettingsPage.java +++ b/src/main/java/com/en_circle/slt/plugin/highlights/SltColorSettingsPage.java @@ -21,18 +21,20 @@ public class SltColorSettingsPage implements ColorSettingsPage { public static final String DEMO_CODE = new CodeHighlightTemplate().render(); private static final AttributesDescriptor[] DESCRIPTORS = new AttributesDescriptor[] { - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.parenthesis"), CommonLispHighlighterColors.PARENTS), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.number"), CommonLispHighlighterColors.NUMBER), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.stringliteral"), CommonLispHighlighterColors.STRING), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.comment"), CommonLispHighlighterColors.COMMENT), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.keyword"), CommonLispHighlighterColors.KEYWORD), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.helper"), CommonLispHighlighterColors.DEFUN_FORM), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.specvariable"), CommonLispHighlighterColors.SPECIAL_VARIABLE), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.constant"), CommonLispHighlighterColors.CONSTANT), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.specialform"), CommonLispHighlighterColors.SPECIAL_FORM), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.function"), CommonLispHighlighterColors.FUNCTION), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.macro"), CommonLispHighlighterColors.MACRO), - new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.reader"), CommonLispHighlighterColors.SUGAR), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.parenthesis"), SltHighlighterColors.PARENTS), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.number"), SltHighlighterColors.NUMBER), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.stringliteral"), SltHighlighterColors.STRING), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.comment"), SltHighlighterColors.COMMENT), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.keyword"), SltHighlighterColors.KEYWORD), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.helper"), SltHighlighterColors.DEFUN_FORM), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.specvariable"), SltHighlighterColors.SPECIAL_VARIABLE), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.constant"), SltHighlighterColors.CONSTANT), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.specialform"), SltHighlighterColors.SPECIAL_FORM), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.function"), SltHighlighterColors.FUNCTION), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.macro"), SltHighlighterColors.MACRO), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.reader"), SltHighlighterColors.SUGAR), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.class"), SltHighlighterColors.CLASS), + new AttributesDescriptor(SltBundle.message("slt.ui.colorsettings.method"), SltHighlighterColors.METHOD), }; @Override @@ -42,7 +44,7 @@ public class SltColorSettingsPage implements ColorSettingsPage { @Override public @NotNull SyntaxHighlighter getHighlighter() { - return new CommonLispStaticHighlighter(); + return new SltStaticHighlighter(); } @Override diff --git a/src/main/java/com/en_circle/slt/plugin/highlights/CommonLispHighlighterColors.java b/src/main/java/com/en_circle/slt/plugin/highlights/SltHighlighterColors.java similarity index 91% rename from src/main/java/com/en_circle/slt/plugin/highlights/CommonLispHighlighterColors.java rename to src/main/java/com/en_circle/slt/plugin/highlights/SltHighlighterColors.java index 1824605..9740cc6 100644 --- a/src/main/java/com/en_circle/slt/plugin/highlights/CommonLispHighlighterColors.java +++ b/src/main/java/com/en_circle/slt/plugin/highlights/SltHighlighterColors.java @@ -7,7 +7,7 @@ import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.psi.PsiElement; -public class CommonLispHighlighterColors { +public class SltHighlighterColors { public static TextAttributesKey COMMENT = TextAttributesKey.createTextAttributesKey("CL.COMMENT", DefaultLanguageHighlighterColors.BLOCK_COMMENT); public static TextAttributesKey PARENTS = TextAttributesKey.createTextAttributesKey("CL.PARENTS", DefaultLanguageHighlighterColors.PARENTHESES); @@ -21,6 +21,8 @@ public class CommonLispHighlighterColors { public static TextAttributesKey SPECIAL_FORM = TextAttributesKey.createTextAttributesKey("CL.SPECIAL_FORM", DefaultLanguageHighlighterColors.KEYWORD); public static TextAttributesKey FUNCTION = TextAttributesKey.createTextAttributesKey("CL.FUNCTION", DefaultLanguageHighlighterColors.FUNCTION_CALL); public static TextAttributesKey MACRO = TextAttributesKey.createTextAttributesKey("CL.MACRO", DefaultLanguageHighlighterColors.KEYWORD); + public static TextAttributesKey CLASS = TextAttributesKey.createTextAttributesKey("CL.CLASS", DefaultLanguageHighlighterColors.CLASS_NAME); + public static TextAttributesKey METHOD = TextAttributesKey.createTextAttributesKey("CL.METHOD", DefaultLanguageHighlighterColors.INSTANCE_METHOD); public static void setHighlighting(PsiElement element, AnnotationHolder holder, TextAttributesKey key) { // Annotation a = holder.createInfoAnnotation(element, null); diff --git a/src/main/java/com/en_circle/slt/plugin/highlights/CommonLispStaticHighlighter.java b/src/main/java/com/en_circle/slt/plugin/highlights/SltStaticHighlighter.java similarity index 68% rename from src/main/java/com/en_circle/slt/plugin/highlights/CommonLispStaticHighlighter.java rename to src/main/java/com/en_circle/slt/plugin/highlights/SltStaticHighlighter.java index 4fadb05..2646add 100644 --- a/src/main/java/com/en_circle/slt/plugin/highlights/CommonLispStaticHighlighter.java +++ b/src/main/java/com/en_circle/slt/plugin/highlights/SltStaticHighlighter.java @@ -10,19 +10,19 @@ import java.util.HashMap; -public class CommonLispStaticHighlighter extends SyntaxHighlighterBase { +public class SltStaticHighlighter extends SyntaxHighlighterBase { private static final HashMap colors = new HashMap<>(); static { - SyntaxHighlighterBase.fillMap(colors, CommonLispHighlighterColors.COMMENT, - LispTypes.COMMENT, LispTypes.BLOCK_COMMENT); - SyntaxHighlighterBase.fillMap(colors, CommonLispHighlighterColors.PARENTS, LispTypes.LPAREN, LispTypes.RPAREN); - SyntaxHighlighterBase.fillMap(colors, CommonLispHighlighterColors.NUMBER, + SyntaxHighlighterBase.fillMap(colors, SltHighlighterColors.COMMENT, + LispTypes.LINE_COMMENT, LispTypes.BLOCK_COMMENT); + SyntaxHighlighterBase.fillMap(colors, SltHighlighterColors.PARENTS, LispTypes.LPAREN, LispTypes.RPAREN); + SyntaxHighlighterBase.fillMap(colors, SltHighlighterColors.NUMBER, LispTypes.BIT_ARRAY, LispTypes.BINARY_NUMBER_TOKEN, LispTypes.HEX_NUMBER_TOKEN, LispTypes.RADIX_NUMBER_TOKEN, LispTypes.REAL_NUMBER, LispTypes.INTEGER_NUMBER, LispTypes.RATIO_NUMBER); - SyntaxHighlighterBase.fillMap(colors, CommonLispHighlighterColors.STRING, LispTypes.STRING_TOKEN); - SyntaxHighlighterBase.fillMap(colors, CommonLispHighlighterColors.SUGAR, + SyntaxHighlighterBase.fillMap(colors, SltHighlighterColors.STRING, LispTypes.STRING_TOKEN); + SyntaxHighlighterBase.fillMap(colors, SltHighlighterColors.SUGAR, LispTypes.FUNCTION, LispTypes.UNINTERN, LispTypes.REFERENCE_SET, LispTypes.REFERENCE_LABEL, LispTypes.TEST_SUCCESS, LispTypes.TEST_FALURE, LispTypes.EVAL_VALUE, LispTypes.ARRAY_START, LispTypes.PATHNAME_INDICATOR, LispTypes.STRUCTURE_TOKEN); diff --git a/src/main/java/com/en_circle/slt/plugin/highlights/CommonLispStaticHighlighterFactory.java b/src/main/java/com/en_circle/slt/plugin/highlights/SltStaticHighlighterFactory.java similarity index 78% rename from src/main/java/com/en_circle/slt/plugin/highlights/CommonLispStaticHighlighterFactory.java rename to src/main/java/com/en_circle/slt/plugin/highlights/SltStaticHighlighterFactory.java index d0d4049..03d1023 100644 --- a/src/main/java/com/en_circle/slt/plugin/highlights/CommonLispStaticHighlighterFactory.java +++ b/src/main/java/com/en_circle/slt/plugin/highlights/SltStaticHighlighterFactory.java @@ -7,11 +7,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class CommonLispStaticHighlighterFactory extends SyntaxHighlighterFactory { +public class SltStaticHighlighterFactory extends SyntaxHighlighterFactory { @Override public @NotNull SyntaxHighlighter getSyntaxHighlighter(@Nullable Project project, @Nullable VirtualFile virtualFile) { - return new CommonLispStaticHighlighter(); + return new SltStaticHighlighter(); } } diff --git a/src/main/java/com/en_circle/slt/plugin/highlights/annotators/StaticHighlightAnnotator.java b/src/main/java/com/en_circle/slt/plugin/highlights/annotators/StaticHighlightAnnotator.java index 9c6234b..e36b125 100644 --- a/src/main/java/com/en_circle/slt/plugin/highlights/annotators/StaticHighlightAnnotator.java +++ b/src/main/java/com/en_circle/slt/plugin/highlights/annotators/StaticHighlightAnnotator.java @@ -1,6 +1,6 @@ package com.en_circle.slt.plugin.highlights.annotators; -import com.en_circle.slt.plugin.highlights.CommonLispHighlighterColors; +import com.en_circle.slt.plugin.highlights.SltHighlighterColors; import com.en_circle.slt.plugin.lisp.psi.LispTypes; import com.intellij.lang.ASTNode; import com.intellij.lang.annotation.AnnotationHolder; @@ -14,8 +14,8 @@ public class StaticHighlightAnnotator implements Annotator { public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { ASTNode node = element.getNode(); if (node != null && LispTypes.REAL_PAIR.equals(node.getElementType())) { - CommonLispHighlighterColors.setHighlighting(element, holder, - CommonLispHighlighterColors.NUMBER); + SltHighlighterColors.setHighlighting(element, holder, + SltHighlighterColors.NUMBER); } } diff --git a/src/main/java/com/en_circle/slt/plugin/highlights/annotators/SymbolAnnotator.java b/src/main/java/com/en_circle/slt/plugin/highlights/annotators/SymbolAnnotator.java index ed899f2..1c8d847 100644 --- a/src/main/java/com/en_circle/slt/plugin/highlights/annotators/SymbolAnnotator.java +++ b/src/main/java/com/en_circle/slt/plugin/highlights/annotators/SymbolAnnotator.java @@ -1,10 +1,10 @@ package com.en_circle.slt.plugin.highlights.annotators; -import com.en_circle.slt.plugin.SltSBCL; import com.en_circle.slt.plugin.SymbolState; -import com.en_circle.slt.plugin.highlights.CommonLispHighlighterColors; +import com.en_circle.slt.plugin.highlights.SltHighlighterColors; import com.en_circle.slt.plugin.lisp.LispParserUtil; import com.en_circle.slt.plugin.lisp.psi.LispSymbol; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.Annotator; import com.intellij.psi.PsiElement; @@ -17,42 +17,50 @@ public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder hold if (element instanceof LispSymbol) { String text = element.getText(); String packageName = LispParserUtil.getPackage(element); - SymbolState state = SltSBCL.getInstance().refreshSymbolFromServer(packageName, text, element); + SymbolState state = LispEnvironmentService.getInstance(element.getProject()).refreshSymbolFromServer(packageName, text, element); setHighlight(element, text, holder, state); } } private void setHighlight(PsiElement element, String name, AnnotationHolder holder, SymbolState state) { if (name.startsWith("&")) - CommonLispHighlighterColors.setHighlighting(element, holder, - CommonLispHighlighterColors.DEFUN_FORM); + SltHighlighterColors.setHighlighting(element, holder, + SltHighlighterColors.DEFUN_FORM); switch (state.binding) { case NONE: break; + case CLASS: + SltHighlighterColors.setHighlighting(element, holder, + SltHighlighterColors.CLASS); + break; + case METHOD: + SltHighlighterColors.setHighlighting(element, holder, + SltHighlighterColors.METHOD); + break; case FUNCTION: - CommonLispHighlighterColors.setHighlighting(element, holder, - CommonLispHighlighterColors.FUNCTION); + SltHighlighterColors.setHighlighting(element, holder, + SltHighlighterColors.FUNCTION); break; case MACRO: - CommonLispHighlighterColors.setHighlighting(element, holder, - CommonLispHighlighterColors.MACRO); + SltHighlighterColors.setHighlighting(element, holder, + SltHighlighterColors.MACRO); break; case SPECIAL_FORM: - CommonLispHighlighterColors.setHighlighting(element, holder, - CommonLispHighlighterColors.SPECIAL_FORM); + SltHighlighterColors.setHighlighting(element, holder, + SltHighlighterColors.SPECIAL_FORM); break; case CONSTANT: - CommonLispHighlighterColors.setHighlighting(element, holder, - CommonLispHighlighterColors.CONSTANT); + SltHighlighterColors.setHighlighting(element, holder, + SltHighlighterColors.CONSTANT); break; case SPECIAL_VARIABLE: - CommonLispHighlighterColors.setHighlighting(element, holder, - CommonLispHighlighterColors.SPECIAL_VARIABLE); + SltHighlighterColors.setHighlighting(element, holder, + SltHighlighterColors.SPECIAL_VARIABLE); break; case KEYWORD: - CommonLispHighlighterColors.setHighlighting(element, holder, - CommonLispHighlighterColors.KEYWORD); + SltHighlighterColors.setHighlighting(element, holder, + SltHighlighterColors.KEYWORD); break; } } diff --git a/src/main/java/com/en_circle/slt/plugin/lisp/Lisp.bnf b/src/main/java/com/en_circle/slt/plugin/lisp/Lisp.bnf index 6a5f6f1..4313672 100644 --- a/src/main/java/com/en_circle/slt/plugin/lisp/Lisp.bnf +++ b/src/main/java/com/en_circle/slt/plugin/lisp/Lisp.bnf @@ -24,9 +24,9 @@ sexpr ::= (enhancement* datum) | comment comment ::= LINE_COMMENT | BLOCK_COMMENT -enhancement ::= REFERENCE_SET | REFERENCE_LABEL | TEST_SUCCESS | COMMA | BACKQUOTE | QUOTE | FUNCTION +enhancement ::= REFERENCE_SET | TEST_SUCCESS | COMMA | BACKQUOTE | QUOTE | FUNCTION -datum ::= tested | evaled | pathname | UNDEFINED_SEQUENCE | BIT_ARRAY | CHARACTER +datum ::= tested | evaled | pathname | UNDEFINED_SEQUENCE | BIT_ARRAY | CHARACTER | REFERENCE_LABEL | number | real_pair | compound_symbol | string | vector | array | structure | list | pair diff --git a/src/main/java/com/en_circle/slt/plugin/lisp/LispParserUtil.java b/src/main/java/com/en_circle/slt/plugin/lisp/LispParserUtil.java index 8046686..86fb991 100644 --- a/src/main/java/com/en_circle/slt/plugin/lisp/LispParserUtil.java +++ b/src/main/java/com/en_circle/slt/plugin/lisp/LispParserUtil.java @@ -1,11 +1,12 @@ package com.en_circle.slt.plugin.lisp; -import com.en_circle.slt.plugin.SltSBCL; import com.en_circle.slt.plugin.lisp.lisp.LispUtils; import com.en_circle.slt.plugin.lisp.psi.*; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; import com.intellij.lang.ASTNode; import com.intellij.lang.FileASTNode; import com.intellij.lang.parser.GeneratedParserUtilBase; +import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -19,12 +20,16 @@ public static String getPackage(PsiFile psiFile, int offset) { if (locationNode != null) return getPackage(locationNode.getPsi()); else - return SltSBCL.getInstance().getGlobalPackage(); + return LispEnvironmentService.getInstance(psiFile.getProject()).getGlobalPackage(); } public static String getPackage(PsiElement element) { + Project project = element.getProject(); while (!(element instanceof LispToplevel)) { element = element.getParent(); + if (element == null) { + return LispEnvironmentService.getInstance(project).getGlobalPackage(); + } } PsiElement previous = element.getPrevSibling(); while (previous != null) { @@ -38,15 +43,14 @@ public static String getPackage(PsiElement element) { previous = previous.getPrevSibling(); } - return SltSBCL.getInstance().getGlobalPackage(); + return LispEnvironmentService.getInstance(element.getProject()).getGlobalPackage(); } private static LispList isLispList(PsiElement form) { if (form instanceof LispList) { return (LispList) form; } - if (form instanceof LispToplevel) { - LispToplevel toplevel = (LispToplevel) form; + if (form instanceof LispToplevel toplevel) { LispSexpr sexpr = toplevel.getSexpr(); if (sexpr.getDatum() != null && sexpr.getDatum().getList() != null) { return sexpr.getDatum().getList(); @@ -94,4 +98,20 @@ private static String getAsSymbol(LispSexpr seSymbol) { } return null; } + + public static LispList getIfHead(PsiElement element) { + PsiElement original = element; + while (!(element instanceof LispList list)) { + element = element.getParent(); + if (element == null) { + return null; + } + } + LispSexpr firstElement = list.getSexprList().get(0); + if (firstElement.getDatum() != null && firstElement.getDatum().getCompoundSymbol() != null && + firstElement.getDatum().getCompoundSymbol().getSymbol().equals(original)) { + return list; + } + return null; + } } diff --git a/src/main/java/com/en_circle/slt/plugin/lisp/psi/SltPatterns.java b/src/main/java/com/en_circle/slt/plugin/lisp/psi/SltPatterns.java new file mode 100644 index 0000000..beafd04 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/lisp/psi/SltPatterns.java @@ -0,0 +1,19 @@ +package com.en_circle.slt.plugin.lisp.psi; + +import com.intellij.patterns.ElementPattern; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.PsiElement; + +public class SltPatterns { + + public static ElementPattern getLParen() { + return PlatformPatterns.psiElement(LispTypes.LPAREN); + } + + public static ElementPattern getHeadPattern() { + return PlatformPatterns.psiElement(LispTypes.SYMBOL_TOKEN) + .afterLeaf("(") + .andNot(PlatformPatterns.psiElement().withParent(LispString.class)); + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/references/SltDirectNavigationProvider.java b/src/main/java/com/en_circle/slt/plugin/references/SltDirectNavigationProvider.java index a0719bd..df2e339 100644 --- a/src/main/java/com/en_circle/slt/plugin/references/SltDirectNavigationProvider.java +++ b/src/main/java/com/en_circle/slt/plugin/references/SltDirectNavigationProvider.java @@ -1,9 +1,9 @@ package com.en_circle.slt.plugin.references; -import com.en_circle.slt.plugin.SltSBCL; import com.en_circle.slt.plugin.SymbolState; import com.en_circle.slt.plugin.lisp.LispParserUtil; import com.en_circle.slt.plugin.lisp.psi.LispSymbol; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; import com.en_circle.slt.plugin.swank.components.SourceLocation; import com.intellij.lang.ASTNode; import com.intellij.lang.FileASTNode; @@ -27,7 +27,7 @@ public class SltDirectNavigationProvider implements DirectNavigationProvider { String packageName = LispParserUtil.getPackage(element); Project project = element.getProject(); String symbolName = ((LispSymbol) element).getName(); - SymbolState state = SltSBCL.getInstance().refreshSymbolFromServer(packageName, symbolName, element); + SymbolState state = LispEnvironmentService.getInstance(element.getProject()).refreshSymbolFromServer(packageName, symbolName, element); SourceLocation location = state.location; if (location.isFile()) { VirtualFile vf = LocalFileSystem.getInstance().findFileByIoFile(new File(location.getLocation())); diff --git a/src/main/java/com/en_circle/slt/plugin/references/SltReference.java b/src/main/java/com/en_circle/slt/plugin/references/SltReference.java index 550a1b6..dcac5f2 100644 --- a/src/main/java/com/en_circle/slt/plugin/references/SltReference.java +++ b/src/main/java/com/en_circle/slt/plugin/references/SltReference.java @@ -1,9 +1,9 @@ package com.en_circle.slt.plugin.references; -import com.en_circle.slt.plugin.SltSBCL; import com.en_circle.slt.plugin.SymbolState; import com.en_circle.slt.plugin.lisp.LispParserUtil; import com.en_circle.slt.plugin.lisp.psi.LispSymbol; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; import com.en_circle.slt.plugin.swank.components.SourceLocation; import com.intellij.lang.ASTNode; import com.intellij.lang.FileASTNode; @@ -26,7 +26,7 @@ public SltReference(@NotNull LispSymbol element) { public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) { String symbolName = myElement.getName(); String packageName = LispParserUtil.getPackage(myElement); - SymbolState state = SltSBCL.getInstance().refreshSymbolFromServer(packageName, symbolName, myElement); + SymbolState state = LispEnvironmentService.getInstance(myElement.getProject()).refreshSymbolFromServer(packageName, symbolName, myElement); SourceLocation location = state.location; if (location.isFile()) { VirtualFile vf = LocalFileSystem.getInstance().findFileByIoFile(new File(location.getLocation())); diff --git a/src/main/java/com/en_circle/slt/plugin/services/lisp/LispEnvironmentService.java b/src/main/java/com/en_circle/slt/plugin/services/lisp/LispEnvironmentService.java new file mode 100644 index 0000000..fdaacf3 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/services/lisp/LispEnvironmentService.java @@ -0,0 +1,67 @@ +package com.en_circle.slt.plugin.services.lisp; + +import com.en_circle.slt.plugin.SymbolState; +import com.en_circle.slt.plugin.environment.SltLispEnvironment; +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltLispOutputChangedListener; +import com.en_circle.slt.plugin.lisp.lisp.LispElement; +import com.en_circle.slt.plugin.lisp.psi.LispList; +import com.en_circle.slt.plugin.swank.SlimeListener.DebugInterface; +import com.en_circle.slt.plugin.swank.SlimeListener.RequestResponseLogger; +import com.en_circle.slt.plugin.swank.SlimeRequest; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; + + +public interface LispEnvironmentService extends Disposable { + + static LispEnvironmentService getInstance(Project project) { + LispEnvironmentServiceImpl impl = (LispEnvironmentServiceImpl) project.getService(LispEnvironmentService.class); + boolean wasInit = impl.initProject(project); + if (wasInit) { + impl.postInit(); + } + return impl; + } + + void resetConfiguration(); + + void addServerListener(LispEnvironmentListener listener); + + void setRequestResponseLogger(RequestResponseLogger logger); + + void setDebugInterface(DebugInterface debugInterface); + + void start(); + + void stop(); + + void sendToLisp(SlimeRequest request) throws Exception; + + void sendToLisp(SlimeRequest request, boolean startServer) throws Exception; + + String getGlobalPackage(); + + SymbolState refreshSymbolFromServer(String packageName, String symbolName, PsiElement element); + + LispEnvironmentState getState(); + + SltLispEnvironment getEnvironment(); + + String macroexpand(LispList form, String packageName); + + void updateIndentation(LispElement element); + + enum LispEnvironmentState { + STOPPED, READY, INITIALIZING + } + + interface LispEnvironmentListener extends SltLispOutputChangedListener { + + void onPreStart(); + void onPostStart(); + void onPreStop(); + void onPostStop(); + + } +} diff --git a/src/main/java/com/en_circle/slt/plugin/services/lisp/LispEnvironmentServiceImpl.java b/src/main/java/com/en_circle/slt/plugin/services/lisp/LispEnvironmentServiceImpl.java new file mode 100644 index 0000000..9054648 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/services/lisp/LispEnvironmentServiceImpl.java @@ -0,0 +1,288 @@ +package com.en_circle.slt.plugin.services.lisp; + +import com.en_circle.slt.plugin.SltBundle; +import com.en_circle.slt.plugin.SltState; +import com.en_circle.slt.plugin.SymbolState; +import com.en_circle.slt.plugin.environment.*; +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltOutput; +import com.en_circle.slt.plugin.lisp.lisp.LispContainer; +import com.en_circle.slt.plugin.lisp.lisp.LispElement; +import com.en_circle.slt.plugin.lisp.psi.LispList; +import com.en_circle.slt.plugin.services.lisp.components.SltIndentationContainer; +import com.en_circle.slt.plugin.services.lisp.components.SltLispEnvironmentMacroExpandCache; +import com.en_circle.slt.plugin.services.lisp.components.SltLispEnvironmentSymbolCache; +import com.en_circle.slt.plugin.swank.SlimeListener; +import com.en_circle.slt.plugin.swank.SlimeListener.DebugInterface; +import com.en_circle.slt.plugin.swank.SlimeListener.RequestResponseLogger; +import com.en_circle.slt.plugin.swank.SlimeRequest; +import com.en_circle.slt.plugin.swank.SwankClient; +import com.en_circle.slt.tools.ProjectUtils; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.psi.PsiElement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +public class LispEnvironmentServiceImpl implements LispEnvironmentService { + private static final Logger log = LoggerFactory.getLogger(LispEnvironmentServiceImpl.class); + + private Supplier environmentProvider; + private SltLispEnvironment environment; + private SltLispEnvironmentConfiguration.Builder configurationBuilder; + private SltLispEnvironmentConfiguration configuration; + private SwankClient client; + private SlimeListener slimeListener; + private RequestResponseLogger logger; + private DebugInterface debugInterface; + private final List serverListeners = Collections.synchronizedList(new ArrayList<>()); + private volatile boolean starting = false; + + private Project project; + private SltIndentationContainer indentationContainer; + private SltLispEnvironmentSymbolCache symbolCache; + private SltLispEnvironmentMacroExpandCache macroExpandCache; + + public synchronized boolean initProject(Project project) { + if (this.project == null) { + indentationContainer = new SltIndentationContainer(); + symbolCache = new SltLispEnvironmentSymbolCache(project); + macroExpandCache = new SltLispEnvironmentMacroExpandCache(); + this.project = project; + return true; + } + return false; + } + + public void postInit() { + symbolCache.start(); + } + + @Override + public void resetConfiguration() { + this.configurationBuilder = null; + } + + public boolean configured() { + environmentProvider = SltSBCLEnvironment::new; + configurationBuilder = new SltSBCLEnvironmentConfiguration.Builder() + .setExecutable(SltState.getInstance().sbclExecutable) + .setPort(SltState.getInstance().port) + .setQuicklispStartScriptPath(SltState.getInstance().quicklispStartScript) + .setProjectDirectory(ProjectUtils.getCurrentProject().getBasePath()); + + // add validation checks and custom "SDK" + + return true; + } + + @Override + public void addServerListener(LispEnvironmentListener listener) { + serverListeners.add(listener); + } + + @Override + public void setRequestResponseLogger(RequestResponseLogger logger) { + this.logger = logger; + } + + @Override + public void setDebugInterface(DebugInterface debugInterface) { + this.debugInterface = debugInterface; + } + + @Override + public void start() { + starting = true; + ApplicationManager.getApplication().invokeLater(() -> { + try { + ensureToolWindowIsOpen(); + doStart(); + } catch (Exception e) { + log.warn(SltBundle.message("slt.error.sbclstart"), e); + Messages.showErrorDialog(ProjectUtils.getCurrentProject(), e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); + } + }); + } + + private void ensureToolWindowIsOpen() { + ToolWindow toolWindow = ToolWindowManager.getInstance(ProjectUtils.getCurrentProject()) + .getToolWindow("Common Lisp"); + assert toolWindow != null; + toolWindow.show(); + } + + @Override + public void stop() { + ApplicationManager.getApplication().invokeLater(() -> { + try { + doStop(); + } catch (Exception e) { + log.warn(SltBundle.message("slt.error.sbclstop"), e); + Messages.showErrorDialog(ProjectUtils.getCurrentProject(), e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.stop")); + } + }); + } + + private void doStart() throws Exception { + try { + if (configurationBuilder == null) { + if (!configured()) { + log.warn(SltBundle.message("slt.error.sbclstart")); + Messages.showErrorDialog(ProjectUtils.getCurrentProject(), + SltBundle.message("slt.ui.errors.sbcl.start.noconf"), + SltBundle.message("slt.ui.errors.sbcl.start")); + return; + } + } + + for (LispEnvironmentListener listener : serverListeners) { + listener.onPreStart(); + } + + if (configuration == null) { + configuration = configurationBuilder + .setListener(this::onServerOutput) + .build(); + } + environment = environmentProvider.get(); + environment.start(configuration); + + slimeListener = new SlimeListener(ProjectUtils.getCurrentProject(), true, logger, debugInterface); + client = new SwankClient("127.0.0.1", SltState.getInstance().port, slimeListener); + + for (LispEnvironmentListener listener : serverListeners) { + listener.onPostStart(); + } + } finally { + starting = false; + } + } + + private void onServerOutput(SltOutput output, String newData) { + for (LispEnvironmentListener listener : serverListeners) { + listener.onOutputChanged(output, newData); + } + } + + private void doStop() throws Exception { + for (LispEnvironmentListener listener : serverListeners) { + listener.onPreStop(); + } + try { + client.close(); + } finally { + indentationContainer.clear(); + indentationContainer.clear(); + symbolCache.clear(); + if (environment != null) { + environment.stop(); + environment = null; + } + } + + for (LispEnvironmentListener listener : serverListeners) { + listener.onPostStop(); + } + } + + @Override + public void sendToLisp(SlimeRequest request) throws Exception { + sendToLisp(request, true); + } + + @Override + public void sendToLisp(SlimeRequest request, boolean startServer) throws Exception { + if (startServer && environment == null || !environment.isActive()) { + ApplicationManager.getApplication().invokeLater(() -> { + starting = true; + ApplicationManager.getApplication().invokeLater(() -> { + try { + ensureToolWindowIsOpen(); + doStart(); + doSend(request); + } catch (Exception e) { + log.warn(SltBundle.message("slt.error.sbclstart"), e); + Messages.showErrorDialog(ProjectUtils.getCurrentProject(), e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); + } + }); + }); + return; + } + + if (environment == null || !environment.isActive()) { + if (!startServer) + return; // ignored + throw new SltProcessException("server offline"); + } + + doSend(request); + } + + private void doSend(SlimeRequest request) { + if (slimeListener != null) { + slimeListener.call(request, client); + } + } + + @Override + public String getGlobalPackage() { + return "CL-USER"; + } + + @Override + public SymbolState refreshSymbolFromServer(String packageName, String symbolName, PsiElement element) { + return symbolCache.refreshSymbolFromServer(packageName, symbolName, element); + } + + @Override + public LispEnvironmentState getState() { + if (starting) { + return LispEnvironmentState.INITIALIZING; + } + if (environment != null && environment.isActive()) { + return LispEnvironmentState.READY; + } + return LispEnvironmentState.STOPPED; + } + + @Override + public SltLispEnvironment getEnvironment() { + return environment; + } + + @Override + public String macroexpand(LispList form, String packageName) { + try { + return macroExpandCache.macroexpand(form, packageName); + } catch (Exception e) { + log.error(e.getMessage()); + log.debug(e.getMessage(), e); + } + return null; + } + + @Override + public void updateIndentation(LispElement element) { + indentationContainer.update((LispContainer) element); + } + + @Override + public void dispose() { + try { + doStop(); + } catch (Exception ignored) { + + } + if (symbolCache != null) + symbolCache.terminate(); + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/services/lisp/components/SltIndentationContainer.java b/src/main/java/com/en_circle/slt/plugin/services/lisp/components/SltIndentationContainer.java new file mode 100644 index 0000000..daca83c --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/services/lisp/components/SltIndentationContainer.java @@ -0,0 +1,46 @@ +package com.en_circle.slt.plugin.services.lisp.components; + +import com.en_circle.slt.plugin.lisp.lisp.LispContainer; +import com.en_circle.slt.plugin.lisp.lisp.LispElement; +import com.en_circle.slt.plugin.lisp.lisp.LispInteger; +import com.en_circle.slt.plugin.lisp.lisp.LispString; + +import java.math.BigInteger; +import java.util.*; + +public class SltIndentationContainer { + + private final Map indentations = new HashMap<>(); + + public synchronized void update(LispContainer updates) { + for (LispElement element : updates.getItems()) { + LispContainer macro = (LispContainer) element; + LispString symbolNameElement = (LispString) macro.getItems().get(0); + String symbolName = symbolNameElement.getValue(); + IndentationUpdate update = indentations.computeIfAbsent(symbolName, s -> new IndentationUpdate()); + update.name = symbolName.toUpperCase(Locale.ROOT); + update.bodyArg = (macro.getItems().get(1) instanceof LispInteger) ? ((LispInteger) macro.getItems().get(1)).getValue() : null; + update.packages.clear(); + if (macro.getItems().size() > 2 && macro.getItems().get(2) instanceof LispContainer packages) { + for (LispElement packageNameValue : packages.getItems()) { + if (packageNameValue instanceof LispString packageName) { + update.packages.add(packageName.getValue()); + } + } + } + } + } + + public synchronized void clear() { + indentations.clear(); + } + + private static class IndentationUpdate { + + private String name; + private BigInteger bodyArg; + private final Set packages = new HashSet<>(); + + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/services/lisp/components/SltLispEnvironmentMacroExpandCache.java b/src/main/java/com/en_circle/slt/plugin/services/lisp/components/SltLispEnvironmentMacroExpandCache.java new file mode 100644 index 0000000..599affb --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/services/lisp/components/SltLispEnvironmentMacroExpandCache.java @@ -0,0 +1,90 @@ +package com.en_circle.slt.plugin.services.lisp.components; + +import com.en_circle.slt.plugin.lisp.lisp.LispString; +import com.en_circle.slt.plugin.lisp.lisp.LispUtils; +import com.en_circle.slt.plugin.lisp.psi.LispList; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.swank.requests.MacroexpandAll; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class SltLispEnvironmentMacroExpandCache { + + private final LoadingCache expandedMacros; + + public SltLispEnvironmentMacroExpandCache() { + CacheLoader loader = new CacheLoader<>() { + @Override + public MacroExpandEntry load(@NotNull SltLispEnvironmentMacroExpandCache.MacroExpandEntry key) throws Exception { + LispEnvironmentService.getInstance(key.project).sendToLisp( + MacroexpandAll.macroexpand(key.form, key.packageName, result -> { + key.evaluated = LispUtils.unescape(((LispString) result).getValue()); + })); + return key; + } + }; + expandedMacros = CacheBuilder.newBuilder() + .maximumSize(512) + .build(loader); + } + + + public String macroexpand(LispList form, String packageName) throws Exception { + MacroExpandEntry entry = new MacroExpandEntry(); + entry.form = form.getNode().getText(); + PsiFile file = form.getContainingFile(); + if (file != null) { + entry.virtualFile = file.getVirtualFile(); + entry.modification = file.getModificationStamp(); + entry.packageName = packageName; + entry.project = form.getProject(); + MacroExpandEntry cachedEntry = expandedMacros.get(entry); + if (cachedEntry.modification < entry.modification) { + expandedMacros.refresh(cachedEntry); + } + return expandedMacros.get(entry).evaluated; + } + return null; + } + + public void clear() { + expandedMacros.invalidateAll(); + } + + private static class MacroExpandEntry { + + public Project project; + private String form; + private String packageName; + private String evaluated; + private VirtualFile virtualFile; + private long modification; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MacroExpandEntry entry = (MacroExpandEntry) o; + + if (!Objects.equals(form, entry.form)) return false; + if (!Objects.equals(packageName, entry.packageName)) return false; + return Objects.equals(virtualFile, entry.virtualFile); + } + + @Override + public int hashCode() { + int result = form != null ? form.hashCode() : 0; + result = 31 * result + (packageName != null ? packageName.hashCode() : 0); + result = 31 * result + (virtualFile != null ? virtualFile.hashCode() : 0); + return result; + } + } +} diff --git a/src/main/java/com/en_circle/slt/plugin/SltSBCLSymbolCache.java b/src/main/java/com/en_circle/slt/plugin/services/lisp/components/SltLispEnvironmentSymbolCache.java similarity index 85% rename from src/main/java/com/en_circle/slt/plugin/SltSBCLSymbolCache.java rename to src/main/java/com/en_circle/slt/plugin/services/lisp/components/SltLispEnvironmentSymbolCache.java index 85ea7ca..493002f 100644 --- a/src/main/java/com/en_circle/slt/plugin/SltSBCLSymbolCache.java +++ b/src/main/java/com/en_circle/slt/plugin/services/lisp/components/SltLispEnvironmentSymbolCache.java @@ -1,12 +1,15 @@ -package com.en_circle.slt.plugin; +package com.en_circle.slt.plugin.services.lisp.components; +import com.en_circle.slt.plugin.SymbolState; import com.en_circle.slt.plugin.SymbolState.SymbolBinding; import com.en_circle.slt.plugin.lisp.lisp.*; -import com.en_circle.slt.plugin.swank.SwankServer; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService.LispEnvironmentState; import com.en_circle.slt.plugin.swank.components.SourceLocation; -import com.en_circle.slt.plugin.swank.requests.SwankEvalAndGrab; +import com.en_circle.slt.plugin.swank.requests.EvalAndGrab; import com.google.common.collect.Lists; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.util.FileContentUtilCore; @@ -17,26 +20,26 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -public class SltSBCLSymbolCache extends Thread { - - public static final SltSBCLSymbolCache INSTANCE = new SltSBCLSymbolCache(); - static { - INSTANCE.start(); - } +public class SltLispEnvironmentSymbolCache extends Thread { private final Map symbolInformation = Collections.synchronizedMap(new HashMap<>()); private final List symbolRefreshQueue = Collections.synchronizedList(new ArrayList<>()); - private SltSBCLSymbolCache() { + private final Project project; + private volatile boolean active = true; + + public SltLispEnvironmentSymbolCache(Project project) { + this.project = project; + setDaemon(true); setName("SBCL Symbol Cache Thread"); } @Override public void run() { - while (true) { + while (active) { try { - if (SwankServer.INSTANCE.isActive()) { + if (LispEnvironmentService.getInstance(project).getState() == LispEnvironmentState.READY) { while (symbolRefreshQueue.isEmpty()) { Thread.sleep(1000); } @@ -59,10 +62,16 @@ public void run() { } } + public void terminate() { + active = false; + } + public SymbolState refreshSymbolFromServer(String packageName, String symbolName, PsiElement element) { SymbolState state = getOrCreateBinding(packageName, symbolName); SymbolState undefinedSymbol = getOrCreateBinding(null, symbolName); - state.containerFiles.add(new WeakReference<>(element.getContainingFile().getVirtualFile())); + if (element != null) { + state.containerFiles.add(new WeakReference<>(element.getContainingFile().getVirtualFile())); + } SymbolBinding currentBinding = state.binding; if (currentBinding == SymbolBinding.NONE) { symbolRefreshQueue.add(state); @@ -130,11 +139,11 @@ private void refreshSymbolsBatched(List refreshStates) throws Excep refreshStates.stream().map(x -> x.name.toUpperCase() + " ").collect(Collectors.joining()) + ")"; request = StringUtils.replace(request, "\"", "\\\""); - SltSBCL.getInstance().sendToSbcl(SwankEvalAndGrab.eval( + LispEnvironmentService.getInstance(project).sendToLisp(EvalAndGrab.eval( String.format( "(slt-core:analyze-symbols (slt-core:read-fix-packages \"%s\"))", request), - SltSBCL.getInstance().getGlobalPackage(), true, (result, stdout, parsed) -> { + LispEnvironmentService.getInstance(project).getGlobalPackage(), true, (result, stdout, parsed) -> { Set toRefresh = new HashSet<>(); if (parsed.size() == 1 && parsed.get(0).getType() == LispElementType.CONTAINER) { int ix = 0; @@ -154,6 +163,14 @@ private void refreshSymbolsBatched(List refreshStates) throws Excep boolean changed = false; state.timestamp = System.currentTimeMillis(); switch (symValue) { + case ":CLASS": + changed |= state.binding != SymbolBinding.CLASS; + state.binding = SymbolBinding.CLASS; + break; + case ":METHOD": + changed |= state.binding != SymbolBinding.METHOD; + state.binding = SymbolBinding.METHOD; + break; case ":SPECIAL-FORM": changed |= state.binding != SymbolBinding.SPECIAL_FORM; state.binding = SymbolBinding.SPECIAL_FORM; @@ -227,4 +244,5 @@ public void clear() { symbolInformation.clear(); symbolRefreshQueue.clear(); } + } diff --git a/src/main/java/com/en_circle/slt/plugin/settings/SltSettingsConfigurable.java b/src/main/java/com/en_circle/slt/plugin/settings/SltSettingsConfigurable.java index ea48dfd..5941efe 100644 --- a/src/main/java/com/en_circle/slt/plugin/settings/SltSettingsConfigurable.java +++ b/src/main/java/com/en_circle/slt/plugin/settings/SltSettingsConfigurable.java @@ -1,12 +1,13 @@ package com.en_circle.slt.plugin.settings; import com.en_circle.slt.plugin.SltBundle; -import com.en_circle.slt.plugin.SltSBCL; import com.en_circle.slt.plugin.SltState; -import com.en_circle.slt.plugin.swank.SwankServer; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService.LispEnvironmentState; +import com.en_circle.slt.tools.ProjectUtils; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.NlsContexts.ConfigurableName; import org.jetbrains.annotations.Nullable; @@ -63,21 +64,18 @@ public void apply() throws ConfigurationException { settings.port = component.getPort(); settings.quicklispStartScript = component.getQuicklispStartScript(); - if (restartServer && SwankServer.INSTANCE.isActive()) { + Project project = ProjectUtils.getCurrentProject(); + + if (restartServer && LispEnvironmentService.getInstance(project).getState() != LispEnvironmentState.STOPPED) { if (Messages.YES == Messages.showYesNoDialog( SltBundle.message("slt.ui.settings.restart.prompt"), SltBundle.message("slt.ui.settings.restart.title"), SltBundle.message("slt.ui.settings.restart.yes"), SltBundle.message("slt.ui.settings.restart.no"), Messages.getQuestionIcon())) { - try { - SltSBCL.getInstance().stop(); - SltSBCL.getInstance().start(); - } catch (Exception e) { - log.warn(SltBundle.message("slt.error.sbclstart"), e); - Messages.showErrorDialog(ProjectManager.getInstance().getDefaultProject(), - e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); - } + LispEnvironmentService.getInstance(project).resetConfiguration(); + LispEnvironmentService.getInstance(project).stop(); + LispEnvironmentService.getInstance(project).start(); } } } diff --git a/src/main/java/com/en_circle/slt/plugin/swank/SlimeListener.java b/src/main/java/com/en_circle/slt/plugin/swank/SlimeListener.java index 6c4677b..8a57801 100644 --- a/src/main/java/com/en_circle/slt/plugin/swank/SlimeListener.java +++ b/src/main/java/com/en_circle/slt/plugin/swank/SlimeListener.java @@ -5,11 +5,10 @@ import com.en_circle.slt.plugin.SltCommonLispParserDefinition; import com.en_circle.slt.plugin.lisp.lisp.*; import com.en_circle.slt.plugin.lisp.psi.LispCoreProjectEnvironment; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; import com.en_circle.slt.plugin.swank.debug.SltDebugInfo; -import com.en_circle.slt.plugin.swank.requests.SltEval; -import com.en_circle.slt.plugin.swank.requests.SltFrameLocalsAndCatchTags; -import com.en_circle.slt.plugin.swank.requests.SltInvokeNthRestart; -import com.en_circle.slt.plugin.swank.requests.SwankEvalAndGrab; +import com.en_circle.slt.plugin.swank.requests.*; +import com.en_circle.slt.tools.ProjectUtils; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; @@ -23,7 +22,7 @@ public class SlimeListener implements SwankClient.SwankReply { - private static BigInteger rpcIdentifier = new BigInteger("0"); + private static BigInteger rpcIdentifier = BigInteger.ZERO; public static synchronized BigInteger nextRpc() { BigInteger next = rpcIdentifier.add(BigInteger.ONE); @@ -70,23 +69,10 @@ private void resolve(String data) { logger.logResponse(data); } - PsiFile source; - if (project == null) { - LispCoreProjectEnvironment projectEnvironment = new LispCoreProjectEnvironment(); - projectEnvironment.getEnvironment() - .registerParserDefinition(SltCommonLispLanguage.INSTANCE, new SltCommonLispParserDefinition()); - PsiFileFactory factory = PsiFileFactory.getInstance(projectEnvironment.getProject()); - source = factory.createFileFromText("swank-reply.cl", SltCommonLispFileType.INSTANCE, data); - } else { - PsiFileFactory factory = PsiFileFactory.getInstance(project); - source = factory.createFileFromText("swank-reply.cl", SltCommonLispFileType.INSTANCE, data); - } - List elements = parse(data); if (elements.size() == 1) { LispElement element = elements.get(0); - if (element instanceof LispContainer) { - LispContainer reply = (LispContainer) element; + if (element instanceof LispContainer reply) { if (isReturn(reply)) { processReturn(reply); } else if (isDebug(reply)) { @@ -95,6 +81,9 @@ private void resolve(String data) { processDebugReturn(reply); } else if (isDebugActivate(reply)) { processDebugActivate(reply); + } else if (isIndentation(reply)) { + Project project = ProjectUtils.getCurrentProject(); + LispEnvironmentService.getInstance(project).updateIndentation(reply.getItems().get(1)); } } } @@ -126,25 +115,41 @@ private void processReturn(LispContainer reply) { LispInteger replyId = (LispInteger) reply.getItems().get(2); try { SlimeRequest request = requests.get(replyId.getValue()); - if (request instanceof SltEval) { - SltEval eval = (SltEval) request; + if (request instanceof Eval eval) { eval.processReply((LispContainer) reply.getItems().get(1)); } - if (request instanceof SwankEvalAndGrab) { - SwankEvalAndGrab evalAndGrab = (SwankEvalAndGrab) request; + if (request instanceof EvalAndGrab evalAndGrab) { evalAndGrab.processReply((LispContainer) reply.getItems().get(1), this::parse); } - if (request instanceof SltInvokeNthRestart) { - SltInvokeNthRestart restart = (SltInvokeNthRestart) request; + if (request instanceof InvokeNthRestart restart) { restart.processReply((LispContainer) reply.getItems().get(1)); } - if (request instanceof SltFrameLocalsAndCatchTags) { - SltFrameLocalsAndCatchTags frames = (SltFrameLocalsAndCatchTags) request; + if (request instanceof FrameLocalsAndCatchTags frames) { frames.processReply((LispContainer) reply.getItems().get(1)); } + + if (request instanceof InspectFrameVar frameVar) { + frameVar.processReply((LispContainer) reply.getItems().get(1)); + } + + if (request instanceof InspectNth inspectNth) { + inspectNth.processReply((LispContainer) reply.getItems().get(1)); + } + + if (request instanceof InspectorAction action) { + action.processReply((LispContainer) reply.getItems().get(1)); + } + + if (request instanceof MacroexpandAll macroexpandAll) { + macroexpandAll.processReply((LispContainer) reply.getItems().get(1)); + } + + if (request instanceof SimpleCompletion simpleCompletion) { + simpleCompletion.processReply((LispContainer) reply.getItems().get(1)); + } } finally { requests.remove(replyId.getValue()); } @@ -191,6 +196,12 @@ private boolean isDebugActivate(LispContainer reply) { ":debug-activate".equals(((LispSymbol) reply.getItems().get(0)).getValue()); } + private boolean isIndentation(LispContainer reply) { + return reply.getItems().size() > 0 && + reply.getItems().get(0) instanceof LispSymbol && + ":indentation-update".equals(((LispSymbol) reply.getItems().get(0)).getValue()); + } + public interface RequestResponseLogger { void logRequest(String request); diff --git a/src/main/java/com/en_circle/slt/plugin/swank/SwankPacket.java b/src/main/java/com/en_circle/slt/plugin/swank/SwankPacket.java index e174940..111d4cd 100644 --- a/src/main/java/com/en_circle/slt/plugin/swank/SwankPacket.java +++ b/src/main/java/com/en_circle/slt/plugin/swank/SwankPacket.java @@ -40,7 +40,7 @@ public static SwankPacket rpcWriteString(String sexpression) { } public static SwankPacket sltEval(String sexpression, BigInteger continuation) { - return sltEval(sexpression, "CL-USER", continuation); + return sltEval(sexpression, ":CL-USER", continuation); } public static SwankPacket sltEval(String sexpression, String packageName, BigInteger continuation) { @@ -52,7 +52,7 @@ public static SwankPacket sltEval(String sexpression, String packageName, String packageName = StringUtils.replace(packageName, "\"", "\\\""); sexpression = StringUtils.replace(sexpression, "\\", "\\\\"); sexpression = StringUtils.replace(sexpression, "\"", "\\\""); - String formatted = String.format("(:emacs-rex (swank:slt-eval \"%s\") %s %s %s)", + String formatted = String.format("(:emacs-rex (swank:slt-eval \"%s\") \":%s\" %s %s)", sexpression, packageName, thread, continuation); return new SwankPacket(formatted); } @@ -62,13 +62,13 @@ public static SwankPacket evalInFrame(String sexpression, BigInteger frame, Stri packageName = StringUtils.replace(packageName, "\"", "\\\""); sexpression = StringUtils.replace(sexpression, "\\", "\\\\"); sexpression = StringUtils.replace(sexpression, "\"", "\\\""); - String formatted = String.format("(:emacs-rex (swank:eval-string-in-frame \"%s\" %s \"%s\") :%s %s %s)", + String formatted = String.format("(:emacs-rex (swank:eval-string-in-frame \"%s\" %s \"%s\") \":%s\" %s %s)", sexpression, frame, packageName, packageName, thread, continuation); return new SwankPacket(formatted); } public static SwankPacket evalRegion(String region, BigInteger continuation) { - return evalRegion(region, "CL-USER", "T", continuation); + return evalRegion(region, ":CL-USER", "T", continuation); } public static SwankPacket evalRegion(String region, String packageName, BigInteger continuation) { @@ -80,13 +80,13 @@ public static SwankPacket evalRegion(String region, String packageName, String t packageName = StringUtils.replace(packageName, "\"", "\\\""); region = StringUtils.replace(region, "\\", "\\\\"); region = StringUtils.replace(region, "\"", "\\\""); - String formatted = String.format("(:emacs-rex (swank:interactive-eval-region \"%s\") :%s %s %s)", + String formatted = String.format("(:emacs-rex (swank:interactive-eval-region \"%s\") \":%s\" %s %s)", region, packageName, thread, continuation); return new SwankPacket(formatted); } public static SwankPacket swankEvalAndGrab(String sexpression, BigInteger continuation) { - return swankEvalAndGrab(sexpression, "CL-USER", continuation); + return swankEvalAndGrab(sexpression, ":CL-USER", continuation); } public static SwankPacket swankEvalAndGrab(String sexpression, String packageName, BigInteger continuation) { @@ -98,13 +98,13 @@ public static SwankPacket swankEvalAndGrab(String sexpression, String packageNam packageName = StringUtils.replace(packageName, "\"", "\\\""); sexpression = StringUtils.replace(sexpression, "\\", "\\\\"); sexpression = StringUtils.replace(sexpression, "\"", "\\\""); - String formatted = String.format("(:emacs-rex (swank:eval-and-grab-output \"%s\") :%s %s %s)", + String formatted = String.format("(:emacs-rex (swank:eval-and-grab-output \"%s\") \":%s\" %s %s)", sexpression, packageName, thread, continuation); return new SwankPacket(formatted); } public static SwankPacket swankEvalRegion(String code, String filename, int bufferPosition, BigInteger continuation) { - return swankEvalRegion(code, filename, bufferPosition, "CL-USER", continuation); + return swankEvalRegion(code, filename, bufferPosition, ":CL-USER", continuation); } public static SwankPacket swankEvalRegion(String code, String filename, int bufferPosition, String packageName, BigInteger continuation) { @@ -119,7 +119,7 @@ public static SwankPacket swankEvalRegion(String code, String filename, int buff code = StringUtils.replace(code, "\"", "\\\""); filename = StringUtils.replace(filename, "\\", "\\\\"); filename = StringUtils.replace(filename, "\"", "\\\""); - String formatted = String.format("(:emacs-rex (swank:compile-string-region-slt \"%s\" \"%s\" %s \"%s\" :%s) :%s %s %s)", + String formatted = String.format("(:emacs-rex (swank:compile-string-region-slt \"%s\" \"%s\" %s \"%s\" :%s) \":%s\" %s %s)", code, filename, bufferPosition, filename, packageName, packageName, thread, continuation); return new SwankPacket(formatted); } @@ -130,7 +130,7 @@ public static SwankPacket invokeNthRestart(BigInteger option, BigInteger level, restartArg = StringUtils.replace(restartArg, "\"", "\\\""); restartArgs = StringUtils.replace(restartArgs, "\\", "\\\\"); restartArgs = StringUtils.replace(restartArgs, "\"", "\\\""); - String formatted = String.format("(:emacs-rex (swank:invoke-nth-restart-slt '%s '%s \"%s\" \"%s\") :CL-USER %s %s)", + String formatted = String.format("(:emacs-rex (swank:invoke-nth-restart-slt '%s '%s \"%s\" \"%s\") \":CL-USER\" %s %s)", level, option, restartArg, restartArgs, threadId, continuation); return new SwankPacket(formatted); } @@ -140,18 +140,54 @@ public static SwankPacket throwToToplevel(BigInteger threadId, BigInteger contin return new SwankPacket(formatted); } - public static SwankPacket frameLocals(BigInteger frame, BigInteger threadId, BigInteger continuation) { - return frameLocals(frame, threadId, "CL-USER", continuation); - } - public static SwankPacket frameLocals(BigInteger frame, BigInteger threadId, String packageName, BigInteger continuation) { packageName = StringUtils.replace(packageName, "\\", "\\\\"); packageName = StringUtils.replace(packageName, "\"", "\\\""); - String formatted = String.format("(:emacs-rex (swank:frame-locals-and-catch-tags %s) :%s %s %s)", + String formatted = String.format("(:emacs-rex (swank:frame-locals-and-catch-tags %s) \":%s\" %s %s)", frame, packageName, threadId, continuation); return new SwankPacket(formatted); } + public static SwankPacket inspectLocal(BigInteger ix, BigInteger frameId, BigInteger threadId, String packageName, BigInteger continuation) { + packageName = StringUtils.replace(packageName, "\\", "\\\\"); + packageName = StringUtils.replace(packageName, "\"", "\\\""); + String formatted = String.format("(:emacs-rex (swank:inspect-frame-var %s %s) \":%s\" %s %s)", + frameId, ix, packageName, threadId, continuation); + return new SwankPacket(formatted); + } + + public static SwankPacket frameInspectNth(BigInteger ix, BigInteger threadId, String packageName, BigInteger continuation) { + packageName = StringUtils.replace(packageName, "\\", "\\\\"); + packageName = StringUtils.replace(packageName, "\"", "\\\""); + String formatted = String.format("(:emacs-rex (swank:inspect-nth-part %s) \":%s\" %s %s)", + ix, packageName, threadId, continuation); + return new SwankPacket(formatted); + } + + public static SwankPacket inspectorBack(BigInteger threadId, String packageName, BigInteger continuation) { + packageName = StringUtils.replace(packageName, "\\", "\\\\"); + packageName = StringUtils.replace(packageName, "\"", "\\\""); + String formatted = String.format("(:emacs-rex (swank:inspector-pop) \":%s\" %s %s)", + packageName, threadId, continuation); + return new SwankPacket(formatted); + } + + public static SwankPacket inspectorForward(BigInteger threadId, String packageName, BigInteger continuation) { + packageName = StringUtils.replace(packageName, "\\", "\\\\"); + packageName = StringUtils.replace(packageName, "\"", "\\\""); + String formatted = String.format("(:emacs-rex (swank:inspector-next) \":%s\" %s %s)", + packageName, threadId, continuation); + return new SwankPacket(formatted); + } + + public static SwankPacket inspectorRefresh(BigInteger threadId, String packageName, BigInteger continuation) { + packageName = StringUtils.replace(packageName, "\\", "\\\\"); + packageName = StringUtils.replace(packageName, "\"", "\\\""); + String formatted = String.format("(:emacs-rex (swank:inspector-reinspect) \":%s\" %s %s)", + packageName, threadId, continuation); + return new SwankPacket(formatted); + } + public static SwankPacket loadFile(String file, BigInteger continuation) { return loadFile(file, "CL-USER", continuation); } @@ -165,11 +201,37 @@ public static SwankPacket loadFile(String file, String packageName, String threa packageName = StringUtils.replace(packageName, "\"", "\\\""); file = StringUtils.replace(file, "\\", "\\\\"); file = StringUtils.replace(file, "\"", "\\\""); - String formatted = String.format("(:emacs-rex (swank:load-file \"%s\") :%s %s %s)", + String formatted = String.format("(:emacs-rex (swank:load-file \"%s\") \":%s\" %s %s)", file, packageName, thread, continuation); return new SwankPacket(formatted); } + public static SwankPacket macroexpand(String form, String packageName, BigInteger continuation) { + return macroexpand(form, "T", packageName, continuation); + } + + public static SwankPacket macroexpand(String form, String threadId, String packageName, BigInteger continuation) { + form = StringUtils.replace(form, "\\", "\\\\"); + form = StringUtils.replace(form, "\"", "\\\""); + packageName = StringUtils.replace(packageName, "\\", "\\\\"); + packageName = StringUtils.replace(packageName, "\"", "\\\""); + String formatted = String.format("(:emacs-rex (swank:swank-macroexpand-all \"%s\") \":%s\" %s %s)", + form, packageName, threadId, continuation); + return new SwankPacket(formatted); + } + + public static SwankPacket simpleCompletion(String prefix, String packageName, String requestPackageName, BigInteger continuation) { + return simpleCompletion(prefix, packageName, "T", requestPackageName, continuation); + } + + public static SwankPacket simpleCompletion(String prefix, String packageName, String threadId, String requestPackageName, BigInteger continuation) { + requestPackageName = StringUtils.replace(requestPackageName, "\\", "\\\\"); + requestPackageName = StringUtils.replace(requestPackageName, "\"", "\\\""); + String formatted = String.format("(:emacs-rex (swank:simple-completions \"%s\" \"%s\") \":%s\" %s %s)", + prefix, packageName, requestPackageName, threadId, continuation); + return new SwankPacket(formatted); + } + private int length; private String expressionSource; diff --git a/src/main/java/com/en_circle/slt/plugin/swank/SwankServer.java b/src/main/java/com/en_circle/slt/plugin/swank/SwankServer.java deleted file mode 100644 index 0533e4f..0000000 --- a/src/main/java/com/en_circle/slt/plugin/swank/SwankServer.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.en_circle.slt.plugin.swank; - -import com.en_circle.slt.plugin.swank.SwankStreamController.WaitForOccurrence; -import com.en_circle.slt.templates.InitScriptTemplate; -import com.en_circle.slt.templates.SltScriptTemplate; -import com.intellij.openapi.application.ApplicationManager; -import org.apache.commons.io.FileUtils; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -public class SwankServer { - - public static SwankServer INSTANCE = new SwankServer(); - - private Process sbclProcess; - - public static void startSbcl(SwankServerConfiguration configuration) { - INSTANCE.start(configuration); - } - - public static void restart(SwankServerConfiguration configuration) { - INSTANCE.stopInstance(); - INSTANCE.start(configuration); - } - - public static void stop() { - INSTANCE.stopInstance(); - } - - public static Process getProcess() { - return INSTANCE.sbclProcess; - } - - private synchronized void stopInstance() { - if (sbclProcess != null) { - sbclProcess.destroy(); - sbclProcess = null; - } - } - - private synchronized void start(SwankServerConfiguration configuration) { - if (sbclProcess != null) - return; - - try { - File sltCore = File.createTempFile("slt", ".cl"); - sltCore.deleteOnExit(); - String sltScriptTemplate = new SltScriptTemplate().render(); - FileUtils.write(sltCore, sltScriptTemplate, StandardCharsets.UTF_8); - - File serverStartSetup = File.createTempFile("startServer", ".cl"); - serverStartSetup.deleteOnExit(); - String startScriptTemplate = new InitScriptTemplate(configuration, sltCore.getAbsolutePath()).render(); - FileUtils.write(serverStartSetup, startScriptTemplate, StandardCharsets.UTF_8); - - String[] commands = new String[]{ - configuration.getExecutablePath(), - "--load", - serverStartSetup.getName() - }; - ProcessBuilder pb = new ProcessBuilder(commands); - pb.directory(serverStartSetup.getParentFile()); - sbclProcess = pb.start(); - - SwankStreamController errorController = new SwankStreamController(sbclProcess.getErrorStream()); - SwankStreamController outputController = new SwankStreamController(sbclProcess.getInputStream()); - - WaitForOccurrence wait = new WaitForOccurrence("Swank started at port"); - errorController.addUpdateListener(wait); - - SwankServerListener listener = configuration.getListener(); - if (listener != null) { - if (ApplicationManager.getApplication() != null) { - errorController.addUpdateListener(data -> - ApplicationManager.getApplication().invokeLater(() -> listener.onOutputChanged(SwankServerOutput.STDERR, data))); - outputController.addUpdateListener(data -> - ApplicationManager.getApplication().invokeLater(() -> listener.onOutputChanged(SwankServerOutput.STDOUT, data))); - } else { - errorController.addUpdateListener(data -> listener.onOutputChanged(SwankServerOutput.STDERR, data)); - outputController.addUpdateListener(data -> listener.onOutputChanged(SwankServerOutput.STDOUT, data)); - } - } - - errorController.start(); - outputController.start(); - - if (!wait.awaitFor(sbclProcess)) { - throw new IOException("Failed to start sbcl!"); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public boolean isActive() { - return sbclProcess != null && sbclProcess.isAlive(); - } - - public interface SwankServerListener { - - void onOutputChanged(SwankServerOutput output, String newData); - - } - - public enum SwankServerOutput { - STDOUT, STDERR - } - -} diff --git a/src/main/java/com/en_circle/slt/plugin/swank/debug/SltInspectedObject.java b/src/main/java/com/en_circle/slt/plugin/swank/debug/SltInspectedObject.java new file mode 100644 index 0000000..2e018bd --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/swank/debug/SltInspectedObject.java @@ -0,0 +1,70 @@ +package com.en_circle.slt.plugin.swank.debug; + +import com.en_circle.slt.plugin.lisp.lisp.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +public class SltInspectedObject { + private static final Logger log = LoggerFactory.getLogger(SltInspectedObject.class); + + private final String title; + private final BigInteger id; + private final List elements = new ArrayList<>(); + + public SltInspectedObject(LispContainer container) throws Exception { + this.title = ((LispString) LispUtils.pvalue(container, new LispSymbol(":TITLE"))).getValue(); + this.id = ((LispInteger) LispUtils.pvalue(container, new LispSymbol(":ID"))).getValue(); + + LispContainer contents = (LispContainer) LispUtils.pvalue(container, new LispSymbol(":CONTENT")); + LispContainer content = (LispContainer) contents.getItems().get(0); + for (LispElement element : content.getItems()) { + if (element instanceof LispString) { + if (elements.isEmpty() || elements.get(elements.size() - 1).id != null) { + SltInspectionElement e = new SltInspectionElement(); + e.text = LispUtils.unescape(((LispString) element).getValue()); + elements.add(e); + } else { + elements.get(elements.size() - 1).text += LispUtils.unescape(((LispString) element).getValue()); + } + } else if (element instanceof LispContainer) { + LispContainer subelement = (LispContainer) element; + SltInspectionElement e = new SltInspectionElement(); + e.text = LispUtils.unescape(((LispString) LispUtils.pvalue(subelement, new LispSymbol(":VALUE"))).getValue()); + e.id = ((LispInteger) subelement.getItems().get(2)).getValue(); + elements.add(e); + } else { + log.warn("Unknown element in inspector!"); + } + } + } + + public String getTitle() { + return title; + } + + public BigInteger getId() { + return id; + } + + public List getElements() { + return elements; + } + + public static class SltInspectionElement { + + private String text; + private BigInteger id = null; + + public String getText() { + return text; + } + + public BigInteger getId() { + return id; + } + } +} diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/SltEval.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/Eval.java similarity index 86% rename from src/main/java/com/en_circle/slt/plugin/swank/requests/SltEval.java rename to src/main/java/com/en_circle/slt/plugin/swank/requests/Eval.java index eacc891..1eeb394 100644 --- a/src/main/java/com/en_circle/slt/plugin/swank/requests/SltEval.java +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/Eval.java @@ -8,21 +8,21 @@ import java.math.BigInteger; -public class SltEval extends SlimeRequest { +public class Eval extends SlimeRequest { public static SlimeRequest eval(String code, String module, Callback callback) { - return new SltEval(code, module, callback); + return new Eval(code, module, callback); } public static SlimeRequest eval(String code, Callback callback) { - return new SltEval(code, "cl-user", callback); + return new Eval(code, "cl-user", callback); } protected final Callback callback; protected final String module; protected final String code; - protected SltEval(String code, String module, Callback callback) { + protected Eval(String code, String module, Callback callback) { this.callback = callback; this.module = module; this.code = code; diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/SwankEvalAndGrab.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/EvalAndGrab.java similarity index 88% rename from src/main/java/com/en_circle/slt/plugin/swank/requests/SwankEvalAndGrab.java rename to src/main/java/com/en_circle/slt/plugin/swank/requests/EvalAndGrab.java index 10b80fd..19925f7 100644 --- a/src/main/java/com/en_circle/slt/plugin/swank/requests/SwankEvalAndGrab.java +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/EvalAndGrab.java @@ -12,14 +12,14 @@ import java.util.List; import java.util.function.Function; -public class SwankEvalAndGrab extends SlimeRequest { +public class EvalAndGrab extends SlimeRequest { public static SlimeRequest eval(String code, String module, boolean parse, Callback callback) { - return new SwankEvalAndGrab(code, module, parse, callback); + return new EvalAndGrab(code, module, parse, callback); } public static SlimeRequest eval(String code, boolean parse, Callback callback) { - return new SwankEvalAndGrab(code, "cl-user", parse, callback); + return new EvalAndGrab(code, "cl-user", parse, callback); } protected final Callback callback; @@ -27,7 +27,7 @@ public static SlimeRequest eval(String code, boolean parse, Callback callback) { protected final String code; protected final boolean parse; - protected SwankEvalAndGrab(String code, String module, boolean parse, Callback callback) { + protected EvalAndGrab(String code, String module, boolean parse, Callback callback) { this.callback = callback; this.module = module; this.code = code; diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/SwankEvalFromVirtualFile.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/EvalFromVirtualFile.java similarity index 80% rename from src/main/java/com/en_circle/slt/plugin/swank/requests/SwankEvalFromVirtualFile.java rename to src/main/java/com/en_circle/slt/plugin/swank/requests/EvalFromVirtualFile.java index a95bad1..60acaf0 100644 --- a/src/main/java/com/en_circle/slt/plugin/swank/requests/SwankEvalFromVirtualFile.java +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/EvalFromVirtualFile.java @@ -5,14 +5,14 @@ import java.math.BigInteger; -public class SwankEvalFromVirtualFile extends SlimeRequest { +public class EvalFromVirtualFile extends SlimeRequest { public static SlimeRequest eval(String code, String filename, int bufferPosition, int lineno, int charno, Callback callback) { return eval(code, filename, bufferPosition, lineno, charno, "cl-user", callback); } public static SlimeRequest eval(String code, String filename, int bufferPosition, int lineno, int charno, String module, Callback callback) { - return new SwankEvalFromVirtualFile(code, module, filename, bufferPosition, lineno, charno, callback); + return new EvalFromVirtualFile(code, module, filename, bufferPosition, lineno, charno, callback); } protected final Callback callback; @@ -23,7 +23,7 @@ public static SlimeRequest eval(String code, String filename, int bufferPosition protected final int lineno; protected final int charno; - protected SwankEvalFromVirtualFile(String code, String module, String filename, int bufferPosition, int lineno, int charno, Callback callback) { + protected EvalFromVirtualFile(String code, String module, String filename, int bufferPosition, int lineno, int charno, Callback callback) { this.callback = callback; this.module = module; this.code = code; diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/EvalStringInFrameEval.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/EvalStringInFrameEval.java index ebe4a67..bc9367e 100644 --- a/src/main/java/com/en_circle/slt/plugin/swank/requests/EvalStringInFrameEval.java +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/EvalStringInFrameEval.java @@ -5,7 +5,7 @@ import java.math.BigInteger; -public class EvalStringInFrameEval extends SltEval { +public class EvalStringInFrameEval extends Eval { public static SlimeRequest evalInFrame(String code, BigInteger frame, BigInteger thread, String module, Callback callback) { return new EvalStringInFrameEval(code, frame, thread, module, callback); diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/SltFrameLocalsAndCatchTags.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/FrameLocalsAndCatchTags.java similarity index 78% rename from src/main/java/com/en_circle/slt/plugin/swank/requests/SltFrameLocalsAndCatchTags.java rename to src/main/java/com/en_circle/slt/plugin/swank/requests/FrameLocalsAndCatchTags.java index 4bbc169..4898e52 100644 --- a/src/main/java/com/en_circle/slt/plugin/swank/requests/SltFrameLocalsAndCatchTags.java +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/FrameLocalsAndCatchTags.java @@ -8,14 +8,14 @@ import java.math.BigInteger; -public class SltFrameLocalsAndCatchTags extends SlimeRequest { +public class FrameLocalsAndCatchTags extends SlimeRequest { public static SlimeRequest getLocals(BigInteger frame, BigInteger threadId, String module, Callback callback) { - return new SltFrameLocalsAndCatchTags(frame, threadId, module, callback); + return new FrameLocalsAndCatchTags(frame, threadId, module, callback); } public static SlimeRequest getLocals(BigInteger frame, BigInteger threadId, Callback callback) { - return new SltFrameLocalsAndCatchTags(frame, threadId, "cl-user", callback); + return new FrameLocalsAndCatchTags(frame, threadId, "CL-USER", callback); } protected final Callback callback; @@ -23,15 +23,14 @@ public static SlimeRequest getLocals(BigInteger frame, BigInteger threadId, Call protected final BigInteger frame; protected final BigInteger threadId; - protected SltFrameLocalsAndCatchTags(BigInteger frame, BigInteger threadId, String module, Callback callback) { + protected FrameLocalsAndCatchTags(BigInteger frame, BigInteger threadId, String module, Callback callback) { this.callback = callback; this.module = module; this.frame = frame; this.threadId = threadId; } - public void processReply(LispContainer data - ) { + public void processReply(LispContainer data) { if (isOk(data)) { callback.onResult(data.getItems().get(1)); } diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/InspectFrameVar.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/InspectFrameVar.java new file mode 100644 index 0000000..99e32f6 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/InspectFrameVar.java @@ -0,0 +1,56 @@ +package com.en_circle.slt.plugin.swank.requests; + +import com.en_circle.slt.plugin.lisp.lisp.LispContainer; +import com.en_circle.slt.plugin.lisp.lisp.LispElement; +import com.en_circle.slt.plugin.lisp.lisp.LispSymbol; +import com.en_circle.slt.plugin.swank.SlimeRequest; +import com.en_circle.slt.plugin.swank.SwankPacket; + +import java.math.BigInteger; + +public class InspectFrameVar extends SlimeRequest { + + public static SlimeRequest inspectVariable(BigInteger ix, BigInteger frame, BigInteger threadId, String module, Callback callback) { + return new InspectFrameVar(ix, frame, threadId, module, callback); + } + + public static SlimeRequest inspectVariable(BigInteger ix, BigInteger frame, BigInteger threadId, Callback callback) { + return new InspectFrameVar(ix, frame, threadId, "CL-USER", callback); + } + + protected final Callback callback; + protected final String module; + protected final BigInteger ix; + protected final BigInteger frame; + protected final BigInteger threadId; + + protected InspectFrameVar(BigInteger ix, BigInteger frame, BigInteger threadId, String module, Callback callback) { + this.callback = callback; + this.module = module; + this.ix = ix; + this.frame = frame; + this.threadId = threadId; + } + + public void processReply(LispContainer data) { + if (isOk(data)) { + callback.onResult(data.getItems().get(1)); + } + } + + private boolean isOk(LispContainer data) { + return data.getItems().size() > 0 && + data.getItems().get(0) instanceof LispSymbol && + ":ok".equals(((LispSymbol) data.getItems().get(0)).getValue()); + } + + @Override + public SwankPacket createPacket(BigInteger requestId) { + return SwankPacket.inspectLocal(ix, frame, threadId, module, requestId); + } + + public interface Callback { + void onResult(LispElement result); + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/InspectNth.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/InspectNth.java new file mode 100644 index 0000000..cebee54 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/InspectNth.java @@ -0,0 +1,54 @@ +package com.en_circle.slt.plugin.swank.requests; + +import com.en_circle.slt.plugin.lisp.lisp.LispContainer; +import com.en_circle.slt.plugin.lisp.lisp.LispElement; +import com.en_circle.slt.plugin.lisp.lisp.LispSymbol; +import com.en_circle.slt.plugin.swank.SlimeRequest; +import com.en_circle.slt.plugin.swank.SwankPacket; + +import java.math.BigInteger; + +public class InspectNth extends SlimeRequest { + + public static SlimeRequest inspectVariable(BigInteger ix, BigInteger threadId, String module, Callback callback) { + return new InspectNth(ix, threadId, module, callback); + } + + public static SlimeRequest inspectVariable(BigInteger ix, BigInteger threadId, Callback callback) { + return new InspectNth(ix, threadId, "CL-USER", callback); + } + + protected final Callback callback; + protected final String module; + protected final BigInteger ix; + protected final BigInteger threadId; + + protected InspectNth(BigInteger ix, BigInteger threadId, String module, Callback callback) { + this.callback = callback; + this.module = module; + this.ix = ix; + this.threadId = threadId; + } + + public void processReply(LispContainer data) { + if (isOk(data)) { + callback.onResult(data.getItems().get(1)); + } + } + + private boolean isOk(LispContainer data) { + return data.getItems().size() > 0 && + data.getItems().get(0) instanceof LispSymbol && + ":ok".equals(((LispSymbol) data.getItems().get(0)).getValue()); + } + + @Override + public SwankPacket createPacket(BigInteger requestId) { + return SwankPacket.frameInspectNth(ix, threadId, module, requestId); + } + + public interface Callback { + void onResult(LispElement result); + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/InspectorAction.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/InspectorAction.java new file mode 100644 index 0000000..4d56a68 --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/InspectorAction.java @@ -0,0 +1,68 @@ +package com.en_circle.slt.plugin.swank.requests; + +import com.en_circle.slt.plugin.lisp.lisp.LispContainer; +import com.en_circle.slt.plugin.lisp.lisp.LispElement; +import com.en_circle.slt.plugin.lisp.lisp.LispSymbol; +import com.en_circle.slt.plugin.swank.SlimeRequest; +import com.en_circle.slt.plugin.swank.SwankPacket; + +import java.math.BigInteger; + +public class InspectorAction extends SlimeRequest { + + public static SlimeRequest action(ActionType actionType, BigInteger threadId, String module, Callback callback) { + return new InspectorAction(actionType, threadId, module, callback); + } + + public static SlimeRequest action(ActionType actionType, BigInteger threadId, Callback callback) { + return new InspectorAction(actionType, threadId, "CL-USER", callback); + } + + protected final Callback callback; + protected final String module; + protected final ActionType actionType; + protected final BigInteger threadId; + + protected InspectorAction(ActionType actionType, BigInteger threadId, String module, Callback callback) { + this.callback = callback; + this.module = module; + this.actionType = actionType; + this.threadId = threadId; + } + + public void processReply(LispContainer data) { + if (isOk(data)) { + callback.onResult(data.getItems().get(1)); + } + } + + private boolean isOk(LispContainer data) { + return data.getItems().size() > 0 && + data.getItems().get(0) instanceof LispSymbol && + ":ok".equals(((LispSymbol) data.getItems().get(0)).getValue()); + } + + @Override + public SwankPacket createPacket(BigInteger requestId) { + switch (actionType) { + + case GO_BACK: + return SwankPacket.inspectorBack(threadId, module, requestId); + case GO_FORWARD: + return SwankPacket.inspectorForward(threadId, module, requestId); + case REFRESH: + return SwankPacket.inspectorRefresh(threadId, module, requestId); + } + throw new IllegalStateException(); + } + + public interface Callback { + void onResult(LispElement result); + } + + public enum ActionType { + + GO_BACK, GO_FORWARD, REFRESH + + } +} diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/SltInvokeNthRestart.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/InvokeNthRestart.java similarity index 83% rename from src/main/java/com/en_circle/slt/plugin/swank/requests/SltInvokeNthRestart.java rename to src/main/java/com/en_circle/slt/plugin/swank/requests/InvokeNthRestart.java index 81d3496..348b511 100644 --- a/src/main/java/com/en_circle/slt/plugin/swank/requests/SltInvokeNthRestart.java +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/InvokeNthRestart.java @@ -7,10 +7,10 @@ import java.math.BigInteger; -public class SltInvokeNthRestart extends SlimeRequest { +public class InvokeNthRestart extends SlimeRequest { public static SlimeRequest nthRestart(BigInteger threadId, BigInteger option, BigInteger nestLevel, String arg, String args, Callback callback) { - return new SltInvokeNthRestart(threadId, option, nestLevel, arg, args, callback); + return new InvokeNthRestart(threadId, option, nestLevel, arg, args, callback); } protected final Callback callback; @@ -20,7 +20,7 @@ public static SlimeRequest nthRestart(BigInteger threadId, BigInteger option, Bi protected final String arg; protected final String args; - protected SltInvokeNthRestart(BigInteger threadId, BigInteger option, BigInteger nestLevel, String arg, String args, Callback callback) { + protected InvokeNthRestart(BigInteger threadId, BigInteger option, BigInteger nestLevel, String arg, String args, Callback callback) { this.threadId = threadId; this.callback = callback; this.restart = option; diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/MacroexpandAll.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/MacroexpandAll.java new file mode 100644 index 0000000..489486f --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/MacroexpandAll.java @@ -0,0 +1,48 @@ +package com.en_circle.slt.plugin.swank.requests; + +import com.en_circle.slt.plugin.lisp.lisp.LispContainer; +import com.en_circle.slt.plugin.lisp.lisp.LispElement; +import com.en_circle.slt.plugin.lisp.lisp.LispSymbol; +import com.en_circle.slt.plugin.swank.SlimeRequest; +import com.en_circle.slt.plugin.swank.SwankPacket; + +import java.math.BigInteger; + +public class MacroexpandAll extends SlimeRequest { + + public static SlimeRequest macroexpand(String form, String module, Callback callback) { + return new MacroexpandAll(form, module, callback); + } + + private final Callback callback; + private final String form; + private final String module; + + public MacroexpandAll(String form, String module, Callback callback) { + this.form = form; + this.module = module; + this.callback = callback; + } + + public void processReply(LispContainer data) { + if (isOk(data)) { + callback.onResult(data.getItems().get(1)); + } + } + + private boolean isOk(LispContainer data) { + return data.getItems().size() > 0 && + data.getItems().get(0) instanceof LispSymbol && + ":ok".equals(((LispSymbol) data.getItems().get(0)).getValue()); + } + + @Override + public SwankPacket createPacket(BigInteger requestId) { + return SwankPacket.macroexpand(form, module, requestId); + } + + public interface Callback { + void onResult(LispElement result); + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/swank/requests/SimpleCompletion.java b/src/main/java/com/en_circle/slt/plugin/swank/requests/SimpleCompletion.java new file mode 100644 index 0000000..a0ead5f --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/swank/requests/SimpleCompletion.java @@ -0,0 +1,50 @@ +package com.en_circle.slt.plugin.swank.requests; + +import com.en_circle.slt.plugin.lisp.lisp.LispContainer; +import com.en_circle.slt.plugin.lisp.lisp.LispElement; +import com.en_circle.slt.plugin.lisp.lisp.LispSymbol; +import com.en_circle.slt.plugin.swank.SlimeRequest; +import com.en_circle.slt.plugin.swank.SwankPacket; + +import java.math.BigInteger; + +public class SimpleCompletion extends SlimeRequest { + + public static SlimeRequest simpleCompletion(String prefix, String packageName, Callback callback) { + return new SimpleCompletion(prefix, packageName, "CL-USER", callback); + } + + protected final Callback callback; + protected final String module; + protected final String prefix; + protected final String packageName; + + protected SimpleCompletion(String prefix, String packageName, String module, Callback callback) { + this.callback = callback; + this.module = module; + this.prefix = prefix; + this.packageName = packageName; + } + + public void processReply(LispContainer data) { + if (isOk(data)) { + callback.onResult(data.getItems().get(1)); + } + } + + private boolean isOk(LispContainer data) { + return data.getItems().size() > 0 && + data.getItems().get(0) instanceof LispSymbol && + ":ok".equals(((LispSymbol) data.getItems().get(0)).getValue()); + } + + @Override + public SwankPacket createPacket(BigInteger requestId) { + return SwankPacket.simpleCompletion(prefix, packageName, module, requestId); + } + + public interface Callback { + void onResult(LispElement result); + } + +} diff --git a/src/main/java/com/en_circle/slt/plugin/ui/PackageSelectorComponent.java b/src/main/java/com/en_circle/slt/plugin/ui/PackageSelectorComponent.java index 1996a02..22b985d 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/PackageSelectorComponent.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/PackageSelectorComponent.java @@ -1,15 +1,17 @@ package com.en_circle.slt.plugin.ui; import com.en_circle.slt.plugin.SltBundle; -import com.en_circle.slt.plugin.SltSBCL; import com.en_circle.slt.plugin.lisp.lisp.LispContainer; import com.en_circle.slt.plugin.lisp.lisp.LispElement; import com.en_circle.slt.plugin.lisp.lisp.LispString; -import com.en_circle.slt.plugin.swank.SwankServer; -import com.en_circle.slt.plugin.swank.requests.SwankEvalAndGrab; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService.LispEnvironmentState; +import com.en_circle.slt.plugin.swank.requests.EvalAndGrab; +import com.en_circle.slt.tools.ProjectUtils; import com.intellij.icons.AllIcons.Actions; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.CustomComponentAction; +import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.Messages; @@ -31,8 +33,11 @@ public class PackageSelectorComponent { private final Supplier currentPackage; private final ComboBox packageComboBox; private PackageChangedListener listener; + private Project project; public PackageSelectorComponent(String id, Supplier currentPackage) { + this.project = ProjectUtils.getCurrentProject(); + this.currentPackage = currentPackage; this.packageComboBox = new ComboBox<>(); this.packageComboBox.addActionListener(e -> { @@ -55,7 +60,7 @@ public ActionToolbar getActionToolbar() { public void refresh() { try { - SltSBCL.getInstance().sendToSbcl(SwankEvalAndGrab.eval("(slt-core:list-package-names)", true, (result, stdout, parsed) -> { + LispEnvironmentService.getInstance(project).sendToLisp(EvalAndGrab.eval("(slt-core:list-package-names)", true, (result, stdout, parsed) -> { resolvePackages(parsed); }), false); } catch (Exception e) { @@ -102,6 +107,11 @@ public void actionPerformed(@NotNull AnActionEvent e) { } + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.EDT; + } + @Override public @NotNull JComponent createCustomComponent(@NotNull Presentation presentation, @NotNull String place) { JPanel panel = new JPanel(new BorderLayout()); @@ -114,7 +124,7 @@ public void actionPerformed(@NotNull AnActionEvent e) { public void update(@NotNull AnActionEvent e) { super.update(e); - e.getPresentation().setEnabled(SwankServer.INSTANCE.isActive()); + e.getPresentation().setEnabled(LispEnvironmentService.getInstance(project).getState() == LispEnvironmentState.READY); } } @@ -125,6 +135,11 @@ private RefreshPackagesAction() { super(SltBundle.message("slt.ui.packageselector.refresh"), "", Actions.Refresh); } + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.EDT; + } + @Override public void actionPerformed(@NotNull AnActionEvent e) { refresh(); @@ -134,7 +149,7 @@ public void actionPerformed(@NotNull AnActionEvent e) { public void update(@NotNull AnActionEvent e) { super.update(e); - e.getPresentation().setEnabled(SwankServer.INSTANCE.isActive()); + e.getPresentation().setEnabled(LispEnvironmentService.getInstance(project).getState() == LispEnvironmentState.READY); } } diff --git a/src/main/java/com/en_circle/slt/plugin/ui/SltComponent.java b/src/main/java/com/en_circle/slt/plugin/ui/SltComponent.java index 6af2c69..367e624 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/SltComponent.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/SltComponent.java @@ -1,6 +1,6 @@ package com.en_circle.slt.plugin.ui; -import com.en_circle.slt.plugin.swank.SwankServer.SwankServerOutput; +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltOutput; import com.intellij.ui.tabs.TabInfo; public interface SltComponent { @@ -11,7 +11,7 @@ public interface SltComponent { void onPreStart(); void onPostStart(); - void handleOutput(SwankServerOutput output, String data); + void handleOutput(SltOutput output, String data); void onPreStop(); void onPostStop(); diff --git a/src/main/java/com/en_circle/slt/plugin/ui/SltCoreWindow.java b/src/main/java/com/en_circle/slt/plugin/ui/SltCoreWindow.java index 9ca06c5..cb22c39 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/SltCoreWindow.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/SltCoreWindow.java @@ -2,10 +2,10 @@ import com.en_circle.slt.plugin.SltBundle; import com.en_circle.slt.plugin.SltCommonLispFileType; -import com.en_circle.slt.plugin.SltSBCL; -import com.en_circle.slt.plugin.SltSBCL.SBCLServerListener; -import com.en_circle.slt.plugin.swank.SwankServer; -import com.en_circle.slt.plugin.swank.SwankServer.SwankServerOutput; +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltOutput; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService.LispEnvironmentListener; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService.LispEnvironmentState; import com.en_circle.slt.plugin.ui.console.SltConsole; import com.en_circle.slt.plugin.ui.console.SltREPL; import com.intellij.icons.AllIcons; @@ -13,10 +13,8 @@ import com.intellij.icons.AllIcons.General; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.psi.PsiFile; @@ -31,8 +29,7 @@ import java.util.Collections; import java.util.List; -public class SltCoreWindow implements SBCLServerListener { - private static final Logger log = Logger.getInstance(SltCoreWindow.class); +public class SltCoreWindow implements LispEnvironmentListener { private final Project project; private final JTextField process; @@ -43,16 +40,14 @@ public class SltCoreWindow implements SBCLServerListener { public SltCoreWindow(ToolWindow toolWindow) { this.project = toolWindow.getProject(); - - SltSBCL.getInstance().addServerListener(this); - SltSBCL.getInstance().setProject(toolWindow.getProject()); + LispEnvironmentService.getInstance(project).addServerListener(this); content = new JPanel(new BorderLayout()); - components.add(new SltOutputHandlerComponent(this, SwankServerOutput.STDOUT)); - components.add(new SltOutputHandlerComponent(this, SwankServerOutput.STDERR)); + components.add(new SltOutputHandlerComponent(this, SltOutput.STDOUT)); + components.add(new SltOutputHandlerComponent(this, SltOutput.STDERR)); SltGeneralLog generalLog = new SltGeneralLog(); components.add(generalLog); - SltSBCL.getInstance().setRequestResponseLogger(generalLog); + LispEnvironmentService.getInstance(project).setRequestResponseLogger(generalLog); createSbclControls(); @@ -91,12 +86,7 @@ private void createSbclControls() { } public void start() { - try { - SltSBCL.getInstance().start(); - } catch (Exception e) { - log.warn(SltBundle.message("slt.error.sbclstart"), e); - Messages.showErrorDialog(project, e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); - } + LispEnvironmentService.getInstance(project).start(); PsiManager psiManager = PsiManager.getInstance(project); List toReparse = new ArrayList<>(); @@ -110,12 +100,7 @@ public void start() { } public void stop() { - try { - SltSBCL.getInstance().stop(); - } catch (Exception e) { - log.warn(SltBundle.message("slt.error.sbclstop"), e); - Messages.showErrorDialog(project, e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.stop")); - } + LispEnvironmentService.getInstance(project).stop(); } public JComponent getContent() { @@ -156,8 +141,7 @@ public void onPostStart() { component.onPostStart(); } - Process p = SwankServer.getProcess(); - process.setText("" + p.pid()); + process.setText(LispEnvironmentService.getInstance(project).getEnvironment().getInformation().getPid()); } @Override @@ -176,7 +160,7 @@ public void onPostStop() { } @Override - public void onOutputChanged(SwankServerOutput output, String newData) { + public void onOutputChanged(SltOutput output, String newData) { for (SltComponent component : components) { component.handleOutput(output, newData); } @@ -189,7 +173,7 @@ public Project getProject() { private class StartSbclAction extends AnAction { private StartSbclAction() { - super(SltBundle.message("slt.ui.process.startsbcl"), "", AllIcons.RunConfigurations.TestState.Run); + super(SltBundle.message("slt.ui.process.startinstance"), "", AllIcons.RunConfigurations.TestState.Run); } @Override @@ -201,14 +185,14 @@ public void actionPerformed(@NotNull AnActionEvent e) { public void update(@NotNull AnActionEvent e) { super.update(e); - e.getPresentation().setEnabled(!SwankServer.INSTANCE.isActive()); + e.getPresentation().setEnabled(LispEnvironmentService.getInstance(project).getState() == LispEnvironmentState.STOPPED); } } private class StopSbclAction extends AnAction { private StopSbclAction() { - super(SltBundle.message("slt.ui.process.stopsbcl"), "", AllIcons.Actions.Suspend); + super(SltBundle.message("slt.ui.process.stopinstance"), "", AllIcons.Actions.Suspend); } @Override @@ -220,14 +204,14 @@ public void actionPerformed(@NotNull AnActionEvent e) { public void update(@NotNull AnActionEvent e) { super.update(e); - e.getPresentation().setEnabled(SwankServer.INSTANCE.isActive()); + e.getPresentation().setEnabled(LispEnvironmentService.getInstance(project).getState() != LispEnvironmentState.STOPPED); } } private class ConsoleWindowAction extends AnAction { private ConsoleWindowAction() { - super(SltBundle.message("slt.ui.process.openrepl"), "", General.Add); + super(SltBundle.message("slt.ui.process.openrepl.sbcl"), "", General.Add); } @Override @@ -239,7 +223,7 @@ public void actionPerformed(@NotNull AnActionEvent e) { public void update(@NotNull AnActionEvent e) { super.update(e); - e.getPresentation().setEnabled(SwankServer.INSTANCE.isActive()); + e.getPresentation().setEnabled(LispEnvironmentService.getInstance(project).getState() == LispEnvironmentState.READY); } } diff --git a/src/main/java/com/en_circle/slt/plugin/ui/SltGeneralLog.java b/src/main/java/com/en_circle/slt/plugin/ui/SltGeneralLog.java index 6413558..741411c 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/SltGeneralLog.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/SltGeneralLog.java @@ -1,8 +1,8 @@ package com.en_circle.slt.plugin.ui; import com.en_circle.slt.plugin.SltBundle; +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltOutput; import com.en_circle.slt.plugin.swank.SlimeListener.RequestResponseLogger; -import com.en_circle.slt.plugin.swank.SwankServer.SwankServerOutput; import com.en_circle.slt.tools.BufferedString; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.*; @@ -110,7 +110,7 @@ public void onPostStart() { } @Override - public void handleOutput(SwankServerOutput output, String data) { + public void handleOutput(SltOutput output, String data) { } diff --git a/src/main/java/com/en_circle/slt/plugin/ui/SltOutputHandlerComponent.java b/src/main/java/com/en_circle/slt/plugin/ui/SltOutputHandlerComponent.java index 8db8576..4ec8b73 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/SltOutputHandlerComponent.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/SltOutputHandlerComponent.java @@ -1,7 +1,7 @@ package com.en_circle.slt.plugin.ui; import com.en_circle.slt.plugin.SltBundle; -import com.en_circle.slt.plugin.swank.SwankServer.SwankServerOutput; +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltOutput; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.*; import com.intellij.ui.components.JBScrollPane; @@ -15,17 +15,17 @@ public class SltOutputHandlerComponent implements SltComponent { - private final SwankServerOutput output; + private final SltOutput output; private final JPanel dataContainer; private JTextArea area; private TabInfo tabInfo; - public SltOutputHandlerComponent(SltCoreWindow coreWindow, SwankServerOutput output) { + public SltOutputHandlerComponent(SltCoreWindow coreWindow, SltOutput output) { this.output = output; this.dataContainer = new JPanel(new BorderLayout()); } - public SwankServerOutput getOutput() { + public SltOutput getOutput() { return output; } @@ -106,7 +106,7 @@ public void onPostStart() { } @Override - public void handleOutput(SwankServerOutput output, String data) { + public void handleOutput(SltOutput output, String data) { if (output == this.output) { area.setText(area.getText() + data); DefaultCaret caret = (DefaultCaret) area.getCaret(); @@ -127,7 +127,7 @@ public void onPostStop() { @Override public String getTitle() { - return getOutput() == SwankServerOutput.STDERR ? SltBundle.message("slt.ui.process.log.error.title") : + return getOutput() == SltOutput.STDERR ? SltBundle.message("slt.ui.process.log.error.title") : SltBundle.message("slt.ui.process.log.output.title"); } diff --git a/src/main/java/com/en_circle/slt/plugin/ui/console/SltConsole.java b/src/main/java/com/en_circle/slt/plugin/ui/console/SltConsole.java index 3b35daa..7e2e206 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/console/SltConsole.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/console/SltConsole.java @@ -2,10 +2,10 @@ import com.en_circle.slt.plugin.SltBundle; import com.en_circle.slt.plugin.SltCommonLispLanguage; -import com.en_circle.slt.plugin.SltSBCL; -import com.en_circle.slt.plugin.swank.SwankServer; -import com.en_circle.slt.plugin.swank.SwankServer.SwankServerOutput; -import com.en_circle.slt.plugin.swank.requests.SltEval; +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltOutput; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService.LispEnvironmentState; +import com.en_circle.slt.plugin.swank.requests.Eval; import com.en_circle.slt.plugin.ui.SltComponent; import com.intellij.execution.console.ConsoleExecuteAction; import com.intellij.execution.console.ConsoleHistoryController; @@ -15,6 +15,7 @@ import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Disposer; import com.intellij.ui.tabs.TabInfo; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -50,6 +51,7 @@ protected void setPackage(String packageName) { public TabInfo create() { languageConsole = new LanguageConsoleBuilder() .build(project, SltCommonLispLanguage.INSTANCE); + Disposer.register(project, languageConsole); languageConsole.setPrompt(currentModule + "> "); ConsoleExecuteAction action = new ConsoleExecuteAction(languageConsole, new SltConsoleExecuteActionHandler(languageConsole) { @@ -59,7 +61,7 @@ protected void execute(String code) { eval(code); } - }, languageConsoleView -> SwankServer.INSTANCE.isActive()); + }, languageConsoleView -> LispEnvironmentService.getInstance(project).getState() == LispEnvironmentState.READY); action.registerCustomShortcutSet(action.getShortcutSet(), languageConsole.getConsoleEditor().getComponent()); new ConsoleHistoryController(new MyConsoleRootType("cl"), null, languageConsole).install(); @@ -73,7 +75,7 @@ protected void execute(String code) { protected void eval(String data) { try { if (StringUtils.isNotBlank(data)) { - SltSBCL.getInstance().sendToSbcl(SltEval.eval(data, currentModule, + LispEnvironmentService.getInstance(project).sendToLisp(Eval.eval(data, currentModule, result -> languageConsole.print(result + "\n", ConsoleViewContentType.NORMAL_OUTPUT))); } } catch (Exception e) { @@ -98,7 +100,7 @@ public void onPostStart() { } @Override - public void handleOutput(SwankServerOutput output, String data) { + public void handleOutput(SltOutput output, String data) { } diff --git a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebugger.java b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebugger.java index 108d082..a111edb 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebugger.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebugger.java @@ -1,9 +1,10 @@ package com.en_circle.slt.plugin.ui.debug; import com.en_circle.slt.plugin.swank.debug.SltDebugInfo; +import com.intellij.openapi.Disposable; import com.intellij.ui.tabs.TabInfo; -public interface SltDebugger { +public interface SltDebugger extends Disposable { TabInfo getTab(); diff --git a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebuggerImpl.java b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebuggerImpl.java index 88a15d1..786bea4 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebuggerImpl.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebuggerImpl.java @@ -1,16 +1,17 @@ package com.en_circle.slt.plugin.ui.debug; import com.en_circle.slt.plugin.SltBundle; -import com.en_circle.slt.plugin.SltSBCL; import com.en_circle.slt.plugin.SltUIConstants; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; import com.en_circle.slt.plugin.swank.debug.SltDebugAction; import com.en_circle.slt.plugin.swank.debug.SltDebugArgument; import com.en_circle.slt.plugin.swank.debug.SltDebugInfo; import com.en_circle.slt.plugin.swank.debug.SltDebugStackTraceElement; -import com.en_circle.slt.plugin.swank.requests.SltFrameLocalsAndCatchTags; -import com.en_circle.slt.plugin.swank.requests.SltInvokeNthRestart; +import com.en_circle.slt.plugin.swank.requests.FrameLocalsAndCatchTags; +import com.en_circle.slt.plugin.swank.requests.InvokeNthRestart; import com.en_circle.slt.plugin.swank.requests.ThrowToToplevel; import com.intellij.icons.AllIcons.Actions; +import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -47,7 +48,7 @@ import java.util.List; import java.util.Map; -public class SltDebuggerImpl implements SltDebugger { +public class SltDebuggerImpl implements SltDebugger, Disposable { private static final Logger log = LoggerFactory.getLogger(SltDebuggerImpl.class); private final JComponent content; @@ -56,6 +57,7 @@ public class SltDebuggerImpl implements SltDebugger { private BigInteger lastDebugId; private JPanel singleFrameComponent; private final List stackframes = new ArrayList<>(); + private BigInteger debugContext; public SltDebuggerImpl(SltDebuggers parent) { this.parent = parent; @@ -189,7 +191,7 @@ private void actionAccepted(SltDebugAction action, SltDebugInfo debugInfo) { int ix = debugInfo.getActions().indexOf(action); if (action.getArguments().isEmpty()) { try { - SltSBCL.getInstance().sendToSbcl(SltInvokeNthRestart.nthRestart(debugInfo.getThreadId(), + LispEnvironmentService.getInstance(parent.getProject()).sendToLisp(InvokeNthRestart.nthRestart(debugInfo.getThreadId(), BigInteger.valueOf(ix), debugInfo.getDebugLevel(), "NIL", "NIL", () -> {})); } catch (Exception e) { log.warn(SltBundle.message("slt.error.sbclstart"), e); @@ -213,7 +215,7 @@ private void actionAccepted(SltDebugAction action, SltDebugInfo debugInfo) { } String args = arguments.size() == 0 ? "NIL" : "(" + String.join(" ", arguments) + ")"; try { - SltSBCL.getInstance().sendToSbcl(SltInvokeNthRestart.nthRestart(debugInfo.getThreadId(), + LispEnvironmentService.getInstance(parent.getProject()).sendToLisp(InvokeNthRestart.nthRestart(debugInfo.getThreadId(), BigInteger.valueOf(ix), debugInfo.getDebugLevel(), args, rest, () -> {})); } catch (Exception e) { log.warn(SltBundle.message("slt.error.sbclstart"), e); @@ -223,10 +225,6 @@ private void actionAccepted(SltDebugAction action, SltDebugInfo debugInfo) { } - private void closeGui() { - parent.removeDebugger(this, lastDebugId); - } - private void stackframeClicked(SltDebugStackTraceElement element, SltDebugInfo debugInfo) { int ix = debugInfo.getStacktrace().indexOf(element); for (int ix2=0; ix2 { - ApplicationManager.getApplication().invokeLater(() -> { + ApplicationManager.getApplication().runWriteAction(() -> { SltFrameInfo frameInfo = new SltFrameInfo(parent.getProject(), debugInfo.getThreadId(), BigInteger.valueOf(ix), element.getFramePackage()); singleFrameComponent.removeAll(); @@ -270,7 +268,7 @@ private void stackframeClicked(SltDebugStackTraceElement element, SltDebugInfo d private void close() { try { - SltSBCL.getInstance().sendToSbcl(new ThrowToToplevel(lastDebugId)); + LispEnvironmentService.getInstance(parent.getProject()).sendToLisp(new ThrowToToplevel(lastDebugId)); } catch (Exception e) { log.warn(SltBundle.message("slt.error.sbclstart"), e); Messages.showErrorDialog(parent.getProject(), e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); @@ -282,4 +280,8 @@ public void activate() { this.tabInfo.fireAlert(); } + @Override + public void dispose() { + + } } diff --git a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebuggers.java b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebuggers.java index bf201d8..bb2e642 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebuggers.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltDebuggers.java @@ -1,9 +1,9 @@ package com.en_circle.slt.plugin.ui.debug; -import com.en_circle.slt.plugin.SltSBCL; -import com.en_circle.slt.plugin.SltSBCL.SBCLServerListener; +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltOutput; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService.LispEnvironmentListener; import com.en_circle.slt.plugin.swank.SlimeListener.DebugInterface; -import com.en_circle.slt.plugin.swank.SwankServer.SwankServerOutput; import com.en_circle.slt.plugin.swank.debug.SltDebugInfo; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; @@ -17,7 +17,7 @@ import java.util.HashMap; import java.util.Map; -public class SltDebuggers implements DebugInterface, SBCLServerListener { +public class SltDebuggers implements DebugInterface, LispEnvironmentListener { private final ToolWindow toolWindow; private final JPanel content; @@ -32,8 +32,8 @@ public SltDebuggers(ToolWindow toolWindow) { this.tabs = new JBTabsImpl(toolWindow.getProject()); this.content.add(this.tabs.getComponent()); - SltSBCL.getInstance().addServerListener(this); - SltSBCL.getInstance().setDebugInterface(this); + LispEnvironmentService.getInstance(toolWindow.getProject()).addServerListener(this); + LispEnvironmentService.getInstance(toolWindow.getProject()).setDebugInterface(this); } public JPanel getContent() { @@ -106,7 +106,7 @@ public void onPostStop() { } @Override - public void onOutputChanged(SwankServerOutput output, String newData) { + public void onOutputChanged(SltOutput output, String newData) { } diff --git a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltFrameConsole.java b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltFrameConsole.java index ebe20ad..a842578 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltFrameConsole.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltFrameConsole.java @@ -1,7 +1,7 @@ package com.en_circle.slt.plugin.ui.debug; import com.en_circle.slt.plugin.SltBundle; -import com.en_circle.slt.plugin.SltSBCL; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; import com.en_circle.slt.plugin.swank.requests.EvalStringInFrameEval; import com.en_circle.slt.plugin.ui.console.SltConsole; import com.intellij.execution.ui.ConsoleViewContentType; @@ -32,7 +32,7 @@ public SltFrameConsole(Project project, BigInteger threadId, BigInteger frame, R protected void eval(String data) { try { if (StringUtils.isNotBlank(data)) { - SltSBCL.getInstance().sendToSbcl(EvalStringInFrameEval.evalInFrame(data, frame, threadId, currentModule, + LispEnvironmentService.getInstance(project).sendToLisp(EvalStringInFrameEval.evalInFrame(data, frame, threadId, currentModule, result -> { languageConsole.print(result + "\n", ConsoleViewContentType.NORMAL_OUTPUT); onChange.run(); diff --git a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltFrameInfo.java b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltFrameInfo.java index f9311fb..5829bd6 100644 --- a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltFrameInfo.java +++ b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltFrameInfo.java @@ -1,9 +1,10 @@ package com.en_circle.slt.plugin.ui.debug; import com.en_circle.slt.plugin.SltBundle; -import com.en_circle.slt.plugin.SltSBCL; +import com.en_circle.slt.plugin.SltUIConstants; import com.en_circle.slt.plugin.lisp.lisp.*; -import com.en_circle.slt.plugin.swank.requests.SltFrameLocalsAndCatchTags; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.swank.requests.FrameLocalsAndCatchTags; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; @@ -17,10 +18,16 @@ import javax.swing.*; import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.font.TextAttribute; import java.math.BigInteger; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class SltFrameInfo { private static final Logger log = LoggerFactory.getLogger(SltFrameInfo.class); @@ -30,7 +37,10 @@ public class SltFrameInfo { private final BigInteger frameId; private final String module; private final JComponent content; + private SltInspector inspector; private JBTable localsTable; + private JBTabs tabs; + private TabInfo inspectorTab; public SltFrameInfo(Project project, BigInteger threadId, BigInteger frameId, String module) { this.project = project; @@ -44,10 +54,26 @@ public SltFrameInfo(Project project, BigInteger threadId, BigInteger frameId, St } private void create() { - JBTabs tabs = new JBTabsImpl(project); + tabs = new JBTabsImpl(project); localsTable = new JBTable(new FrameTableModel(new ArrayList<>())); localsTable.setFillsViewportHeight(true); + localsTable.setFocusable(false); + localsTable.setRowSelectionAllowed(false); + localsTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + int row = localsTable.rowAtPoint(e.getPoint()); + if (row >= 0) { + int column = localsTable.columnAtPoint(e.getPoint()); + if (column == 0) { + FrameTableModel model = (FrameTableModel) localsTable.getModel(); + openInspector(model.locals.get(row)); + } + } + } + }); + resetRenderer(); TabInfo locals = new TabInfo(new JBScrollPane(localsTable)); locals.setText(SltBundle.message("slt.ui.debugger.frame.locals")); @@ -56,12 +82,40 @@ private void create() { SltFrameConsole frameConsole = new SltFrameConsole(project, threadId, frameId, this::reloadLocals, module); TabInfo consoleTab = frameConsole.create(); tabs.addTab(consoleTab); + + inspector = new SltInspector(project, threadId); + inspectorTab = new TabInfo(new JBScrollPane(inspector.getContent())); + inspectorTab.setText(SltBundle.message("slt.ui.debugger.frame.inspector")); + tabs.addTab(inspectorTab); + content.add(tabs.getComponent(), BorderLayout.CENTER); } + private void resetRenderer() { + localsTable.getColumn(SltBundle.message("slt.ui.debugger.frame.arg")).setCellRenderer(new DefaultTableCellRenderer() { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + Map attributes = new HashMap<>(getFont().getAttributes()); + attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); + setFont(getFont().deriveFont(attributes)); + setForeground(SltUIConstants.HYPERLINK_COLOR); + setCursor(new Cursor(Cursor.HAND_CURSOR)); + + return this; + } + }); + } + + private void openInspector(Local local) { + inspector.loadLocal(local, frameId); + tabs.select(inspectorTab, true); + } + private void reloadLocals() { try { - SltSBCL.getInstance().sendToSbcl(SltFrameLocalsAndCatchTags.getLocals(frameId, threadId, result -> { + LispEnvironmentService.getInstance(project).sendToLisp(FrameLocalsAndCatchTags.getLocals(frameId, threadId, result -> { ApplicationManager.getApplication().invokeLater(() -> { refreshFrameValues(result); }); @@ -78,15 +132,16 @@ public JComponent getContent() { public void refreshFrameValues(LispElement localsAndTags) { LispElement locals = ((LispContainer) localsAndTags).getItems().get(0); - if (locals instanceof LispContainer) { - LispContainer locs = (LispContainer) locals; + if (locals instanceof LispContainer locs) { List parsedLocals = new ArrayList<>(); + int ix=0; for (LispElement e : locs.getItems()) { try { LispContainer ec = (LispContainer) e; String name = ((LispString) LispUtils.pvalue(ec, new LispSymbol(":NAME"))).getValue(); String value = ((LispString) LispUtils.pvalue(ec, new LispSymbol(":VALUE"))).getValue(); Local l = new Local(); + l.ix = ix++; l.name = name; l.value = value; parsedLocals.add(l); @@ -98,13 +153,16 @@ public void refreshFrameValues(LispElement localsAndTags) { } } localsTable.setModel(new FrameTableModel(parsedLocals)); + resetRenderer(); } else { localsTable.setModel(new FrameTableModel(new ArrayList<>())); + resetRenderer(); } } - private static class Local { + public static class Local { + int ix; private String name; private String value; @@ -120,11 +178,11 @@ private FrameTableModel(List locals) { @Override public String getColumnName(int column) { - switch (column) { - case 0: return SltBundle.message("slt.ui.debugger.frame.arg"); - case 1: return SltBundle.message("slt.ui.debugger.frame.value"); - } - return null; + return switch (column) { + case 0 -> SltBundle.message("slt.ui.debugger.frame.arg"); + case 1 -> SltBundle.message("slt.ui.debugger.frame.value"); + default -> null; + }; } @Override @@ -145,5 +203,10 @@ public Object getValueAt(int rowIndex, int columnIndex) { return locals.get(rowIndex).value; } } + + @Override + public Class getColumnClass(int columnIndex) { + return String.class; + } } } diff --git a/src/main/java/com/en_circle/slt/plugin/ui/debug/SltInspector.java b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltInspector.java new file mode 100644 index 0000000..ecbc4fa --- /dev/null +++ b/src/main/java/com/en_circle/slt/plugin/ui/debug/SltInspector.java @@ -0,0 +1,227 @@ +package com.en_circle.slt.plugin.ui.debug; + +import com.en_circle.slt.plugin.SltBundle; +import com.en_circle.slt.plugin.SltUIConstants; +import com.en_circle.slt.plugin.lisp.lisp.LispContainer; +import com.en_circle.slt.plugin.lisp.lisp.LispElement; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService.LispEnvironmentState; +import com.en_circle.slt.plugin.swank.debug.SltInspectedObject; +import com.en_circle.slt.plugin.swank.debug.SltInspectedObject.SltInspectionElement; +import com.en_circle.slt.plugin.swank.requests.InspectFrameVar; +import com.en_circle.slt.plugin.swank.requests.InspectNth; +import com.en_circle.slt.plugin.swank.requests.InspectorAction; +import com.en_circle.slt.plugin.swank.requests.InspectorAction.ActionType; +import com.en_circle.slt.plugin.ui.debug.SltFrameInfo.Local; +import com.intellij.icons.AllIcons.Actions; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.text.HtmlBuilder; +import com.intellij.openapi.util.text.HtmlChunk; +import com.intellij.ui.components.JBScrollPane; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import javax.swing.event.HyperlinkEvent.EventType; +import javax.swing.text.DefaultEditorKit; +import javax.swing.text.html.HTMLEditorKit; +import java.awt.*; +import java.math.BigInteger; +import java.util.regex.Pattern; + +public class SltInspector { + private static final Logger log = LoggerFactory.getLogger(SltInspector.class); + + private final Project project; + private final JPanel content; + private final BigInteger threadId; + private final JEditorPane inspectorGeneratedContents; + + public SltInspector(Project project, BigInteger threadId) { + this.project = project; + this.threadId = threadId; + + content = new JPanel(new BorderLayout()); + DefaultActionGroup controlGroup = new DefaultActionGroup(); + controlGroup.add(new GoBackAction()); + controlGroup.add(new GoForwardAction()); + controlGroup.add(new RefreshAction()); + ActionToolbar toolbar = ActionManager.getInstance() + .createActionToolbar(SltInspector.class.getName(), controlGroup, true); + toolbar.setTargetComponent(content); + content.add(toolbar.getComponent(), BorderLayout.NORTH); + + inspectorGeneratedContents = new JEditorPane("text/html", ""); + inspectorGeneratedContents.setEditorKit(new HTMLEditorKit()); + inspectorGeneratedContents.setEditable(false); + inspectorGeneratedContents.addHyperlinkListener(e -> { + if (e.getEventType() == EventType.ACTIVATED) + navigate(e.getDescription()); + }); + content.add(new JBScrollPane(inspectorGeneratedContents), BorderLayout.CENTER); + } + + public void loadLocal(Local local, BigInteger frame) { + try { + LispEnvironmentService.getInstance(project) + .sendToLisp(InspectFrameVar.inspectVariable(BigInteger.valueOf(local.ix), frame, threadId, + result -> ApplicationManager.getApplication().invokeLater(() -> processResult(result)))); + } catch (Exception e) { + log.warn(SltBundle.message("slt.error.sbclstart"), e); + Messages.showErrorDialog(project, e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); + } + } + + private void processResult(LispElement result) { + SltInspectedObject parsedResult = null; + try { + parsedResult = new SltInspectedObject((LispContainer) result); + } catch (Exception ignored) { + // in case we get garbage we show error + } + clear(); + if (parsedResult == null) { + inspectorGeneratedContents.setText("Failed to parse result. Please report bug with this text: \n" + result.toString()); + } else { + inspectorGeneratedContents.setContentType("text/html"); + inspectorGeneratedContents.setEditorKit(new HTMLEditorKit()); + loadInspectedObject(parsedResult); + } + inspectorGeneratedContents.setCaretPosition(0); + } + + private void loadInspectedObject(SltInspectedObject inspectedObject) { + HtmlBuilder contentBuilder = new HtmlBuilder(); + contentBuilder.append(HtmlChunk.text(inspectedObject.getTitle()) + .wrapWith("h3")); + contentBuilder.append(HtmlChunk.hr()); + for (SltInspectionElement element : inspectedObject.getElements()) { + String text = element.getText(); + if (element.getId() == null) { + String[] parts = text.split(Pattern.quote("\n")); + for (int i=0; i + ApplicationManager.getApplication().invokeLater(() -> processResult(result)))); + } catch (Exception e) { + log.warn(SltBundle.message("slt.error.sbclstart"), e); + Messages.showErrorDialog(project, e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); + } + } + + private class GoBackAction extends AnAction { + + private GoBackAction() { + super(SltBundle.message("slt.ui.inspector.navigation.back"), "", Actions.Back); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent event) { + try { + LispEnvironmentService.getInstance(project) + .sendToLisp(InspectorAction.action(ActionType.GO_BACK, threadId, result -> + ApplicationManager.getApplication().invokeLater(() -> processResult(result)))); + } catch (Exception e) { + log.warn(SltBundle.message("slt.error.sbclstart"), e); + Messages.showErrorDialog(project, e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); + } + } + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + + e.getPresentation().setEnabled(LispEnvironmentService.getInstance(project).getState() == LispEnvironmentState.READY); + } + } + + private class GoForwardAction extends AnAction { + + private GoForwardAction() { + super(SltBundle.message("slt.ui.inspector.navigation.forward"), "", Actions.Forward); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent event) { + try { + LispEnvironmentService.getInstance(project) + .sendToLisp(InspectorAction.action(ActionType.GO_FORWARD, threadId, result -> + ApplicationManager.getApplication().invokeLater(() -> processResult(result)))); + } catch (Exception e) { + log.warn(SltBundle.message("slt.error.sbclstart"), e); + Messages.showErrorDialog(project, e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); + } + } + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + + e.getPresentation().setEnabled(LispEnvironmentService.getInstance(project).getState() ==LispEnvironmentState.READY); + } + } + + private class RefreshAction extends AnAction { + + private RefreshAction() { + super(SltBundle.message("slt.ui.inspector.navigation.refresh"), "", Actions.Refresh); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent event) { + try { + LispEnvironmentService.getInstance(project) + .sendToLisp(InspectorAction.action(ActionType.REFRESH, threadId, result -> + ApplicationManager.getApplication().invokeLater(() -> processResult(result)))); + } catch (Exception e) { + log.warn(SltBundle.message("slt.error.sbclstart"), e); + Messages.showErrorDialog(project, e.getMessage(), SltBundle.message("slt.ui.errors.sbcl.start")); + } + } + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + + e.getPresentation().setEnabled(LispEnvironmentService.getInstance(project).getState() == LispEnvironmentState.READY); + } + } + + public JComponent getContent() { + return content; + } + +} diff --git a/src/main/java/com/en_circle/slt/templates/InitScriptTemplate.java b/src/main/java/com/en_circle/slt/templates/InitScriptTemplate.java deleted file mode 100644 index a6cb931..0000000 --- a/src/main/java/com/en_circle/slt/templates/InitScriptTemplate.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.en_circle.slt.templates; - -import com.en_circle.slt.plugin.swank.SwankServerConfiguration; -import org.watertemplate.Template; - -public class InitScriptTemplate extends Template { - - public InitScriptTemplate(SwankServerConfiguration configuration, String sltCoreScript) { - add("qlpath", configuration.getQuicklispStartScript()); - add("port", "" + configuration.getPort()); - add("cwd", configuration.getProjectDirectory()); - add("sbclcorefile", sltCoreScript); - } - - @Override - protected String getFilePath() { - return "initscript.cl"; - } -} diff --git a/src/main/java/com/en_circle/slt/tools/ProjectUtils.java b/src/main/java/com/en_circle/slt/tools/ProjectUtils.java new file mode 100644 index 0000000..86e525d --- /dev/null +++ b/src/main/java/com/en_circle/slt/tools/ProjectUtils.java @@ -0,0 +1,23 @@ +package com.en_circle.slt.tools; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.wm.WindowManager; + +import java.awt.*; + +public class ProjectUtils { + + public static Project getCurrentProject() { + Project[] projects = ProjectManager.getInstance().getOpenProjects(); + Project activeProject = null; + for (Project project : projects) { + Window window = WindowManager.getInstance().suggestParentWindow(project); + if (window != null && window.isActive()) { + activeProject = project; + } + } + return activeProject; + } + +} diff --git a/src/main/java/com/en_circle/slt/tools/SltApplicationUtils.java b/src/main/java/com/en_circle/slt/tools/SltApplicationUtils.java new file mode 100644 index 0000000..0d47db0 --- /dev/null +++ b/src/main/java/com/en_circle/slt/tools/SltApplicationUtils.java @@ -0,0 +1,63 @@ +package com.en_circle.slt.tools; + +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService; +import com.en_circle.slt.plugin.services.lisp.LispEnvironmentService.LispEnvironmentState; +import com.en_circle.slt.plugin.swank.SlimeRequest; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.ApplicationUtil; +import com.intellij.openapi.progress.ProgressIndicatorProvider; +import com.intellij.openapi.project.Project; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; + +public class SltApplicationUtils { + private static final Logger log = LoggerFactory.getLogger(SltApplicationUtils.class); + + public static X getAsyncResultNoThrow(Project project, Function, SlimeRequest> request) { + return getAsyncResultNoThrow(project, request, true); + } + + public static X getAsyncResultNoThrow(Project project, Function, SlimeRequest> request, boolean startLisp) { + try { + return getAsyncResult(project, request, startLisp); + } catch (Exception e) { + log.warn(e.getMessage()); + return null; + } + } + + public static X getAsyncResult(Project project, Function, SlimeRequest> request) throws Exception { + return getAsyncResult(project, request, true); + } + + public static X getAsyncResult(Project project, Function, SlimeRequest> request, boolean startLisp) throws Exception { + if (LispEnvironmentService.getInstance(project).getState() == LispEnvironmentState.READY && !startLisp) { + return null; + } + + Future future = ApplicationManager.getApplication().executeOnPooledThread(() -> { + CyclicBarrier barrier = new CyclicBarrier(2); + List pointer = new ArrayList<>(); + LispEnvironmentService.getInstance(project).sendToLisp(request.apply(result -> { + pointer.add(result); + try { + barrier.await(); + } catch (Exception ignored) { + + } + }), startLisp); + barrier.await(); + return pointer.isEmpty() ? null : pointer.get(0); + }); + return ApplicationUtil.runWithCheckCanceled(future, + ProgressIndicatorProvider.getInstance().getProgressIndicator()); + } + +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index e759fcc..b50411a 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -13,6 +13,9 @@ messages.SltBundle + + @@ -35,18 +38,15 @@ - - + anchor="bottom" icon="/icons/fileicon.svg" /> + implementationClass="com.en_circle.slt.plugin.highlights.SltStaticHighlighter"/> + implementationClass="com.en_circle.slt.plugin.highlights.SltStaticHighlighterFactory"/> @@ -57,6 +57,11 @@ + + + + @@ -108,4 +113,20 @@ + + ## 0.2.0 + + ### Added + + - Added first version of inspector - so far only read-only. + To access, start interactive debugging and then click on any local variable. + - Macro expand. When you hover over a symbol that is a macro call in a form, + it will macro expand it in the documentation. Due to async notion, + you need to hover again to see it. + + ### Fixes + + - Changed internal environment to be more decoupled + + \ No newline at end of file diff --git a/src/main/resources/META-INF/pluginIcon.svg b/src/main/resources/META-INF/pluginIcon.svg index dcf6b99..51db885 100644 --- a/src/main/resources/META-INF/pluginIcon.svg +++ b/src/main/resources/META-INF/pluginIcon.svg @@ -1,12 +1,256 @@ - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SLT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/logo/logo.svg b/src/main/resources/logo/logo.svg new file mode 100644 index 0000000..a1f4846 --- /dev/null +++ b/src/main/resources/logo/logo.svg @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SLT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/messages/SltBundle.properties b/src/main/resources/messages/SltBundle.properties index 71a2ff5..f418b83 100644 --- a/src/main/resources/messages/SltBundle.properties +++ b/src/main/resources/messages/SltBundle.properties @@ -13,10 +13,15 @@ slt.documentation.types.specialform=Special Form slt.documentation.types.constant=Constant Form slt.documentation.types.specvariable=Special Variable slt.documentation.types.keyword=Keyword +slt.documentation.types.class=Class +slt.documentation.macroexpand=Macro expansion: +slt.documentation.types.method=Method +slt.documentation.macroexpand.generating=Generating macro expansion, hover again to see it # UI slt.ui.errors.sbcl.start=Failed to Start SBCL slt.ui.errors.sbcl.stop=Failed to Stop SBCL +slt.ui.errors.sbcl.start.noconf=No configuration set up slt.ui.packageselector.title=Current package: slt.ui.packageselector.refresh=Refresh Packages @@ -32,13 +37,15 @@ slt.ui.colorsettings.specialform=Special form slt.ui.colorsettings.function=Function call slt.ui.colorsettings.macro=Macro call slt.ui.colorsettings.reader=Reader macro +slt.ui.colorsettings.class=Class +slt.ui.colorsettings.method=Method # Process slt.ui.process.title=SBCL Process -slt.ui.process.processpid=SBCL Process Pid: -slt.ui.process.startsbcl=Start SBCL Instance -slt.ui.process.stopsbcl=Stop SBCL Instance -slt.ui.process.openrepl=Create SBCL REPL +slt.ui.process.processpid=Lisp Process Pid: +slt.ui.process.startinstance=Start Lisp Instance +slt.ui.process.stopinstance=Stop Lisp Instance +slt.ui.process.openrepl.sbcl=Create Lisp REPL slt.ui.process.gl.clear=Clear Text slt.ui.process.gl.wrap=Soft Wrap slt.ui.process.gl.scroll=Scroll to End @@ -65,10 +72,16 @@ slt.ui.debugger.rest.text=Expressions separated by space for rest argument: slt.ui.debugger.rest.title=Specify Restart Rest Arguments slt.ui.debugger.frame.repl=Frame REPL slt.ui.debugger.frame.locals=Locals +slt.ui.debugger.frame.inspector=Inspector slt.ui.debugger.frame.failedtoparse=Failed to parse slt.ui.debugger.frame.arg=Name slt.ui.debugger.frame.value=Value +# inspector +slt.ui.inspector.navigation.back=Go Back in History +slt.ui.inspector.navigation.forward=Go Forward in History +slt.ui.inspector.navigation.refresh=Refresh Currently Inspected Object + # Settings slt.ui.settings.title=SLT Configuration slt.ui.settings.executable=SBCL executable: diff --git a/src/main/resources/templates/en_US/highlight.cl b/src/main/resources/templates/en_US/highlight.cl index 01b6746..088c5c8 100644 --- a/src/main/resources/templates/en_US/highlight.cl +++ b/src/main/resources/templates/en_US/highlight.cl @@ -1 +1,20 @@ -((#x4505) 5) \ No newline at end of file +(defpackage :example + (:use :cl) + (:export foobar)) + +(in-package :example) ; my example comment + +(defun foobar (arg1 &rest bar) + "Implements the foo bar!" + (cons #1=(if bar + (loop for x from 0 to 47 collect + `(arg1 ,x)) + (loop for x from 10 to 20 collect + `(,@arg1 ,x))) + #1#)) + +#| long +comment |# + +(defclass foo (T) + ()) \ No newline at end of file diff --git a/src/main/resources/templates/en_US/slt.cl b/src/main/resources/templates/en_US/slt.cl index d1be363..68ed20f 100644 --- a/src/main/resources/templates/en_US/slt.cl +++ b/src/main/resources/templates/en_US/slt.cl @@ -2,7 +2,9 @@ (defpackage :slt-core (:use :cl :swank) - (:export analyze-symbol analyze-symbols read-fix-packages list-package-names)) + (:export analyze-symbol analyze-symbols read-fix-packages list-package-names + initialize-or-get-debug-context debug-context debug-frame-variable register-variable + )) ; swank/slime overrides @@ -95,6 +97,12 @@ format suitable for Emacs." (let ((*standard-output* (make-string-output-stream))) (cond ((not test-sym) (list NIL NIL NIL)) + ((and (fboundp test-sym) + (typep (symbol-function test-sym) 'generic-function)) + (progn + (describe test-sym) + (list :method (get-output-stream-string *standard-output*) + (swank:find-source-location (symbol-function test-sym))))) ((special-operator-p test-sym) (list :special-form NIL NIL)) ((macro-function test-sym) (progn (describe test-sym) @@ -107,7 +115,7 @@ format suitable for Emacs." (list :function (get-output-stream-string *standard-output*) - (swank:find-source-location (symbol-function test-sym))))) + (swank:find-source-location (symbol-function test-sym))))) ((specialp test-sym) (progn (describe test-sym) (list :special (get-output-stream-string *standard-output*) NIL))) @@ -117,6 +125,10 @@ format suitable for Emacs." ((constantp test-sym) (progn (describe test-sym) (list :constant (get-output-stream-string *standard-output*) NIL))) + ((find-class test-sym NIL) (progn + (describe test-sym) + (list :class (get-output-stream-string *standard-output*) + (swank:find-source-location (find-class test-sym))))) (T (list NIL NIL NIL)))))) (defun analyze-symbols (symbols) diff --git a/src/test/java/SlimeTest.java b/src/test/java/SlimeTest.java index a3e90ce..e2634e0 100644 --- a/src/test/java/SlimeTest.java +++ b/src/test/java/SlimeTest.java @@ -1,5 +1,10 @@ -import com.en_circle.slt.plugin.swank.*; +import com.en_circle.slt.plugin.environment.SltLispEnvironment; +import com.en_circle.slt.plugin.environment.SltSBCLEnvironment; +import com.en_circle.slt.plugin.environment.SltSBCLEnvironmentConfiguration; +import com.en_circle.slt.plugin.swank.SlimeListener; import com.en_circle.slt.plugin.swank.SlimeListener.DebugInterface; +import com.en_circle.slt.plugin.swank.SwankClient; +import com.en_circle.slt.plugin.swank.SwankPacket; import com.en_circle.slt.plugin.swank.debug.SltDebugInfo; import org.awaitility.Awaitility; @@ -13,7 +18,8 @@ public static void main(String[] args) throws Exception { try { AtomicLong sent = new AtomicLong(); AtomicLong expected = new AtomicLong(); - SwankServer.startSbcl(new SwankServerConfiguration.Builder().build()); + SltLispEnvironment environment = new SltSBCLEnvironment(); + environment.start(new SltSBCLEnvironmentConfiguration.Builder().build()); SlimeListener listener = new SlimeListener(null, false, null, new DebugInterface() { @Override public void onDebugCreate(SltDebugInfo info) { @@ -42,7 +48,7 @@ public void onDebugReturn(BigInteger debugId, BigInteger level) { .atMost(10, TimeUnit.SECONDS) .until(() -> expected.get() > sent.get() && sent.get() > 0); } - SwankServer.stop(); + environment.stop(); } catch (Exception e) { } diff --git a/src/test/java/SwankTest.java b/src/test/java/SwankTest.java index f2b64e2..45b6262 100644 --- a/src/test/java/SwankTest.java +++ b/src/test/java/SwankTest.java @@ -1,14 +1,15 @@ import com.en_circle.slt.plugin.SltCommonLispFileType; import com.en_circle.slt.plugin.SltCommonLispLanguage; import com.en_circle.slt.plugin.SltCommonLispParserDefinition; +import com.en_circle.slt.plugin.environment.SltLispEnvironment; +import com.en_circle.slt.plugin.environment.SltLispEnvironment.SltOutput; +import com.en_circle.slt.plugin.environment.SltSBCLEnvironment; +import com.en_circle.slt.plugin.environment.SltSBCLEnvironmentConfiguration; import com.en_circle.slt.plugin.lisp.lisp.LispElement; import com.en_circle.slt.plugin.lisp.lisp.LispUtils; import com.en_circle.slt.plugin.lisp.psi.LispCoreProjectEnvironment; import com.en_circle.slt.plugin.swank.SwankClient; import com.en_circle.slt.plugin.swank.SwankPacket; -import com.en_circle.slt.plugin.swank.SwankServer; -import com.en_circle.slt.plugin.swank.SwankServer.SwankServerOutput; -import com.en_circle.slt.plugin.swank.SwankServerConfiguration; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; @@ -18,14 +19,15 @@ public class SwankTest { public static void main(String[] args) throws Exception { try { - SwankServerConfiguration c = new SwankServerConfiguration.Builder() + SltSBCLEnvironmentConfiguration c = new SltSBCLEnvironmentConfiguration.Builder() .setListener((output, newData) -> { - if (output == SwankServerOutput.STDERR) { + if (output == SltOutput.STDERR) { System.err.print(newData); } }) .build(); - SwankServer.startSbcl(c); + SltLispEnvironment environment = new SltSBCLEnvironment(); + environment.start(c); try (SwankClient client = new SwankClient("127.0.0.1", 4005, packet -> { LispCoreProjectEnvironment projectEnvironment = new LispCoreProjectEnvironment(); projectEnvironment.getEnvironment() @@ -50,7 +52,7 @@ public static void main(String[] args) throws Exception { Thread.sleep(10000); } - SwankServer.stop(); + environment.stop(); } catch (Exception e) { e.printStackTrace(); }