diff --git a/pom.xml b/pom.xml index 1940059..e5f1a4f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.flowingcode.addons xterm-console - 3.2.1-SNAPSHOT + 3.3.0-SNAPSHOT XTerm Console Addon Integration of xterm.js for Vaadin Flow @@ -77,7 +77,7 @@ org.projectlombok lombok provided - 1.18.34 + 1.18.42 @@ -86,6 +86,12 @@ true + + com.flowingcode.vaadin + json-migration-helper + 0.9.2 + + org.slf4j slf4j-simple @@ -95,6 +101,12 @@ com.vaadin vaadin-testbench test + + + com.flowingcode.vaadin.test + testbench-rpc + 1.4.0 + test org.hamcrest diff --git a/src/main/java/com/flowingcode/vaadin/addons/xterm/ClientTerminalAddon.java b/src/main/java/com/flowingcode/vaadin/addons/xterm/ClientTerminalAddon.java index 51b1f37..2926a1c 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/xterm/ClientTerminalAddon.java +++ b/src/main/java/com/flowingcode/vaadin/addons/xterm/ClientTerminalAddon.java @@ -2,7 +2,7 @@ * #%L * XTerm Console Addon * %% - * Copyright (C) 2020 - 2025 Flowing Code + * Copyright (C) 2020 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,17 @@ */ package com.flowingcode.vaadin.addons.xterm; +import com.flowingcode.vaadin.jsonmigration.JsonMigration; import com.vaadin.flow.dom.Element; -import com.vaadin.flow.internal.JsonCodec; +import com.vaadin.flow.server.Version; import elemental.json.Json; import elemental.json.JsonArray; +import elemental.json.JsonValue; import java.io.Serializable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import lombok.SneakyThrows; /** * Represents an abstract base class for server-side terminal add-ons that have a corresponding @@ -96,11 +102,39 @@ protected final void executeJs(String expression, Serializable... parameters) { JsonArray args = Json.createArray(); for (int i = 0; i < parameters.length; i++) { - args.set(i, JsonCodec.encodeWithTypeInfo(parameters[i])); + args.set(i, encodeWithTypeInfo(parameters[i])); } expression = expression.replaceAll("\\$(\\d+)", "\\$1[$1]"); xterm.executeJs("(function(){" + expression + "}).apply(this.addons[$0],$1);", name, args); } + private static final MethodHandle encodeWithTypeInfo = lookup_encodeWithTypeInfo(); + + @SneakyThrows + private static MethodHandle lookup_encodeWithTypeInfo() { + MethodHandle handle; + if (Version.getMajorVersion() > 24) { + Class result = Class.forName("tools.jackson.databind.JsonNode"); + Class codec = Class.forName("com.vaadin.flow.internal.JacksonCodec"); + MethodType type = MethodType.methodType(result, Object.class); + handle = MethodHandles.lookup().findStatic(codec, "encodeWithTypeInfo", type); + } else { + Class codec = Class.forName("com.vaadin.flow.internal.JsonCodec"); + MethodType type = MethodType.methodType(JsonValue.class, Object.class); + handle = MethodHandles.lookup().findStatic(codec, "encodeWithTypeInfo", type); + } + return handle.asType(MethodType.methodType(Object.class, Object.class)); + } + + private static JsonValue encodeWithTypeInfo(Object obj) { + try { + return JsonMigration.convertToJsonValue(encodeWithTypeInfo.invokeExact(obj)); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + } diff --git a/src/main/java/com/flowingcode/vaadin/addons/xterm/ITerminalConsole.java b/src/main/java/com/flowingcode/vaadin/addons/xterm/ITerminalConsole.java index e545aaf..de34c22 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/xterm/ITerminalConsole.java +++ b/src/main/java/com/flowingcode/vaadin/addons/xterm/ITerminalConsole.java @@ -2,7 +2,7 @@ * #%L * XTerm Console Addon * %% - * Copyright (C) 2020 - 2023 Flowing Code + * Copyright (C) 2020 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ */ package com.flowingcode.vaadin.addons.xterm; +import com.flowingcode.vaadin.jsonmigration.JsonMigration; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.ComponentEvent; import com.vaadin.flow.component.ComponentEventListener; @@ -30,12 +31,14 @@ import elemental.json.JsonValue; import java.util.concurrent.CompletableFuture; import lombok.Getter; +import lombok.experimental.ExtensionMethod; /** * Add console support to XTerm. This provides handling of cursor, home/end, insert, delete and * backspace keys, as well as a {@link #addLineListener(ComponentEventListener) line event}. */ @SuppressWarnings("serial") +@ExtensionMethod(value = JsonMigration.class, suppressBaseMethods = true) public interface ITerminalConsole extends HasElement { // TODO set cursor properties (blink, style, width) separately for insert and overwrite modes diff --git a/src/main/java/com/flowingcode/vaadin/addons/xterm/XTermBase.java b/src/main/java/com/flowingcode/vaadin/addons/xterm/XTermBase.java index de8ec8e..66fc659 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/xterm/XTermBase.java +++ b/src/main/java/com/flowingcode/vaadin/addons/xterm/XTermBase.java @@ -19,6 +19,7 @@ */ package com.flowingcode.vaadin.addons.xterm; +import com.flowingcode.vaadin.jsonmigration.JsonMigration; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.HasEnabled; import com.vaadin.flow.component.HasSize; @@ -55,12 +56,14 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import lombok.experimental.Delegate; +import lombok.experimental.ExtensionMethod; /** Server-side component for the XTerm component. */ @SuppressWarnings("serial") @NpmPackage(value = "xterm", version = "5.1.0") @JsModule("./fc-xterm/xterm-element.ts") @CssImport("xterm/css/xterm.css") +@ExtensionMethod(value = JsonMigration.class, suppressBaseMethods = true) public abstract class XTermBase extends Component implements ITerminal, ITerminalOptions, HasSize, HasEnabled { diff --git a/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/AbstractViewTest.java b/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/AbstractViewTest.java index f3b7ad9..22bd67d 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/AbstractViewTest.java +++ b/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/AbstractViewTest.java @@ -49,7 +49,7 @@ public abstract class AbstractViewTest extends ParallelTest { @Rule public ScreenshotOnFailureRule rule = new ScreenshotOnFailureRule(this, true); public AbstractViewTest() { - this(""); + this(IntegrationView.ROUTE); } protected AbstractViewTest(String route) { diff --git a/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/IntegrationView.java b/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/IntegrationView.java new file mode 100644 index 0000000..af669a8 --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/IntegrationView.java @@ -0,0 +1,76 @@ +/*- + * #%L + * XTerm Console Addon + * %% + * Copyright (C) 2020 - 2026 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.addons.xterm.integration; + +import com.flowingcode.vaadin.addons.xterm.ITerminalClipboard.UseSystemClipboard; +import com.flowingcode.vaadin.addons.xterm.ITerminalOptions.CursorStyle; +import com.flowingcode.vaadin.addons.xterm.TerminalHistory; +import com.flowingcode.vaadin.addons.xterm.XTerm; +import com.vaadin.flow.component.ClientCallable; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; + +@SuppressWarnings("serial") +@Route(value = IntegrationView.ROUTE) +public class IntegrationView extends VerticalLayout implements IntegrationViewCallables { + + public static final String ROUTE = "xterm/it"; + + private XTerm xterm; + + private SampleClientTerminalAddon sampleClientTerminalAddon; + + public IntegrationView() { + setSizeFull(); + setPadding(false); + getElement().getStyle().set("background", "black"); + + xterm = new XTerm(); + xterm.setPrompt("[user@xterm ~]$ "); + + xterm.writeln("xterm add-on by Flowing Code S.A.\n\n"); + xterm.writePrompt(); + + xterm.setCursorBlink(true); + xterm.setCursorStyle(CursorStyle.UNDERLINE); + + xterm.setSizeFull(); + + xterm.setCopySelection(true); + xterm.setUseSystemClipboard(UseSystemClipboard.READWRITE); + xterm.setPasteWithMiddleClick(true); + xterm.setPasteWithRightClick(true); + + sampleClientTerminalAddon = new SampleClientTerminalAddon(xterm); + TerminalHistory.extend(xterm); + xterm.addLineListener(ev -> xterm.writePrompt()); + + xterm.focus(); + xterm.fit(); + add(xterm); + } + + @Override + @ClientCallable + public void setSampleClientTerminalAddonValue(String value) { + sampleClientTerminalAddon.setValue(value); + } + +} diff --git a/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/IntegrationViewCallables.java b/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/IntegrationViewCallables.java new file mode 100644 index 0000000..cc4782a --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/IntegrationViewCallables.java @@ -0,0 +1,26 @@ +/*- + * #%L + * XTerm Console Addon + * %% + * Copyright (C) 2020 - 2026 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.addons.xterm.integration; + +public interface IntegrationViewCallables { + + void setSampleClientTerminalAddonValue(String value); + +} diff --git a/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/SampleClientTerminalAddon.java b/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/SampleClientTerminalAddon.java new file mode 100644 index 0000000..ab898c6 --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/SampleClientTerminalAddon.java @@ -0,0 +1,56 @@ +/*- + * #%L + * XTerm Console Addon + * %% + * Copyright (C) 2020 - 2026 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.addons.xterm.integration; + +import com.flowingcode.vaadin.addons.xterm.ClientTerminalAddon; +import com.flowingcode.vaadin.addons.xterm.XTermBase; + +@SuppressWarnings("serial") +public class SampleClientTerminalAddon extends ClientTerminalAddon { + + public final static String NAME = "sample-terminal-addon"; + + public SampleClientTerminalAddon(XTermBase xterm) { + super(xterm); + + // see https://github.com/FlowingCode/XTermConsoleAddon/issues/98 + xterm.getElement().executeJs(""" + window.Vaadin.Flow.fcXtermConnector = window.Vaadin.Flow.fcXtermConnector || {}; + window.Vaadin.Flow.fcXtermConnector.load_sample = (name, node) => { + const addon = { + activate: () => {}, + dispose: () => {} + }; + node.terminal.loadAddon(addon); + node.addons[name]=addon; + }; + Vaadin.Flow.fcXtermConnector.load_sample($0, this);""", NAME); + } + + public void setValue(String value) { + executeJs("this.value=$0;", value); + } + + @Override + public String getName() { + return NAME; + } + +} \ No newline at end of file diff --git a/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/SampleClientTerminalAddonIT.java b/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/SampleClientTerminalAddonIT.java new file mode 100644 index 0000000..6c9c170 --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/SampleClientTerminalAddonIT.java @@ -0,0 +1,43 @@ +/*- + * #%L + * XTerm Console Addon + * %% + * Copyright (C) 2020 - 2026 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.addons.xterm.integration; + +import static org.junit.Assert.assertEquals; +import com.flowingcode.vaadin.testbench.rpc.HasRpcSupport; +import org.junit.Test; + +public class SampleClientTerminalAddonIT extends AbstractViewTest implements HasRpcSupport { + + private static final String BAR = "BAR"; + IntegrationViewCallables $server = createCallableProxy(IntegrationViewCallables.class); + + @Test + public void testSelectionFeature1() throws InterruptedException { + XTermElement term = $(XTermElement.class).first(); + $server.setSampleClientTerminalAddonValue(BAR); + assertEquals(BAR, getSampleClientTerminalValue(term)); + } + + private static String getSampleClientTerminalValue(XTermElement term) { + return (String) term.executeScript("return this.addons[arguments[0]].value;", + SampleClientTerminalAddon.NAME); + } + +} \ No newline at end of file