├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.yml
│ ├── config.yml
│ └── feature-request.yml
└── workflows
│ ├── commits.yml
│ ├── vaadin14.yml
│ ├── vaadin23.yml
│ └── vaadin24.yml
├── .gitignore
├── LICENSE.txt
├── README.md
├── assembly
├── MANIFEST.MF
└── assembly.xml
├── drivers.xml
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── flowingcode
│ │ └── vaadin
│ │ └── addons
│ │ └── xterm
│ │ ├── ClientTerminalAddon.java
│ │ ├── ITerminal.java
│ │ ├── ITerminalClipboard.java
│ │ ├── ITerminalConsole.java
│ │ ├── ITerminalFit.java
│ │ ├── ITerminalOptions.java
│ │ ├── ITerminalSelection.java
│ │ ├── PreserveStateAddon.java
│ │ ├── TerminalAddon.java
│ │ ├── TerminalHistory.java
│ │ ├── TerminalTheme.java
│ │ ├── XTerm.java
│ │ ├── XTermBase.java
│ │ └── utils
│ │ └── StateMemoizer.java
└── resources
│ └── META-INF
│ ├── VAADIN
│ └── package.properties
│ ├── frontend
│ └── fc-xterm
│ │ ├── xterm-clipboard-mixin.ts
│ │ ├── xterm-console-mixin.ts
│ │ ├── xterm-element.ts
│ │ ├── xterm-fit-mixin.ts
│ │ ├── xterm-insertfix-mixin.ts
│ │ ├── xterm-selection-mixin.ts
│ │ └── xterm.ts
│ └── native-image
│ └── com.flowingcode.addons
│ └── xterm-console
│ ├── proxy-config.json
│ └── reflect-config.json
└── test
└── java
└── com
└── flowingcode
└── vaadin
└── addons
├── DemoLayout.java
└── xterm
├── DemoView.java
├── PreserveStateAddonTest.java
├── XtermDemoView.java
├── integration
├── AbstractViewTest.java
├── ClipboardFeatureIT.java
├── ConsoleFeatureIT.java
├── FitFeatureIT.java
├── Position.java
├── SelectionFeatureIT.java
├── TerminalHistoryIT.java
├── XTermElement.java
├── XTermIT.java
└── XTermTestUtils.java
├── test
└── SerializationTest.java
└── utils
└── StateMemoizerTest.java
/.github/ISSUE_TEMPLATE/bug-report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Please report issues related to XTerm add-on here.
3 | body:
4 | - type: textarea
5 | id: problem-description
6 | attributes:
7 | label: Describe the bug
8 | description: A clear description of the issue you're experiencing.
9 | validations:
10 | required: true
11 | - type: textarea
12 | id: expected-behavior
13 | attributes:
14 | label: Expected behavior
15 | description: A clear and concise description of the expected behavior.
16 | validations:
17 | required: false
18 | - type: textarea
19 | id: minimal-reproduction
20 | attributes:
21 | label: Minimal reproducible example
22 | description: If possible, add a concise code snippet that reproduces the issue and describe the steps needed to follow to reproduce it.
23 | validations:
24 | required: false
25 | - type: input
26 | id: addon-version
27 | attributes:
28 | label: Add-on Version
29 | description: The version of the add-on on which you're experiencing the issue.
30 | validations:
31 | required: true
32 | - type: input
33 | id: vaadin-version
34 | attributes:
35 | label: Vaadin Version
36 | description: The complete Vaadin version (X.Y.Z) on which the issue is reproducible.
37 | validations:
38 | required: true
39 | - type: textarea
40 | id: additional-information
41 | attributes:
42 | label: Additional information
43 | description: "Any other context/information about the issue can be added here (browser, OS, etc.)."
44 | validations:
45 | required: false
46 |
47 |
48 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Please add feature suggestions related to XTerm add-on here.
3 | body:
4 | - type: textarea
5 | id: feature-proposal
6 | attributes:
7 | label: Feature proposal
8 | description: A concise but detailed description of the feature that you would like to see in the add-on.
9 | validations:
10 | required: true
11 | - type: textarea
12 | id: feature-implementation
13 | attributes:
14 | label: Describe solution expectations
15 | description: Do you have an idea/expectations of how it could be implemented? Did you try a possible solution that you want to share?
16 | validations:
17 | required: false
18 | - type: textarea
19 | id: additional-information
20 | attributes:
21 | label: Additional information
22 | description: Add any extra information you think it might be relevant to the request.
23 | validations:
24 | required: false
25 |
--------------------------------------------------------------------------------
/.github/workflows/commits.yml:
--------------------------------------------------------------------------------
1 | name: Check Commits
2 |
3 | on:
4 | pull_request:
5 |
6 | jobs:
7 | check-commits:
8 | uses: FlowingCode/GithubActions/.github/workflows/check-commits.yml@main
9 |
--------------------------------------------------------------------------------
/.github/workflows/vaadin14.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Java CI with Maven
10 |
11 | on:
12 | push:
13 | branches: [ "1.x" ]
14 | pull_request:
15 | branches: [ "1.x" ]
16 |
17 | jobs:
18 | build-vaadin14:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Set up JDK
23 | uses: actions/setup-java@v3
24 | with:
25 | java-version: '8'
26 | distribution: 'temurin'
27 | cache: maven
28 | - name: Build (Vaadin 14)
29 | run: mvn -B package --file pom.xml
30 |
--------------------------------------------------------------------------------
/.github/workflows/vaadin23.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Java CI with Maven
10 |
11 | on:
12 | push:
13 | branches: [ "2.x" ]
14 | pull_request:
15 | branches: [ "2.x" ]
16 |
17 | jobs:
18 |
19 | build-vaadin23:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v4
23 | - name: Set up JDK
24 | uses: actions/setup-java@v3
25 | with:
26 | java-version: '11'
27 | distribution: 'temurin'
28 | cache: maven
29 | - name: Build (Vaadin 23)
30 | run: mvn -B package --file pom.xml -Pv23
31 |
--------------------------------------------------------------------------------
/.github/workflows/vaadin24.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Java CI with Maven
10 |
11 | on:
12 | push:
13 | branches: [ "master" ]
14 | pull_request:
15 | branches: [ "master" ]
16 |
17 | jobs:
18 |
19 | build-vaadin24:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v4
23 | - name: Set up JDK
24 | uses: actions/setup-java@v3
25 | with:
26 | java-version: '17'
27 | distribution: 'temurin'
28 | cache: maven
29 | - name: Build (Vaadin 24)
30 | run: mvn -B package --file pom.xml
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | target
3 | .vscode
4 | .settings
5 | .project
6 | .classpath
7 | webpack.generated.js
8 | package-lock.json
9 | package.json
10 | webpack.config.js
11 | /error-screenshots
12 | drivers
13 | tsconfig.json
14 | .idea
15 | types.d.ts
16 | /frontend/generated
17 | /frontend/index.html
18 | vite.generated.ts
19 | /.npmrc
20 | /.pnpm-debug.log
21 | /pnpm-lock.yaml
22 | /pnpmfile.js
23 | /src/main/bundles
24 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://vaadin.com/directory/component/xterm-console-addon)
2 | [](https://vaadin.com/directory/component/xterm-console-addon)
3 | [](https://jenkins.flowingcode.com/job/XTerm-2-addon)
4 | [](https://javadoc.flowingcode.com/artifact/com.flowingcode.addons/xterm-console)
5 |
6 | # XTerm Console Add-on
7 |
8 | Vaadin 14+ Java integration of [xterm.js](https://xtermjs.org/) terminal emulator.
9 |
10 | ## Features
11 |
12 | * Send input text to server
13 | * Programmatically write to the console
14 | * Clipboard support
15 | * Command line edition (cursor keys, insert, etc.)
16 | * ANSI escape sequences
17 | * And much more...
18 |
19 | ## Online demo
20 |
21 | * [Vaadin 14](http://addonsv14.flowingcode.com/xterm) (Add-on version 1.x)
22 | * [Vaadin 23](http://addonsv23.flowingcode.com/xterm) (Add-on version 2.x)
23 | * [Vaadin 24](https://addonsv24.flowingcode.com/xterm) (Add-on version 3.x)
24 |
25 | ## Download release
26 |
27 | [Available in Vaadin Directory](https://vaadin.com/directory/component/xterm-console-addon)
28 |
29 | ### Maven install
30 |
31 | Add the following dependencies in your pom.xml file:
32 |
33 | ```xml
34 |
45 | * This constructor ensures the add-on is registered with the terminal and verifies that the 46 | * add-on's name, as returned by {@link #getName()}, is not {@code null}. A non-null name is 47 | * required for client-side add-ons to be uniquely identified and targeted for JavaScript 48 | * execution. 49 | *
50 | * 51 | * @param xterm the {@link XTermBase} instance this add-on will be attached to. Must not be 52 | * {@code null}. 53 | * @throws NullPointerException if {@code xterm} is {@code null} 54 | * @throws IllegalStateException if {@link #getName()} returns {@code null} immediately after 55 | * superclass construction. This check relies on {@code getName()} being a static value. 56 | */ 57 | protected ClientTerminalAddon(XTermBase xterm) { 58 | super(xterm); 59 | this.xterm = xterm; 60 | if (getName() == null) { 61 | throw new IllegalStateException("getName() must return a non-null value"); 62 | } 63 | } 64 | 65 | /** 66 | * The xterm instance that this add-on is associated with. 67 | */ 68 | protected XTermBase getXterm() { 69 | return xterm; 70 | } 71 | 72 | /** 73 | * Retrieves the unique name of this client-side add-on. 74 | *75 | * This name is used by {@link #executeJs(String, Serializable...)} to target the corresponding 76 | * JavaScript object on the client (i.e., {@code this.addons[name]} within the client-side 77 | * terminal's scope). The name effectively acts as a key in a client-side add-ons collection 78 | * managed by the terminal. 79 | *
80 | * 81 | * @return the unique, non-null string identifier for the client-side counterpart of this add-on. 82 | * Subclasses must implement this to provide a name for add-on-specific JavaScript 83 | * execution. 84 | */ 85 | protected abstract String getName(); 86 | 87 | /** 88 | * Executes a JavaScript {@code expression} in the context of this add-on, with the specified 89 | * {@code parameters}. 90 | * 91 | * @see #getName() 92 | * @see Element#executeJs(String, Serializable...) 93 | */ 94 | protected final void executeJs(String expression, Serializable... parameters) { 95 | String name = getName(); 96 | 97 | JsonArray args = Json.createArray(); 98 | for (int i = 0; i < parameters.length; i++) { 99 | args.set(i, JsonCodec.encodeWithTypeInfo(parameters[i])); 100 | } 101 | 102 | expression = expression.replaceAll("\\$(\\d+)", "\\$1[$1]"); 103 | xterm.executeJs("(function(){" + expression + "}).apply(this.addons[$0],$1);", name, args); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/flowingcode/vaadin/addons/xterm/ITerminal.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * XTerm Console Addon 4 | * %% 5 | * Copyright (C) 2020 - 2023 Flowing Code 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.flowingcode.vaadin.addons.xterm; 21 | 22 | import java.io.Serializable; 23 | import java.util.concurrent.CompletableFuture; 24 | 25 | /** The API that represents an xterm.js terminal. */ 26 | public interface ITerminal extends Serializable { 27 | 28 | /** Unfocus the terminal. */ 29 | void blur(); 30 | 31 | /** Focus the terminal. */ 32 | void focus(); 33 | 34 | /** Gets whether the terminal has an active selection. */ 35 | CompletableFuture- Letter spacing - Cursor blink 119 | */ 120 | void setRendererType(RendererType value); 121 | 122 | enum RendererType { 123 | DOM, 124 | CANVAS 125 | } 126 | 127 | /** 128 | * Whether to select the word under the cursor on right click, this is standard behavior in a lot 129 | * of macOS applications. 130 | */ 131 | void setRightClickSelectsWord(boolean value); 132 | 133 | /** 134 | * Whether screen reader support is enabled. When on this will expose supporting elements in the 135 | * DOM to support NVDA on Windows and VoiceOver on macOS. 136 | */ 137 | void setScreenReaderMode(boolean value); 138 | 139 | /** 140 | * The amount of scrollback in the terminal. Scrollback is the amount of rows that are retained 141 | * when lines are scrolled beyond the initial viewport. 142 | */ 143 | void setScrollback(int value); 144 | 145 | /** The scrolling speed multiplier used for adjusting normal scrolling speed. */ 146 | void setScrollSensitivity(int value); 147 | 148 | /** The size of tab stops in the terminal. */ 149 | void setTabStopWidth(int value); 150 | 151 | /** 152 | * A string containing all characters that are considered word separated by the double click to 153 | * select work logic. 154 | */ 155 | void setWordSeparator(String value); 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/com/flowingcode/vaadin/addons/xterm/ITerminalSelection.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * XTerm Console Addon 4 | * %% 5 | * Copyright (C) 2020 - 2023 Flowing Code 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.flowingcode.vaadin.addons.xterm; 21 | 22 | import com.vaadin.flow.component.HasElement; 23 | 24 | /** 25 | * Add selection support to XTerm using arrow keys. 26 | */ 27 | public interface ITerminalSelection extends HasElement { 28 | 29 | /** Sets the command line prompt. */ 30 | default void setKeyboardSelectionEnabled(boolean enabled) { 31 | getElement().setProperty("keyboardSelectionEnabled", enabled); 32 | } 33 | 34 | /** Returns the command line prompt. */ 35 | default boolean getKeyboardSelectionEnabled() { 36 | // the feature is enabled by default 37 | // getProperty defaults to false in case the mixin isn't applied 38 | return getElement().getProperty("keyboardSelectionEnabled", false); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/flowingcode/vaadin/addons/xterm/PreserveStateAddon.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * XTerm Console Addon 4 | * %% 5 | * Copyright (C) 2020 - 2025 Flowing Code 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.flowingcode.vaadin.addons.xterm; 21 | 22 | import com.flowingcode.vaadin.addons.xterm.utils.StateMemoizer; 23 | import lombok.experimental.Delegate; 24 | 25 | import java.util.Objects; 26 | import java.util.concurrent.CompletableFuture; 27 | 28 | /** 29 | * Add-on which preserves the client-side state when the component is removed 30 | * from the UI then reattached later on. The problem here is that when the 31 | * {@link XTerm} server-side component is detached from the UI, the xterm.js client-side 32 | * component is destroyed along with its state. When the {@link XTerm} component 33 | * is later re-attached to the UI, a new unconfigured xterm.js is created on the 34 | * client-side. 35 | *
36 | * To use this addon, simply create the addon then make sure to call all {@link ITerminal} 37 | * and {@link ITerminalOptions} methods via this addon: 38 | *39 | * final XTerm xterm = new XTerm(); 40 | * final PreserveStateAddon addon = new PreserveStateAddon(xterm); 41 | * addon.writeln("Hello!"); 42 | * addon.setPrompt("$ "); 43 | * addon.writePrompt(); 44 | *45 | */ 46 | public class PreserveStateAddon extends TerminalAddon 47 | implements ITerminal, ITerminalOptions { 48 | 49 | /** 50 | * The xterm to delegate all calls to. 51 | */ 52 | private final XTerm xterm; 53 | /** 54 | * Remembers everything that was printed into the xterm and what the user typed in. 55 | */ 56 | private final StringBuilder scrollbackBuffer = new StringBuilder(); 57 | /** 58 | * All commands are properly applied before the first attach; they're just 59 | * not preserved after subsequent detach/attach. 60 | */ 61 | private boolean wasDetachedOnce = false; 62 | /** 63 | * Used to re-apply all options to the xterm after it has been reattached back to the UI. 64 | * Otherwise, the options would not be applied to the client-side xterm.js component. 65 | */ 66 | private final StateMemoizer optionsMemoizer; 67 | 68 | /** 69 | * Delegate all option setters through this delegate, which is the {@link #optionsMemoizer} proxy. 70 | * That will allow us to re-apply the settings when the xterm is re-attached. 71 | * 72 | * For example, calling {@link ITerminalOptions#setBellSound(String)} 73 | * on this addon will pass through the call to this delegate, which in turn passes 74 | * the call to {@link #optionsMemoizer} which remembers the call and passes 75 | * it to {@link #xterm}. 76 | * 77 | * After the xterm.js is re-attached, we simply call {@link StateMemoizer#apply()} 78 | * to apply all changed setters again to xterm.js, to make sure xterm.js is 79 | * configured. 80 | */ 81 | @Delegate 82 | private final ITerminalOptions optionsDelegate; 83 | 84 | public PreserveStateAddon(XTerm xterm) { 85 | super(xterm); 86 | this.xterm = Objects.requireNonNull(xterm); 87 | optionsMemoizer = new StateMemoizer(xterm, ITerminalOptions.class); 88 | optionsDelegate = (ITerminalOptions) optionsMemoizer.getProxy(); 89 | xterm.addAttachListener(e -> { 90 | if (wasDetachedOnce) { 91 | optionsMemoizer.apply(); 92 | xterm.write(scrollbackBuffer.toString()); 93 | xterm.writePrompt(); 94 | } 95 | }); 96 | xterm.addDetachListener(e -> wasDetachedOnce = true); 97 | xterm.addLineListener(e -> { 98 | // add the prompt to the scrollback buffer 99 | scrollbackBuffer.append(xterm.getPrompt()); 100 | // also make sure that any user input ends up in the scrollback buffer. 101 | scrollbackBuffer.append(e.getLine()); 102 | scrollbackBuffer.append('\n'); 103 | }); 104 | } 105 | 106 | @Override 107 | public void blur() { 108 | xterm.blur(); 109 | } 110 | 111 | @Override 112 | public void focus() { 113 | xterm.focus(); 114 | } 115 | 116 | @Override 117 | public CompletableFuture
29 | * Concrete add-on implementations should subclass this class to provide specific features. Each 30 | * add-on is tightly coupled with a specific {@code XTermBase} instance, allowing it to interact 31 | * with and enhance that terminal. 32 | *
33 | * 34 | * @author Javier Godoy / Flowing Code S.A. 35 | */ 36 | @SuppressWarnings("serial") 37 | public abstract class TerminalAddon implements Serializable { 38 | 39 | /** 40 | * Constructs a new {@code TerminalAddon} and associates it with the provided {@link XTermBase} 41 | * instance. 42 | * 43 | * @param xterm the {@code XTermBase} instance to which this add-on will be attached 44 | * @throws NullPointerException if the provided {@code xterm} is {@code null} 45 | */ 46 | protected TerminalAddon(XTermBase xterm) { 47 | Objects.requireNonNull(xterm); 48 | xterm.registerServerSideAddon(this); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/flowingcode/vaadin/addons/xterm/TerminalHistory.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * XTerm Console Addon 4 | * %% 5 | * Copyright (C) 2020 - 2023 Flowing Code 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.flowingcode.vaadin.addons.xterm; 21 | 22 | import com.vaadin.flow.component.ComponentUtil; 23 | import com.vaadin.flow.component.Key; 24 | import com.vaadin.flow.shared.Registration; 25 | import java.io.IOException; 26 | import java.io.ObjectInputStream; 27 | import java.io.Serializable; 28 | import java.util.ArrayList; 29 | import java.util.Collections; 30 | import java.util.Iterator; 31 | import java.util.LinkedList; 32 | import java.util.List; 33 | import java.util.ListIterator; 34 | import java.util.Objects; 35 | import java.util.Optional; 36 | import java.util.function.Predicate; 37 | 38 | /** Manages a command history buffer for {@link XTerm}. */ 39 | @SuppressWarnings("serial") 40 | public class TerminalHistory implements Serializable { 41 | 42 | private LinkedList291 | * Example usage: 292 | *
293 | * 294 | *{@code 295 | * MySpecificAddon addon = terminal.getAddon(MySpecificAddon.class); 296 | * if (addon != null) { 297 | * addon.doSomethingSpecific(); 298 | * } 299 | * }300 | * 301 | * @param
The tests use Chrome driver (see pom.xml for integration-tests profile) to run integration 35 | * tests on a headless Chrome. If a property {@code test.use .hub} is set to true, {@code 36 | * AbstractViewTest} will assume that the TestBench test is running in a CI environment. In order to 37 | * keep the this class light, it makes certain assumptions about the CI environment (such as 38 | * available environment variables). It is not advisable to use this class as a base class for you 39 | * own TestBench tests. 40 | * 41 | *
To learn more about TestBench, visit Vaadin TestBench.
43 | */
44 | public abstract class AbstractViewTest extends ParallelTest {
45 | private static final int SERVER_PORT = 8080;
46 |
47 | private final String route;
48 |
49 | @Rule public ScreenshotOnFailureRule rule = new ScreenshotOnFailureRule(this, true);
50 |
51 | public AbstractViewTest() {
52 | this("");
53 | }
54 |
55 | protected AbstractViewTest(String route) {
56 | this.route = route;
57 | }
58 |
59 | @BeforeClass
60 | public static void setupClass() {
61 | WebDriverManager.chromedriver().setup();
62 | }
63 |
64 | @Override
65 | @Before
66 | public void setup() throws Exception {
67 | if (isUsingHub()) {
68 | super.setup();
69 | } else {
70 | setDriver(TestBench.createDriver(new ChromeDriver()));
71 | }
72 | getDriver().get(getURL(route));
73 | }
74 |
75 | /**
76 | * Returns deployment host name concatenated with route.
77 | *
78 | * @return URL to route
79 | */
80 | private static String getURL(String route) {
81 | return String.format("http://%s:%d/%s", getDeploymentHostname(), SERVER_PORT, route);
82 | }
83 |
84 | /** Property set to true when running on a test hub. */
85 | private static final String USE_HUB_PROPERTY = "test.use.hub";
86 |
87 | /**
88 | * Returns whether we are using a test hub. This means that the starter is running tests in
89 | * Vaadin's CI environment, and uses TestBench to connect to the testing hub.
90 | *
91 | * @return whether we are using a test hub
92 | */
93 | private static boolean isUsingHub() {
94 | return Boolean.TRUE.toString().equals(System.getProperty(USE_HUB_PROPERTY));
95 | }
96 |
97 | /**
98 | * If running on CI, get the host name from environment variable HOSTNAME
99 | *
100 | * @return the host name
101 | */
102 | private static String getDeploymentHostname() {
103 | return isUsingHub() ? System.getenv("HOSTNAME") : "localhost";
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/ClipboardFeatureIT.java:
--------------------------------------------------------------------------------
1 | /*-
2 | * #%L
3 | * XTerm Console Addon
4 | * %%
5 | * Copyright (C) 2020 - 2023 Flowing Code
6 | * %%
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * #L%
19 | */
20 | package com.flowingcode.vaadin.addons.xterm.integration;
21 |
22 | import static org.hamcrest.Matchers.is;
23 | import static org.junit.Assert.assertThat;
24 | import java.util.List;
25 | import org.junit.Test;
26 | import org.openqa.selenium.interactions.Actions;
27 |
28 | public class ClipboardFeatureIT extends AbstractViewTest {
29 |
30 | private static int[] intArray(Object obj) {
31 | return ((List>) obj).stream().mapToInt(i -> ((Long) i).intValue()).toArray();
32 | }
33 |
34 | @Test
35 | public void testFeature() {
36 | XTermElement term = $(XTermElement.class).first();
37 |
38 | term.setPrompt(null);
39 | term.write("\\x1bcTEXT");
40 |
41 | int[] size =
42 | intArray(
43 | term
44 | .executeScript(
45 | "return [this.clientWidth, this.clientHeight]"));
46 |
47 | term.setUseSystemClipboard(false);
48 |
49 | new Actions(driver)
50 | .moveToElement(term, -size[0] / 2, -size[1] / 2 + 10)
51 | .clickAndHold()
52 | .moveByOffset(100, 0)
53 | .release()
54 | .perform();
55 |
56 | new Actions(driver).contextClick().perform();
57 | assertThat(term.currentLine(), is("TEXTTEXT"));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/ConsoleFeatureIT.java:
--------------------------------------------------------------------------------
1 | /*-
2 | * #%L
3 | * XTerm Console Addon
4 | * %%
5 | * Copyright (C) 2020 - 2023 Flowing Code
6 | * %%
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * #L%
19 | */
20 | package com.flowingcode.vaadin.addons.xterm.integration;
21 |
22 | import static com.flowingcode.vaadin.addons.xterm.integration.XTermTestUtils.makeFullLine;
23 | import static org.hamcrest.Matchers.is;
24 | import static org.hamcrest.Matchers.isEmptyString;
25 | import static org.junit.Assert.assertThat;
26 | import org.junit.Test;
27 | import org.openqa.selenium.Keys;
28 |
29 | public class ConsoleFeatureIT extends AbstractViewTest {
30 |
31 | @Test
32 | public void testWriteWrappedLine() throws InterruptedException {
33 | XTermElement term = $(XTermElement.class).first();
34 | Position home = term.cursorPosition();
35 | String text = makeFullLine(term, true) + "Z";
36 | term.sendKeys(text);
37 | assertThat(term.currentLine(), is(text));
38 | assertThat(term.cursorPosition(), is(new Position(1, home.y + 1)));
39 | assertThat(term.lineAtOffset(0), is("Z"));
40 | }
41 |
42 | @Test
43 | public void testFeature() throws InterruptedException {
44 | // navigation with keyboard
45 | XTermElement term = $(XTermElement.class).first();
46 |
47 | Position pos = term.cursorPosition();
48 |
49 | term.sendKeys("HELLO");
50 | assertThat(term.currentLine(), is("HELLO"));
51 | assertThat(term.cursorPosition(), is(pos.plus(5, 0)));
52 |
53 | term.sendKeys(Keys.ARROW_LEFT);
54 | assertThat(term.cursorPosition(), is(pos.plus(4, 0)));
55 |
56 | term.sendKeys(Keys.ARROW_RIGHT);
57 | assertThat(term.cursorPosition(), is(pos.plus(5, 0)));
58 |
59 | term.sendKeys(Keys.HOME);
60 | assertThat(term.cursorPosition(), is(pos.plus(0, 0)));
61 |
62 | term.sendKeys(Keys.END);
63 | assertThat(term.cursorPosition(), is(pos.plus(5, 0)));
64 |
65 | term.sendKeys(Keys.BACK_SPACE);
66 | assertThat(term.currentLine(), is("HELL"));
67 | assertThat(term.cursorPosition(), is(pos.plus(4, 0)));
68 |
69 | term.sendKeys(Keys.HOME, Keys.DELETE);
70 | assertThat(term.currentLine(), is("ELL"));
71 | assertThat(term.cursorPosition(), is(pos.plus(0, 0)));
72 |
73 | term.sendKeys("A");
74 | assertThat(term.currentLine(), is("AELL"));
75 |
76 | term.sendKeys(Keys.INSERT, "B");
77 | assertThat(term.currentLine(), is("ABLL"));
78 |
79 | term.sendKeys(Keys.INSERT, "C");
80 | assertThat(term.currentLine(), is("ABCLL"));
81 |
82 |
83 | // long line
84 | term.sendKeys(Keys.ENTER);
85 | assertThat(term.currentLine(), is(""));
86 | assertThat(term.cursorPosition(), is(pos.advance(0, 1)));
87 |
88 | String prompt = term.lineAtOffset(0);
89 | String text = makeFullLine(term, true);
90 | int cols = text.length();
91 |
92 | term.sendKeys(text);
93 | term.sendKeys(Keys.HOME);
94 | assertThat(term.cursorPosition(), is(pos));
95 | assertThat(term.currentLine(), is(text));
96 |
97 | term.sendKeys("A");
98 | assertThat(term.lineAtOffset(0), is(prompt + "A" + text.substring(0, cols - 1)));
99 | assertThat(term.lineAtOffset(1), is(text.substring(cols - 1)));
100 |
101 | term.sendKeys("B");
102 | assertThat(term.lineAtOffset(0), is(prompt + "AB" + text.substring(0, cols - 2)));
103 | assertThat(term.lineAtOffset(1), is(text.substring(cols - 2)));
104 |
105 | term.sendKeys(Keys.END);
106 | assertThat(term.cursorPosition(), is(new Position(2, pos.y + 1)));
107 |
108 | term.sendKeys(Keys.HOME);
109 | assertThat(term.cursorPosition(), is(pos));
110 |
111 | term.sendKeys(Keys.DELETE);
112 | assertThat(term.lineAtOffset(0), is(prompt + "B" + text.substring(0, cols - 1)));
113 | assertThat(term.lineAtOffset(1), is(text.substring(cols - 1)));
114 |
115 | term.sendKeys(Keys.DELETE);
116 | assertThat(term.lineAtOffset(0), is(prompt + text));
117 | assertThat(term.lineAtOffset(1), isEmptyString());
118 | }
119 |
120 | @Test
121 | public void testCsiSequences() throws InterruptedException {
122 | // CSI sequences that implement navigation with keyboard
123 | XTermElement term = $(XTermElement.class).first();
124 | Position pos = term.cursorPosition();
125 |
126 | term.sendKeys("HELLO");
127 | assertThat(term.currentLine(), is("HELLO"));
128 |
129 | // Cursor Home Logical Line
130 | term.write("\u001b[dx,dy
.*/
33 | public Position advance(int dx, int dy) {
34 | this.x+=dx;
35 | this.y+=dy;
36 | return this;
37 | }
38 |
39 | /**Return a new position that is equal to this position plus dx,dy
.*/
40 | public Position plus(int dx, int dy) {
41 | return new Position(this.x+dx, this.y+dy);
42 | }
43 |
44 | /**Return a new position that is equal to this position adjusted by columnWidth
.*/
45 | public Position adjust(int columnWidth) {
46 | int pos = x + y*columnWidth;
47 | return new Position(pos%columnWidth, pos/columnWidth);
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/SelectionFeatureIT.java:
--------------------------------------------------------------------------------
1 | /*-
2 | * #%L
3 | * XTerm Console Addon
4 | * %%
5 | * Copyright (C) 2020 - 2023 Flowing Code
6 | * %%
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * #L%
19 | */
20 | package com.flowingcode.vaadin.addons.xterm.integration;
21 |
22 | import static com.flowingcode.vaadin.addons.xterm.integration.XTermTestUtils.makeFullLine;
23 | import static org.hamcrest.Matchers.is;
24 | import static org.hamcrest.Matchers.isEmptyString;
25 | import static org.junit.Assert.assertThat;
26 | import org.junit.Test;
27 | import org.openqa.selenium.Keys;
28 |
29 | public class SelectionFeatureIT extends AbstractViewTest {
30 |
31 | @Test
32 | public void testSelectionFeature() throws InterruptedException {
33 | XTermElement term = $(XTermElement.class).first();
34 | Position pos;
35 |
36 | term.write("abcd");
37 | pos = term.cursorPosition();
38 |
39 | // select left, backspace
40 | term.sendKeys(Keys.SHIFT, Keys.ARROW_LEFT);
41 | assertThat(term.getSelection(), is("d"));
42 | assertThat(term.cursorPosition(), is(pos));
43 | term.sendKeys(Keys.BACK_SPACE);
44 | assertThat(term.currentLine(), is("abc"));
45 | assertThat(term.cursorPosition(), is(pos.advance(-1, 0)));
46 |
47 | // select left, delete
48 | term.sendKeys(Keys.SHIFT, Keys.ARROW_LEFT);
49 | assertThat(term.getSelection(), is("c"));
50 | assertThat(term.cursorPosition(), is(pos));
51 | term.sendKeys(Keys.DELETE);
52 | assertThat(term.currentLine(), is("ab"));
53 | assertThat(term.cursorPosition(), is(pos.advance(-1, 0)));
54 |
55 | // select right, delete
56 | term.sendKeys(Keys.HOME);
57 | pos = term.cursorPosition();
58 | term.sendKeys(Keys.SHIFT, Keys.ARROW_RIGHT);
59 | assertThat(term.getSelection(), is("a"));
60 | assertThat(term.cursorPosition(), is(pos));
61 | term.sendKeys(Keys.DELETE);
62 | assertThat(term.currentLine(), is("b"));
63 | assertThat(term.cursorPosition(), is(pos));
64 |
65 | // select right, backspace
66 | term.sendKeys(Keys.SHIFT, Keys.ARROW_RIGHT);
67 | assertThat(term.getSelection(), is("b"));
68 | assertThat(term.cursorPosition(), is(pos));
69 | term.sendKeys(Keys.BACK_SPACE);
70 | assertThat(term.currentLine(), isEmptyString());
71 | assertThat(term.cursorPosition(), is(pos));
72 |
73 | term.write("abcd");
74 | pos = term.cursorPosition();
75 |
76 | // select to home, delete
77 | term.sendKeys(Keys.SHIFT, Keys.HOME);
78 | assertThat(term.getSelection(), is("abcd"));
79 | assertThat(term.cursorPosition(), is(pos));
80 | term.sendKeys(Keys.DELETE);
81 | assertThat(term.currentLine(), isEmptyString());
82 | assertThat(term.cursorPosition(), is(pos.advance(-4, 0)));
83 |
84 | // select to end, delete
85 | term.write("abcd");
86 | term.sendKeys(Keys.HOME);
87 | pos = term.cursorPosition();
88 | term.sendKeys(Keys.SHIFT, Keys.END);
89 | assertThat(term.getSelection(), is("abcd"));
90 | assertThat(term.cursorPosition(), is(pos));
91 | term.sendKeys(Keys.DELETE);
92 | assertThat(term.currentLine(), isEmptyString());
93 | assertThat(term.cursorPosition(), is(pos));
94 |
95 | String text = makeFullLine(term, true) + makeFullLine(term, false) + makeFullLine(term, false);
96 |
97 | // select to home, delete (wrapping)
98 | term.write(text);
99 | assertThat(term.currentLine(), is(text));
100 | term.sendKeys(Keys.SHIFT, Keys.HOME);
101 | assertThat(term.getSelection(), is(text));
102 | term.sendKeys(Keys.DELETE);
103 | assertThat(term.currentLine(), isEmptyString());
104 |
105 | // select to end, delete (wrapping)
106 | term.write(text);
107 | assertThat(term.currentLine(), is(text));
108 | term.sendKeys(Keys.HOME);
109 | term.sendKeys(Keys.SHIFT, Keys.END);
110 | assertThat(term.getSelection(), is(text));
111 | term.sendKeys(Keys.DELETE);
112 | assertThat(term.currentLine(), isEmptyString());
113 |
114 | }
115 |
116 | }
--------------------------------------------------------------------------------
/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/TerminalHistoryIT.java:
--------------------------------------------------------------------------------
1 | /*-
2 | * #%L
3 | * XTerm Console Addon
4 | * %%
5 | * Copyright (C) 2020 - 2023 Flowing Code
6 | * %%
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * #L%
19 | */
20 | package com.flowingcode.vaadin.addons.xterm.integration;
21 |
22 | import static org.hamcrest.Matchers.is;
23 | import static org.hamcrest.Matchers.isEmptyString;
24 | import static org.junit.Assert.assertThat;
25 | import org.junit.Test;
26 | import org.openqa.selenium.Keys;
27 |
28 | public class TerminalHistoryIT extends AbstractViewTest {
29 |
30 | @Test
31 | public void testArrowKeys() {
32 | XTermElement term = $(XTermElement.class).first();
33 |
34 | Position pos = term.cursorPosition();
35 | term.sendKeys("foo1\nfoo2\n");
36 |
37 | assertThat(term.cursorPosition(), is(pos.advance(0, 2)));
38 | assertThat(term.currentLine(), isEmptyString());
39 |
40 | term.sendKeys(Keys.ARROW_UP);
41 | assertThat(term.currentLine(), is("foo2"));
42 |
43 | term.sendKeys(Keys.ARROW_UP);
44 | assertThat(term.currentLine(), is("foo1"));
45 |
46 | term.sendKeys(Keys.ARROW_UP);
47 | assertThat(term.currentLine(), is("foo1"));
48 |
49 | term.sendKeys(Keys.ARROW_DOWN);
50 | assertThat(term.currentLine(), is("foo2"));
51 |
52 | term.sendKeys(Keys.ARROW_DOWN);
53 | assertThat(term.currentLine(), isEmptyString());
54 | }
55 |
56 | @Test
57 | public void testArrowKeysAndRestore() {
58 | XTermElement term = $(XTermElement.class).first();
59 |
60 | Position pos = term.cursorPosition();
61 | term.sendKeys("foo1\nfoo2\n");
62 |
63 | assertThat(term.cursorPosition(), is(pos.advance(0, 2)));
64 | assertThat(term.currentLine(), isEmptyString());
65 |
66 | term.sendKeys("bar");
67 | term.sendKeys(Keys.ARROW_UP);
68 | assertThat(term.currentLine(), is("foo2"));
69 |
70 | term.sendKeys(Keys.ARROW_DOWN);
71 | assertThat(term.currentLine(), is("bar"));
72 | }
73 |
74 | @Test
75 | public void testArrowUpAfterRunningLastCommandFromHistory() {
76 | XTermElement term = $(XTermElement.class).first();
77 |
78 | term.sendKeys("foo1\n");
79 | term.sendKeys("foo2\n");
80 |
81 | assertThat(term.currentLine(), isEmptyString());
82 |
83 | term.sendKeys(Keys.ARROW_UP);
84 | assertThat(term.currentLine(), is("foo2"));
85 | term.sendKeys("\n");
86 |
87 | term.sendKeys(Keys.ARROW_UP);
88 | assertThat(term.currentLine(), is("foo2"));
89 |
90 | term.sendKeys(Keys.ARROW_UP);
91 | assertThat(term.currentLine(), is("foo1"));
92 | }
93 |
94 | @Test
95 | public void testArrowUpAfterRunningEmptyCommand() {
96 | XTermElement term = $(XTermElement.class).first();
97 |
98 | term.sendKeys("foo1\n");
99 | term.sendKeys("foo2\n");
100 |
101 | assertThat(term.currentLine(), isEmptyString());
102 |
103 | term.sendKeys(Keys.ARROW_UP);
104 | assertThat(term.currentLine(), is("foo2"));
105 | term.sendKeys("\u0008\u0008\u0008\u0008"); // 4 backspaces
106 | assertThat(term.currentLine(), isEmptyString());
107 | term.sendKeys("\n");
108 |
109 | term.sendKeys(Keys.ARROW_UP);
110 | // The position in the history should be back at the end
111 | assertThat(term.currentLine(), is("foo2"));
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/XTermElement.java:
--------------------------------------------------------------------------------
1 | /*-
2 | * #%L
3 | * XTerm Console Addon
4 | * %%
5 | * Copyright (C) 2020 - 2023 Flowing Code
6 | * %%
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * #L%
19 | */
20 | package com.flowingcode.vaadin.addons.xterm.integration;
21 |
22 | import com.vaadin.testbench.TestBenchElement;
23 | import com.vaadin.testbench.commands.TestBenchCommandExecutor;
24 | import com.vaadin.testbench.elementsbase.Element;
25 | import java.util.Arrays;
26 | import java.util.List;
27 | import org.openqa.selenium.WebElement;
28 |
29 | /**
30 | * A TestBench element representing a <fc-xterm>
element.
31 | */
32 | @Element("fc-xterm")
33 | public class XTermElement extends TestBenchElement {
34 |
35 | private WebElement input;
36 |
37 | @Override
38 | protected void init(WebElement element, TestBenchCommandExecutor commandExecutor) {
39 | super.init(element, commandExecutor);
40 | input = (WebElement) waitUntil(
41 | driver -> executeScript("return this.terminal.textarea"));
42 | }
43 | public void write(String text) {
44 | executeScript(String.format("this.terminal.write('%s')", text));
45 | }
46 |
47 | public int getColumnWidth() {
48 | return ((Long) executeScript("return this.terminal.cols")).intValue();
49 | }
50 |
51 | final String currentLine() {
52 | return getPropertyString("currentLine");
53 | }
54 |
55 | public String getSelection() {
56 | return (String) executeScript("return this.terminal.getSelection()");
57 | }
58 |
59 | public String lineAtOffset(int offset) {
60 | return ((String) executeScript(
61 | "buffer=this.terminal._core._inputHandler._bufferService.buffer;"
62 | + "line=buffer.lines.get(buffer.ybase+buffer.y+(arguments[0]));"
63 | + "return line.translateToString().substr(0,line.getTrimmedLength());",
64 | offset));
65 | }
66 |
67 | public Position cursorPosition() {
68 | int[] pos = intArray(executeScript(
69 | "buffer=this.terminal.buffer.active; return [buffer.cursorX, buffer.cursorY]",
70 | this));
71 | return new Position(pos[0], pos[1]);
72 | }
73 |
74 | private static int[] intArray(Object obj) {
75 | return ((List>) obj).stream().mapToInt(i -> ((Long) i).intValue()).toArray();
76 | }
77 |
78 | public void setUseSystemClipboard(boolean value) {
79 | setProperty("useSystemClipboard", value);
80 | }
81 |
82 | public void setPrompt(String value) {
83 | setProperty("prompt", value);
84 | }
85 |
86 | @Override
87 | public void sendKeys(CharSequence... keysToSend) {
88 | input.sendKeys(keysToSend);
89 | }
90 |
91 | @Override
92 | public Object executeScript(String script, Object... arguments) {
93 | script = String.format(
94 | "return function(arguments){arguments.pop(); %s}.bind(arguments[arguments.length-1])([].slice.call(arguments))",
95 | script);
96 | arguments = Arrays.copyOf(arguments, arguments.length + 1);
97 | arguments[arguments.length - 1] = this;
98 | return getCommandExecutor().executeScript(script, arguments);
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/test/java/com/flowingcode/vaadin/addons/xterm/integration/XTermIT.java:
--------------------------------------------------------------------------------
1 | /*-
2 | * #%L
3 | * XTerm Console Addon
4 | * %%
5 | * Copyright (C) 2020 - 2023 Flowing Code
6 | * %%
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * #L%
19 | */
20 | package com.flowingcode.vaadin.addons.xterm.integration;
21 |
22 | import static org.hamcrest.Matchers.is;
23 | import static org.hamcrest.Matchers.not;
24 | import static org.hamcrest.Matchers.notNullValue;
25 | import static org.junit.Assert.assertThat;
26 | import com.vaadin.testbench.TestBenchElement;
27 | import org.hamcrest.Description;
28 | import org.hamcrest.Matcher;
29 | import org.hamcrest.TypeSafeDiagnosingMatcher;
30 | import org.junit.Test;
31 | import org.openqa.selenium.By;
32 |
33 | public class XTermIT extends AbstractViewTest {
34 |
35 | private Matcher