Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.flowingcode.addons</groupId>
<artifactId>xterm-console</artifactId>
<version>3.2.1-SNAPSHOT</version>
<version>3.3.0-SNAPSHOT</version>
<name>XTerm Console Addon</name>
<description>Integration of xterm.js for Vaadin Flow</description>

Expand Down Expand Up @@ -77,7 +77,7 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
<version>1.18.34</version>
<version>1.18.42</version>
</dependency>

<dependency>
Expand All @@ -86,6 +86,12 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.flowingcode.vaadin</groupId>
<artifactId>json-migration-helper</artifactId>
<version>0.9.2</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
Expand All @@ -95,6 +101,12 @@
<groupId>com.vaadin</groupId>
<artifactId>vaadin-testbench</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.flowingcode.vaadin.test</groupId>
<artifactId>testbench-rpc</artifactId>
<version>1.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);

}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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);
}

}