├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
└── main
├── java
├── META-INF
│ └── MANIFEST.MF
└── cn
│ └── objectspace
│ ├── WebSSHStarter.java
│ └── webssh
│ ├── config
│ └── WebSSHWebSocketConfig.java
│ ├── constant
│ └── ConstantPool.java
│ ├── controller
│ └── RouterController.java
│ ├── interceptor
│ └── WebSocketInterceptor.java
│ ├── pojo
│ ├── SSHConnectInfo.java
│ └── WebSSHData.java
│ ├── service
│ ├── WebSSHService.java
│ └── impl
│ │ └── WebSSHServiceImpl.java
│ └── websocket
│ └── WebSSHWebSocketHandler.java
└── resources
├── Dockerfile
├── config
└── application.yml
└── static
├── css
└── xterm.css
├── js
├── jquery-3.4.1.min.js
├── webssh.js
└── xterm.js
├── page
└── webssh.html
└── xterm
├── .eslintrc.json
├── LICENSE
├── README.md
├── css
└── xterm.css
├── lib
├── xterm.js
└── xterm.js.map
├── package.json
├── src
├── browser
│ ├── AccessibilityManager.ts
│ ├── Clipboard.ts
│ ├── Color.ts
│ ├── ColorContrastCache.ts
│ ├── ColorManager.ts
│ ├── Dom.ts
│ ├── Lifecycle.ts
│ ├── Linkifier.ts
│ ├── Linkifier2.ts
│ ├── LocalizableStrings.ts
│ ├── MouseZoneManager.ts
│ ├── RenderDebouncer.ts
│ ├── ScreenDprMonitor.ts
│ ├── Terminal.ts
│ ├── Types.d.ts
│ ├── Viewport.ts
│ ├── input
│ │ ├── CompositionHelper.ts
│ │ ├── Mouse.ts
│ │ └── MoveToCell.ts
│ ├── public
│ │ ├── AddonManager.ts
│ │ └── Terminal.ts
│ ├── renderer
│ │ ├── BaseRenderLayer.ts
│ │ ├── CharacterJoinerRegistry.ts
│ │ ├── CursorRenderLayer.ts
│ │ ├── GridCache.ts
│ │ ├── LinkRenderLayer.ts
│ │ ├── Renderer.ts
│ │ ├── RendererUtils.ts
│ │ ├── SelectionRenderLayer.ts
│ │ ├── TextRenderLayer.ts
│ │ ├── Types.d.ts
│ │ ├── atlas
│ │ │ ├── BaseCharAtlas.ts
│ │ │ ├── CharAtlasCache.ts
│ │ │ ├── CharAtlasUtils.ts
│ │ │ ├── Constants.ts
│ │ │ ├── DynamicCharAtlas.ts
│ │ │ ├── LRUMap.ts
│ │ │ └── Types.d.ts
│ │ └── dom
│ │ │ ├── DomRenderer.ts
│ │ │ └── DomRendererRowFactory.ts
│ ├── selection
│ │ ├── SelectionModel.ts
│ │ └── Types.d.ts
│ ├── services
│ │ ├── CharSizeService.ts
│ │ ├── CoreBrowserService.ts
│ │ ├── MouseService.ts
│ │ ├── RenderService.ts
│ │ ├── SelectionService.ts
│ │ ├── Services.ts
│ │ └── SoundService.ts
│ └── tsconfig.json
├── common
│ ├── CircularList.ts
│ ├── Clone.ts
│ ├── CoreTerminal.ts
│ ├── EventEmitter.ts
│ ├── InputHandler.ts
│ ├── Lifecycle.ts
│ ├── Platform.ts
│ ├── TypedArrayUtils.ts
│ ├── Types.d.ts
│ ├── WindowsMode.ts
│ ├── buffer
│ │ ├── AttributeData.ts
│ │ ├── Buffer.ts
│ │ ├── BufferLine.ts
│ │ ├── BufferReflow.ts
│ │ ├── BufferSet.ts
│ │ ├── CellData.ts
│ │ ├── Constants.ts
│ │ ├── Marker.ts
│ │ └── Types.d.ts
│ ├── data
│ │ ├── Charsets.ts
│ │ └── EscapeSequences.ts
│ ├── input
│ │ ├── Keyboard.ts
│ │ ├── TextDecoder.ts
│ │ ├── UnicodeV6.ts
│ │ └── WriteBuffer.ts
│ ├── parser
│ │ ├── Constants.ts
│ │ ├── DcsParser.ts
│ │ ├── EscapeSequenceParser.ts
│ │ ├── OscParser.ts
│ │ ├── Params.ts
│ │ └── Types.d.ts
│ ├── services
│ │ ├── BufferService.ts
│ │ ├── CharsetService.ts
│ │ ├── CoreMouseService.ts
│ │ ├── CoreService.ts
│ │ ├── DirtyRowService.ts
│ │ ├── InstantiationService.ts
│ │ ├── LogService.ts
│ │ ├── OptionsService.ts
│ │ ├── ServiceRegistry.ts
│ │ ├── Services.ts
│ │ └── UnicodeService.ts
│ └── tsconfig.json
├── tsconfig-base.json
└── tsconfig-library-base.json
├── tsconfig.all.json
└── typings
└── xterm.d.ts
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.ts linguist-language=java;
2 | *.js linguist-language=java;
3 | *.html linguist-language=java;
4 | *.css linguist-language=java;
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
25 | .idea/
26 | target/
27 | *.iml
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 基于[NoCortY](https://github.com/NoCortY)的项目[WebSSH](https://github.com/NoCortY/WebSSH)修改。
2 | 1. xterm版本升至4.X
3 | 2. terminal自适应宽高
4 | 3. 心跳检查
5 | 4. 断开后自动重新连接
6 |
7 | **效果**
8 |
9 | 自适应宽高
10 |
11 | 
12 |
13 | 心跳检查
14 |
15 | 
16 |
17 | 断开自动重连
18 |
19 | 
20 |
21 |
22 | 参考资料:
23 | [xterm.js + vue + websocket实现终端功能(xterm 3.x+xterm 4.x)](https://blog.csdn.net/weixin_38318244/article/details/103908129)
24 | [理解WebSocket心跳及重连机制(五)](https://www.cnblogs.com/tugenhua0707/p/8648044.html)
25 |
26 | ## 2020-3-13
27 |
28 | 最近有些事挺忙的,可能暂时稍微顺延一下更新的日程(大概需要在4月1号之后),但是一定会继续维护下去的,望多多理解。
29 |
30 | ## 启动
31 |
32 | 项目导入IDEA后可以直接进行运行,没有任何外部依赖~~
33 |
34 | **本项目的Blog**:[使用纯Java实现一个WebSSH项目](https://blog.objectspace.cn/2020/03/10/%E4%BD%BF%E7%94%A8%E7%BA%AFJava%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAWebSSH%E9%A1%B9%E7%9B%AE/)
35 |
36 | **注意**:
37 |
38 | 由于前端代码中没有指定终端的信息
39 |
40 | 所以需要各位自己输入这些信息,位置在webssh.html中
41 |
42 | ```javascript
43 | openTerminal( {
44 | /*operate:'connect',
45 | host: '',//IP
46 | port: '',//端口号
47 | username: '',//用户名
48 | password: ''//密码*/
49 | });
50 | ```
51 |
52 | ## 运行展示
53 |
54 | - ### 连接
55 |
56 | 
57 |
58 | - ### 连接成功
59 |
60 | 
61 |
62 | - ### 命令操作
63 |
64 | ls命令:
65 |
66 | 
67 |
68 | vim编辑器:
69 |
70 | 
71 |
72 | top命令:
73 |
74 | 
75 |
76 | ## 写在最后
77 | 欢迎各位大佬给我提issue,感谢!
78 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | cn.objectspace
8 | WebSSH
9 | 1.0-SNAPSHOT
10 | jar
11 |
12 | org.springframework.boot
13 | spring-boot-starter-parent
14 | 2.1.7.RELEASE
15 |
16 |
17 |
18 |
19 |
20 | org.springframework.boot
21 | spring-boot-starter-web
22 |
23 |
24 |
25 | com.jcraft
26 | jsch
27 | 0.1.54
28 |
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-websocket
33 |
34 |
35 |
36 | commons-io
37 | commons-io
38 | 1.4
39 |
40 |
41 | commons-fileupload
42 | commons-fileupload
43 | 1.3.3
44 |
45 |
46 |
47 |
48 | WebSSH
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-maven-plugin
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/main/java/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Main-Class: cn.objectspace.WebSSHStarter
3 |
4 |
--------------------------------------------------------------------------------
/src/main/java/cn/objectspace/WebSSHStarter.java:
--------------------------------------------------------------------------------
1 | package cn.objectspace;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class WebSSHStarter {
8 | public static void main(String[] args) {
9 | SpringApplication.run(WebSSHStarter.class);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/cn/objectspace/webssh/config/WebSSHWebSocketConfig.java:
--------------------------------------------------------------------------------
1 | package cn.objectspace.webssh.config;
2 |
3 | import cn.objectspace.webssh.interceptor.WebSocketInterceptor;
4 | import cn.objectspace.webssh.websocket.WebSSHWebSocketHandler;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.web.socket.config.annotation.EnableWebSocket;
8 | import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
9 | import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
10 |
11 | /**
12 | * @Description: websocket配置
13 | * @Author: NoCortY
14 | * @Date: 2020/3/8
15 | */
16 | @Configuration
17 | @EnableWebSocket
18 | public class WebSSHWebSocketConfig implements WebSocketConfigurer{
19 | @Autowired
20 | WebSSHWebSocketHandler webSSHWebSocketHandler;
21 | @Override
22 | public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
23 | //socket通道
24 | //指定处理器和路径
25 | webSocketHandlerRegistry.addHandler(webSSHWebSocketHandler, "/webssh")
26 | .addInterceptors(new WebSocketInterceptor())
27 | .setAllowedOrigins("*");
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/cn/objectspace/webssh/constant/ConstantPool.java:
--------------------------------------------------------------------------------
1 | package cn.objectspace.webssh.constant;
2 |
3 | /**
4 | * @Description: 常量池
5 | * @Author: NoCortY
6 | * @Date: 2020/3/8
7 | */
8 | public class ConstantPool {
9 | /**
10 | * 随机生成uuid的key名
11 | */
12 | public static final String USER_UUID_KEY = "user_uuid";
13 | /**
14 | * 发送指令:连接
15 | */
16 | public static final String WEBSSH_OPERATE_CONNECT = "connect";
17 | /**
18 | * 发送指令:命令
19 | */
20 | public static final String WEBSSH_OPERATE_COMMAND = "command";
21 | /**
22 | * 发送指令:心跳
23 | */
24 | public static final String WEBSSH_OPERATE_HEARTBEAT = "heartbeat";
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/cn/objectspace/webssh/controller/RouterController.java:
--------------------------------------------------------------------------------
1 | package cn.objectspace.webssh.controller;
2 |
3 | import org.springframework.stereotype.Controller;
4 | import org.springframework.web.bind.annotation.RequestMapping;
5 |
6 | @Controller
7 | public class RouterController {
8 | @RequestMapping("/websshpage")
9 | public String websshpage(){
10 | return "webssh";
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/cn/objectspace/webssh/interceptor/WebSocketInterceptor.java:
--------------------------------------------------------------------------------
1 | package cn.objectspace.webssh.interceptor;
2 |
3 | import cn.objectspace.webssh.constant.ConstantPool;
4 | import org.springframework.http.server.ServerHttpRequest;
5 | import org.springframework.http.server.ServerHttpResponse;
6 | import org.springframework.http.server.ServletServerHttpRequest;
7 | import org.springframework.web.socket.WebSocketHandler;
8 | import org.springframework.web.socket.server.HandshakeInterceptor;
9 |
10 | import java.util.Map;
11 | import java.util.UUID;
12 |
13 | public class WebSocketInterceptor implements HandshakeInterceptor {
14 | /**
15 | * @Description: Handler处理前调用
16 | * @Param: [serverHttpRequest, serverHttpResponse, webSocketHandler, map]
17 | * @return: boolean
18 | * @Author: NoCortY
19 | * @Date: 2020/3/1
20 | */
21 | @Override
22 | public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map map) throws Exception {
23 | if (serverHttpRequest instanceof ServletServerHttpRequest) {
24 | ServletServerHttpRequest request = (ServletServerHttpRequest) serverHttpRequest;
25 | //生成一个UUID
26 | String uuid = UUID.randomUUID().toString().replace("-","");
27 | //将uuid放到websocketsession中
28 | map.put(ConstantPool.USER_UUID_KEY, uuid);
29 | return true;
30 | } else {
31 | return false;
32 | }
33 | }
34 |
35 | @Override
36 | public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
37 |
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/cn/objectspace/webssh/pojo/SSHConnectInfo.java:
--------------------------------------------------------------------------------
1 | package cn.objectspace.webssh.pojo;
2 |
3 | import com.jcraft.jsch.Channel;
4 | import com.jcraft.jsch.JSch;
5 | import org.springframework.web.socket.WebSocketSession;
6 | /**
7 | * @Description: ssh连接信息
8 | * @Author: NoCortY
9 | * @Date: 2020/3/8
10 | */
11 | public class SSHConnectInfo {
12 | private WebSocketSession webSocketSession;
13 | private JSch jSch;
14 | private Channel channel;
15 |
16 |
17 | public WebSocketSession getWebSocketSession() {
18 | return webSocketSession;
19 | }
20 |
21 | public void setWebSocketSession(WebSocketSession webSocketSession) {
22 | this.webSocketSession = webSocketSession;
23 | }
24 |
25 | public JSch getjSch() {
26 | return jSch;
27 | }
28 |
29 | public void setjSch(JSch jSch) {
30 | this.jSch = jSch;
31 | }
32 |
33 | public Channel getChannel() {
34 | return channel;
35 | }
36 |
37 | public void setChannel(Channel channel) {
38 | this.channel = channel;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/cn/objectspace/webssh/pojo/WebSSHData.java:
--------------------------------------------------------------------------------
1 | package cn.objectspace.webssh.pojo;
2 |
3 | /**
4 | * @Description: webssh数据传输
5 | * @Author: NoCortY
6 | * @Date: 2020/3/8
7 | */
8 | public class WebSSHData {
9 | //操作
10 | private String operate;
11 | private String host;
12 | //端口号默认为22
13 | private Integer port = 22;
14 | private String username;
15 | private String password;
16 | private String command = "";
17 |
18 | private int cols = 80;
19 | private int rows = 24;
20 | private int width = 640;
21 | private int height = 480;
22 |
23 | public int getRows() {
24 | return rows;
25 | }
26 |
27 | public void setRows(int rows) {
28 | this.rows = rows;
29 | }
30 |
31 | public int getWidth() {
32 | return width;
33 | }
34 |
35 | public void setWidth(int width) {
36 | this.width = width;
37 | }
38 |
39 | public int getHeight() {
40 | return height;
41 | }
42 |
43 | public void setHeight(int height) {
44 | this.height = height;
45 | }
46 |
47 | public int getCols() {
48 | return cols;
49 | }
50 |
51 | public void setCols(int cols) {
52 | this.cols = cols;
53 | }
54 |
55 | public String getOperate() {
56 | return operate;
57 | }
58 |
59 | public void setOperate(String operate) {
60 | this.operate = operate;
61 | }
62 |
63 | public String getHost() {
64 | return host;
65 | }
66 |
67 | public void setHost(String host) {
68 | this.host = host;
69 | }
70 |
71 | public Integer getPort() {
72 | return port;
73 | }
74 |
75 | public void setPort(Integer port) {
76 | this.port = port;
77 | }
78 |
79 | public String getUsername() {
80 | return username;
81 | }
82 |
83 | public void setUsername(String username) {
84 | this.username = username;
85 | }
86 |
87 | public String getPassword() {
88 | return password;
89 | }
90 |
91 | public void setPassword(String password) {
92 | this.password = password;
93 | }
94 |
95 | public String getCommand() {
96 | return command;
97 | }
98 |
99 | public void setCommand(String command) {
100 | this.command = command;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/cn/objectspace/webssh/service/WebSSHService.java:
--------------------------------------------------------------------------------
1 | package cn.objectspace.webssh.service;
2 |
3 | import org.springframework.web.socket.WebSocketSession;
4 |
5 | import java.io.IOException;
6 |
7 | /**
8 | * @Description: WebSSH的业务逻辑
9 | * @Author: NoCortY
10 | * @Date: 2020/3/7
11 | */
12 | public interface WebSSHService {
13 | /**
14 | * @Description: 初始化ssh连接
15 | * @Param:
16 | * @return:
17 | * @Author: NoCortY
18 | * @Date: 2020/3/7
19 | */
20 | public void initConnection(WebSocketSession session);
21 |
22 | /**
23 | * @Description: 处理客户段发的数据
24 | * @Param:
25 | * @return:
26 | * @Author: NoCortY
27 | * @Date: 2020/3/7
28 | */
29 | public void recvHandle(String buffer, WebSocketSession session);
30 |
31 | /**
32 | * @Description: 数据写回前端 for websocket
33 | * @Param:
34 | * @return:
35 | * @Author: NoCortY
36 | * @Date: 2020/3/7
37 | */
38 | public void sendMessage(WebSocketSession session, byte[] buffer) throws IOException;
39 |
40 | /**
41 | * @Description: 关闭连接
42 | * @Param:
43 | * @return:
44 | * @Author: NoCortY
45 | * @Date: 2020/3/7
46 | */
47 | public void close(WebSocketSession session);
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/cn/objectspace/webssh/websocket/WebSSHWebSocketHandler.java:
--------------------------------------------------------------------------------
1 | package cn.objectspace.webssh.websocket;
2 |
3 | import cn.objectspace.webssh.constant.ConstantPool;
4 | import cn.objectspace.webssh.service.WebSSHService;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.stereotype.Component;
9 | import org.springframework.web.socket.*;
10 |
11 |
12 | /**
13 | * @Description: WebSSH的WebSocket处理器
14 | * @Author: NoCortY
15 | * @Date: 2020/3/8
16 | */
17 | @Component
18 | public class WebSSHWebSocketHandler implements WebSocketHandler{
19 | @Autowired
20 | private WebSSHService webSSHService;
21 | private Logger logger = LoggerFactory.getLogger(WebSSHWebSocketHandler.class);
22 |
23 | /**
24 | * @Description: 用户连接上WebSocket的回调
25 | * @Param: [webSocketSession]
26 | * @return: void
27 | * @Author: NoCortY
28 | * @Date: 2020/3/8
29 | */
30 | @Override
31 | public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
32 | logger.info("用户:{},连接WebSSH", webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY));
33 | //调用初始化连接
34 | webSSHService.initConnection(webSocketSession);
35 | }
36 |
37 | /**
38 | * @Description: 收到消息的回调
39 | * @Param: [webSocketSession, webSocketMessage]
40 | * @return: void
41 | * @Author: NoCortY
42 | * @Date: 2020/3/8
43 | */
44 | @Override
45 | public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage> webSocketMessage) throws Exception {
46 | if (webSocketMessage instanceof TextMessage) {
47 | logger.info("用户:{},发送命令:{}", webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY), webSocketMessage.toString());
48 | //调用service接收消息
49 | webSSHService.recvHandle(((TextMessage) webSocketMessage).getPayload(), webSocketSession);
50 | } else if (webSocketMessage instanceof BinaryMessage) {
51 |
52 | } else if (webSocketMessage instanceof PongMessage) {
53 |
54 | } else {
55 | System.out.println("Unexpected WebSocket message type: " + webSocketMessage);
56 | }
57 | }
58 |
59 | /**
60 | * @Description: 出现错误的回调
61 | * @Param: [webSocketSession, throwable]
62 | * @return: void
63 | * @Author: NoCortY
64 | * @Date: 2020/3/8
65 | */
66 | @Override
67 | public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
68 | logger.error("数据传输错误");
69 | }
70 |
71 | /**
72 | * @Description: 连接关闭的回调
73 | * @Param: [webSocketSession, closeStatus]
74 | * @return: void
75 | * @Author: NoCortY
76 | * @Date: 2020/3/8
77 | */
78 | @Override
79 | public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
80 | logger.info("用户:{}断开webssh连接", String.valueOf(webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY)));
81 | //调用service关闭连接
82 | webSSHService.close(webSocketSession);
83 | }
84 |
85 | @Override
86 | public boolean supportsPartialMessages() {
87 | return false;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/resources/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM frolvlad/alpine-oraclejdk8
2 | VOLUME /tmp
3 | ADD WebSSH.jar app.jar
4 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
--------------------------------------------------------------------------------
/src/main/resources/config/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8080
3 | spring:
4 | mvc:
5 | view:
6 | prefix: /page/
7 | suffix: .html
--------------------------------------------------------------------------------
/src/main/resources/static/js/webssh.js:
--------------------------------------------------------------------------------
1 | function WSSHClient() {
2 | };
3 |
4 | WSSHClient.prototype._generateEndpoint = function () {
5 | if (window.location.protocol == 'https:') {
6 | var protocol = 'wss://';
7 | } else {
8 | var protocol = 'ws://';
9 | }
10 | // 内嵌应用可直接获取host
11 | // var host = window.location.host;
12 | // var endpoint = protocol+host+'/race/webssh';
13 | var endpoint = protocol+'127.0.0.1:8080/webssh';
14 | return endpoint;
15 | };
16 |
17 | WSSHClient.prototype.connect = function (options) {
18 | var endpoint = this._generateEndpoint();
19 |
20 | if (window.WebSocket) {
21 | //如果支持websocket
22 | this._connection = new WebSocket(endpoint);
23 | }else {
24 | //否则报错
25 | options.onError('WebSocket Not Supported');
26 | return;
27 | }
28 |
29 | this._connection.onopen = function () {
30 | options.onConnect();
31 | //开始连接,启动心跳检查
32 | // heartCheck.start();
33 | };
34 |
35 | this._connection.onmessage = function (evt) {
36 | var data = evt.data.toString();
37 | //如果是返回心跳,不执行onData();方法
38 | if (data !== "Heartbeat healthy") {
39 | options.onData(data);
40 | } else {
41 | //心跳健康,重置重连次数
42 | reconnectTimes = 0;
43 | }
44 | //收到消息,重置心跳检查
45 | // heartCheck.start();
46 | };
47 |
48 |
49 | this._connection.onclose = function (evt) {
50 | options.onClose();
51 | reconnect(options);
52 | };
53 | };
54 |
55 | WSSHClient.prototype.send = function (data) {
56 | this._connection.send(JSON.stringify(data));
57 | };
58 |
59 | WSSHClient.prototype.sendInitData = function (options) {
60 | //连接参数
61 | this._connection.send(JSON.stringify(options));
62 | }
63 |
64 | //关闭连接
65 | WSSHClient.prototype.close = function () {
66 | this._connection.close();
67 | }
68 |
69 | var client = new WSSHClient();
70 |
71 | //心跳检查
72 | var heartCheck = {
73 | checkTimeout: 5000,//心跳检查时间
74 | closeTimeout: 2000,//无心跳超时时间
75 | checkTimeoutObj: null,//心跳检查定时器
76 | closeTimeoutObj: null,//无心跳关闭定时器
77 | start: function () {
78 | //清除定时器
79 | clearTimeout(this.checkTimeoutObj);
80 | clearTimeout(this.closeTimeoutObj);
81 |
82 | // console.log("检查心跳");
83 | var _this = this;
84 |
85 | this.checkTimeoutObj = setTimeout(function () {
86 | client.send({operate: "heartbeat"});
87 | _this.closeTimeoutObj = setTimeout(function () {
88 | console.log("无心跳,关闭连接");
89 | client.close();
90 | }, _this.closeTimeout);
91 | }, this.checkTimeout);
92 | }
93 | }
94 |
95 | //重新连接
96 | var lockReconnect = false;//重连锁,避免重复连接
97 | var reconnectTimes = 0;
98 | var maxReconnectTimes = 6;
99 | var resetReconnectTimeout;
100 | function reconnect(options) {
101 | if (lockReconnect)
102 | return;
103 |
104 | // console.log("重新连接");
105 | clearTimeout(resetReconnectTimeout);
106 |
107 | //超过次数不重启
108 | if (reconnectTimes >= maxReconnectTimes) {
109 | options.onOverReconnect(reconnectTimes);
110 | return;
111 | }
112 |
113 | options.onReconnect(++reconnectTimes);
114 |
115 | setTimeout(function() {
116 | client.connect(options);
117 | lockReconnect = false;
118 | }, 500);
119 |
120 | //3分钟没重连,设为0次
121 | resetReconnectTimeout = setTimeout(function() {
122 | reconnectTimes=0;
123 | }, 3*60*1000);
124 | }
--------------------------------------------------------------------------------
/src/main/resources/static/page/webssh.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WebSSH
5 |
6 |
7 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
182 |
183 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "parser": "@typescript-eslint/parser",
8 | "parserOptions": {
9 | "project": [
10 | "src/browser/tsconfig.json",
11 | "src/common/tsconfig.json",
12 | "test/api/tsconfig.json",
13 | "test/benchmark/tsconfig.json",
14 | "addons/xterm-addon-attach/src/tsconfig.json",
15 | "addons/xterm-addon-fit/src/tsconfig.json",
16 | "addons/xterm-addon-search/src/tsconfig.json",
17 | "addons/xterm-addon-unicode11/src/tsconfig.json",
18 | "addons/xterm-addon-web-links/src/tsconfig.json",
19 | "addons/xterm-addon-webgl/src/tsconfig.json",
20 | "addons/xterm-addon-serialize/src/tsconfig.json",
21 | "addons/xterm-addon-serialize/benchmark/tsconfig.json",
22 | "addons/xterm-addon-ligatures/src/tsconfig.json"
23 | ],
24 | "sourceType": "module"
25 | },
26 | "ignorePatterns": [
27 | "**/typings/*.d.ts",
28 | "**/node_modules",
29 | "**/*.js"
30 | ],
31 | "plugins": [
32 | "@typescript-eslint"
33 | ],
34 | "rules": {
35 | "no-extra-semi": "error",
36 | "@typescript-eslint/array-type": [
37 | "warn",
38 | {
39 | "default": "array-simple",
40 | "readonly": "generic"
41 | }
42 | ],
43 | "@typescript-eslint/class-name-casing": "warn",
44 | "@typescript-eslint/consistent-type-definitions": "warn",
45 | "@typescript-eslint/explicit-function-return-type": [
46 | "warn",
47 | {
48 | "allowExpressions": true
49 | }
50 | ],
51 | "@typescript-eslint/explicit-member-accessibility": [
52 | "warn",
53 | {
54 | "accessibility": "explicit",
55 | "overrides": {
56 | "constructors": "off"
57 | }
58 | }
59 | ],
60 | "@typescript-eslint/indent": [
61 | "warn",
62 | 2
63 | ],
64 | "@typescript-eslint/interface-name-prefix": [
65 | "warn",
66 | "always"
67 | ],
68 | "@typescript-eslint/member-delimiter-style": [
69 | "warn",
70 | {
71 | "multiline": {
72 | "delimiter": "semi",
73 | "requireLast": true
74 | },
75 | "singleline": {
76 | "delimiter": "comma",
77 | "requireLast": false
78 | }
79 | }
80 | ],
81 | "@typescript-eslint/naming-convention": [
82 | "warn",
83 | { "selector": "default", "format": ["camelCase"] },
84 | // variableLike
85 | { "selector": "variable", "format": ["camelCase", "UPPER_CASE"] },
86 | { "selector": "variable", "filter": "^I.+Service$", "format": ["PascalCase"], "prefix": ["I"] },
87 | // memberLike
88 | { "selector": "memberLike", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require" },
89 | { "selector": "memberLike", "modifiers": ["protected"], "format": ["camelCase"], "leadingUnderscore": "require" },
90 | { "selector": "enumMember", "format": ["UPPER_CASE"] },
91 | // memberLike - Allow enum-like objects to use UPPER_CASE
92 | { "selector": "property", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] },
93 | { "selector": "method", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] },
94 | // typeLike
95 | { "selector": "typeLike", "format": ["PascalCase"] },
96 | { "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
97 | ],
98 | "@typescript-eslint/prefer-namespace-keyword": "warn",
99 | "@typescript-eslint/type-annotation-spacing": "warn",
100 | "@typescript-eslint/quotes": [
101 | "warn",
102 | "single",
103 | { "allowTemplateLiterals": true }
104 | ],
105 | "@typescript-eslint/semi": [
106 | "warn",
107 | "always"
108 | ],
109 | "comma-dangle": [
110 | "warn",
111 | {
112 | "objects": "never",
113 | "arrays": "never",
114 | "functions": "never"
115 | }
116 | ],
117 | "curly": [
118 | "warn",
119 | "multi-line"
120 | ],
121 | "eol-last": "warn",
122 | "eqeqeq": [
123 | "warn",
124 | "always"
125 | ],
126 | "keyword-spacing": "warn",
127 | "new-parens": "warn",
128 | "no-duplicate-imports": "warn",
129 | "no-else-return": [
130 | "warn",
131 | {
132 | "allowElseIf": false
133 | }
134 | ],
135 | "no-eval": "warn",
136 | "no-irregular-whitespace": "warn",
137 | "no-restricted-imports": [
138 | "warn",
139 | {
140 | "patterns": [
141 | ".*\\/out\\/.*"
142 | ]
143 | }
144 | ],
145 | "no-trailing-spaces": "warn",
146 | "no-unsafe-finally": "warn",
147 | "no-var": "warn",
148 | "one-var": [
149 | "warn",
150 | "never"
151 | ],
152 | "prefer-const": "warn",
153 | "spaced-comment": [
154 | "warn",
155 | "always",
156 | {
157 | "markers": ["/"],
158 | "exceptions": ["-"]
159 | }
160 | ]
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
2 | Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)
3 | Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/css/xterm.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2014 The xterm.js authors. All rights reserved.
3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
4 | * https://github.com/chjj/term.js
5 | * @license MIT
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | *
25 | * Originally forked from (with the author's permission):
26 | * Fabrice Bellard's javascript vt100 for jslinux:
27 | * http://bellard.org/jslinux/
28 | * Copyright (c) 2011 Fabrice Bellard
29 | * The original design remains. The terminal itself
30 | * has been extended to include xterm CSI codes, among
31 | * other features.
32 | */
33 |
34 | /**
35 | * Default styles for xterm.js
36 | */
37 |
38 | .xterm {
39 | font-feature-settings: "liga" 0;
40 | position: relative;
41 | user-select: none;
42 | -ms-user-select: none;
43 | -webkit-user-select: none;
44 | }
45 |
46 | .xterm.focus,
47 | .xterm:focus {
48 | outline: none;
49 | }
50 |
51 | .xterm .xterm-helpers {
52 | position: absolute;
53 | top: 0;
54 | /**
55 | * The z-index of the helpers must be higher than the canvases in order for
56 | * IMEs to appear on top.
57 | */
58 | z-index: 5;
59 | }
60 |
61 | .xterm .xterm-helper-textarea {
62 | padding: 0;
63 | border: 0;
64 | margin: 0;
65 | /* Move textarea out of the screen to the far left, so that the cursor is not visible */
66 | position: absolute;
67 | opacity: 0;
68 | left: -9999em;
69 | top: 0;
70 | width: 0;
71 | height: 0;
72 | z-index: -5;
73 | /** Prevent wrapping so the IME appears against the textarea at the correct position */
74 | white-space: nowrap;
75 | overflow: hidden;
76 | resize: none;
77 | }
78 |
79 | .xterm .composition-view {
80 | /* TODO: Composition position got messed up somewhere */
81 | background: #000;
82 | color: #FFF;
83 | display: none;
84 | position: absolute;
85 | white-space: nowrap;
86 | z-index: 1;
87 | }
88 |
89 | .xterm .composition-view.active {
90 | display: block;
91 | }
92 |
93 | .xterm .xterm-viewport {
94 | /* On OS X this is required in order for the scroll bar to appear fully opaque */
95 | background-color: #000;
96 | overflow-y: scroll;
97 | cursor: default;
98 | position: absolute;
99 | right: 0;
100 | left: 0;
101 | top: 0;
102 | bottom: 0;
103 | }
104 |
105 | .xterm .xterm-screen {
106 | position: relative;
107 | }
108 |
109 | .xterm .xterm-screen canvas {
110 | position: absolute;
111 | left: 0;
112 | top: 0;
113 | }
114 |
115 | .xterm .xterm-scroll-area {
116 | visibility: hidden;
117 | }
118 |
119 | .xterm-char-measure-element {
120 | display: inline-block;
121 | visibility: hidden;
122 | position: absolute;
123 | top: 0;
124 | left: -9999em;
125 | line-height: normal;
126 | }
127 |
128 | .xterm {
129 | cursor: text;
130 | }
131 |
132 | .xterm.enable-mouse-events {
133 | /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
134 | cursor: default;
135 | }
136 |
137 | .xterm.xterm-cursor-pointer {
138 | cursor: pointer;
139 | }
140 |
141 | .xterm.column-select.focus {
142 | /* Column selection mode */
143 | cursor: crosshair;
144 | }
145 |
146 | .xterm .xterm-accessibility,
147 | .xterm .xterm-message {
148 | position: absolute;
149 | left: 0;
150 | top: 0;
151 | bottom: 0;
152 | right: 0;
153 | z-index: 10;
154 | color: transparent;
155 | }
156 |
157 | .xterm .live-region {
158 | position: absolute;
159 | left: -9999px;
160 | width: 1px;
161 | height: 1px;
162 | overflow: hidden;
163 | }
164 |
165 | .xterm-dim {
166 | opacity: 0.5;
167 | }
168 |
169 | .xterm-underline {
170 | text-decoration: underline;
171 | }
172 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "_args": [
3 | [
4 | "xterm@4.8.1",
5 | "D:\\IdeaProjects\\sx\\training-ui"
6 | ]
7 | ],
8 | "_from": "xterm@4.8.1",
9 | "_id": "xterm@4.8.1",
10 | "_inBundle": false,
11 | "_integrity": "sha512-ax91ny4tI5eklqIfH79OUSGE2PUX2rGbwONmB6DfqpyhSZO8/cf++sqiaMWEVCMjACyMfnISW7C3gGMoNvNolQ==",
12 | "_location": "/xterm",
13 | "_phantomChildren": {},
14 | "_requested": {
15 | "type": "version",
16 | "registry": true,
17 | "raw": "xterm@4.8.1",
18 | "name": "xterm",
19 | "escapedName": "xterm",
20 | "rawSpec": "4.8.1",
21 | "saveSpec": null,
22 | "fetchSpec": "4.8.1"
23 | },
24 | "_requiredBy": [
25 | "/"
26 | ],
27 | "_resolved": "https://registry.npmjs.org/xterm/-/xterm-4.8.1.tgz",
28 | "_spec": "4.8.1",
29 | "_where": "D:\\IdeaProjects\\sx\\training-ui",
30 | "bugs": {
31 | "url": "https://github.com/xtermjs/xterm.js/issues"
32 | },
33 | "description": "Full xterm terminal, in your browser",
34 | "devDependencies": {
35 | "@types/chai": "^4.2.11",
36 | "@types/debug": "^4.1.5",
37 | "@types/deep-equal": "^1.0.1",
38 | "@types/glob": "^7.1.3",
39 | "@types/jsdom": "^16.2.3",
40 | "@types/mocha": "^7.0.2",
41 | "@types/node": "^10.17.17",
42 | "@types/utf8": "^2.1.6",
43 | "@types/webpack": "^4.41.21",
44 | "@types/ws": "^7.2.6",
45 | "@typescript-eslint/eslint-plugin": "^2.34.0",
46 | "@typescript-eslint/parser": "^2.34.0",
47 | "chai": "^4.2.0",
48 | "deep-equal": "^2.0.3",
49 | "eslint": "^6.8.0",
50 | "express": "^4.17.1",
51 | "express-ws": "^4.0.0",
52 | "glob": "^7.0.5",
53 | "jsdom": "^16.3.0",
54 | "mocha": "^8.0.1",
55 | "mustache": "^4.0.1",
56 | "node-pty": "^0.9.0",
57 | "nyc": "^15.1.0",
58 | "playwright-core": "^0.11.1",
59 | "source-map-loader": "^1.0.1",
60 | "ts-loader": "^8.0.0",
61 | "typescript": "3.9",
62 | "utf8": "^3.0.0",
63 | "webpack": "^4.43.0",
64 | "webpack-cli": "^3.3.12",
65 | "ws": "^7.3.1",
66 | "xterm-benchmark": "^0.1.3"
67 | },
68 | "homepage": "https://github.com/xtermjs/xterm.js#readme",
69 | "license": "MIT",
70 | "main": "lib/xterm.js",
71 | "name": "xterm",
72 | "repository": {
73 | "type": "git",
74 | "url": "git+https://github.com/xtermjs/xterm.js.git"
75 | },
76 | "scripts": {
77 | "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
78 | "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js",
79 | "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js",
80 | "build": "tsc -b ./tsconfig.all.json",
81 | "clean": "rm -rf lib out addons/*/lib addons/*/out",
82 | "lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/",
83 | "package": "webpack",
84 | "posttest": "npm run lint",
85 | "prepackage": "npm run build",
86 | "prepare": "npm run setup",
87 | "prepublishOnly": "npm run package",
88 | "presetup": "node ./bin/install-addons.js",
89 | "pretest-api-chromium": "node ./bin/download_browser.js --browser chromium",
90 | "pretest-api-firefox": "node ./bin/download_browser.js --browser firefox",
91 | "pretest-api-webkit": "node ./bin/download_browser.js --browser webkit",
92 | "setup": "npm run build",
93 | "start": "node demo/start",
94 | "test": "npm run test-unit",
95 | "test-api": "npm run test-api-chromium",
96 | "test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=20000",
97 | "test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=20000",
98 | "test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000",
99 | "test-unit": "node ./bin/test.js",
100 | "test-unit-coverage": "node ./bin/test.js --coverage",
101 | "vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts",
102 | "watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput"
103 | },
104 | "style": "css/xterm.css",
105 | "types": "typings/xterm.d.ts",
106 | "version": "4.8.1"
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/Clipboard.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { ISelectionService } from 'browser/services/Services';
7 | import { ICoreService } from 'common/services/Services';
8 |
9 | /**
10 | * Prepares text to be pasted into the terminal by normalizing the line endings
11 | * @param text The pasted text that needs processing before inserting into the terminal
12 | */
13 | export function prepareTextForTerminal(text: string): string {
14 | return text.replace(/\r?\n/g, '\r');
15 | }
16 |
17 | /**
18 | * Bracket text for paste, if necessary, as per https://cirw.in/blog/bracketed-paste
19 | * @param text The pasted text to bracket
20 | */
21 | export function bracketTextForPaste(text: string, bracketedPasteMode: boolean): string {
22 | if (bracketedPasteMode) {
23 | return '\x1b[200~' + text + '\x1b[201~';
24 | }
25 | return text;
26 | }
27 |
28 | /**
29 | * Binds copy functionality to the given terminal.
30 | * @param ev The original copy event to be handled
31 | */
32 | export function copyHandler(ev: ClipboardEvent, selectionService: ISelectionService): void {
33 | if (ev.clipboardData) {
34 | ev.clipboardData.setData('text/plain', selectionService.selectionText);
35 | }
36 | // Prevent or the original text will be copied.
37 | ev.preventDefault();
38 | }
39 |
40 | /**
41 | * Redirect the clipboard's data to the terminal's input handler.
42 | * @param ev The original paste event to be handled
43 | * @param term The terminal on which to apply the handled paste event
44 | */
45 | export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, coreService: ICoreService): void {
46 | ev.stopPropagation();
47 | if (ev.clipboardData) {
48 | const text = ev.clipboardData.getData('text/plain');
49 | paste(text, textarea, coreService);
50 | }
51 | }
52 |
53 | export function paste(text: string, textarea: HTMLTextAreaElement, coreService: ICoreService): void {
54 | text = prepareTextForTerminal(text);
55 | text = bracketTextForPaste(text, coreService.decPrivateModes.bracketedPasteMode);
56 | coreService.triggerDataEvent(text, true);
57 | textarea.value = '';
58 | }
59 |
60 | /**
61 | * Moves the textarea under the mouse cursor and focuses it.
62 | * @param ev The original right click event to be handled.
63 | * @param textarea The terminal's textarea.
64 | */
65 | export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement): void {
66 |
67 | // Calculate textarea position relative to the screen element
68 | const pos = screenElement.getBoundingClientRect();
69 | const left = ev.clientX - pos.left - 10;
70 | const top = ev.clientY - pos.top - 10;
71 |
72 | // Bring textarea at the cursor position
73 | textarea.style.width = '20px';
74 | textarea.style.height = '20px';
75 | textarea.style.left = `${left}px`;
76 | textarea.style.top = `${top}px`;
77 | textarea.style.zIndex = '1000';
78 |
79 | textarea.focus();
80 | }
81 |
82 | /**
83 | * Bind to right-click event and allow right-click copy and paste.
84 | * @param ev The original right click event to be handled.
85 | * @param textarea The terminal's textarea.
86 | * @param selectionService The terminal's selection manager.
87 | * @param shouldSelectWord If true and there is no selection the current word will be selected
88 | */
89 | export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement, selectionService: ISelectionService, shouldSelectWord: boolean): void {
90 | moveTextAreaUnderMouseCursor(ev, textarea, screenElement);
91 |
92 | if (shouldSelectWord && !selectionService.isClickInSelection(ev)) {
93 | selectionService.selectWordAtCursor(ev);
94 | }
95 |
96 | // Get textarea ready to copy from the context menu
97 | textarea.value = selectionService.selectionText;
98 | textarea.select();
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/ColorContrastCache.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IColor, IColorContrastCache } from 'browser/Types';
7 |
8 | export class ColorContrastCache implements IColorContrastCache {
9 | private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {};
10 | private _rgba: { [bg: number]: { [fg: number]: string | null | undefined } | undefined } = {};
11 |
12 | public clear(): void {
13 | this._color = {};
14 | this._rgba = {};
15 | }
16 |
17 | public setCss(bg: number, fg: number, value: string | null): void {
18 | if (!this._rgba[bg]) {
19 | this._rgba[bg] = {};
20 | }
21 | this._rgba[bg]![fg] = value;
22 | }
23 |
24 | public getCss(bg: number, fg: number): string | null | undefined {
25 | return this._rgba[bg] ? this._rgba[bg]![fg] : undefined;
26 | }
27 |
28 | public setColor(bg: number, fg: number, value: IColor | null): void {
29 | if (!this._color[bg]) {
30 | this._color[bg] = {};
31 | }
32 | this._color[bg]![fg] = value;
33 | }
34 |
35 | public getColor(bg: number, fg: number): IColor | null | undefined {
36 | return this._color[bg] ? this._color[bg]![fg] : undefined;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/Dom.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2020 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | export function removeElementFromParent(...elements: (HTMLElement | undefined)[]): void {
7 | for (const e of elements) {
8 | e?.parentElement?.removeChild(e);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/Lifecycle.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IDisposable } from 'common/Types';
7 |
8 | /**
9 | * Adds a disposable listener to a node in the DOM, returning the disposable.
10 | * @param type The event type.
11 | * @param handler The handler for the listener.
12 | */
13 | export function addDisposableDomListener(
14 | node: Element | Window | Document,
15 | type: string,
16 | handler: (e: any) => void,
17 | options?: boolean | AddEventListenerOptions
18 | ): IDisposable {
19 | node.addEventListener(type, handler, options);
20 | let disposed = false;
21 | return {
22 | dispose: () => {
23 | if (disposed) {
24 | return;
25 | }
26 | disposed = true;
27 | node.removeEventListener(type, handler, options);
28 | }
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/LocalizableStrings.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | // eslint-disable-next-line prefer-const
7 | export let promptLabel = 'Terminal input';
8 |
9 | // eslint-disable-next-line prefer-const
10 | export let tooMuchOutput = 'Too much output to announce, navigate to rows manually to read';
11 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/RenderDebouncer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IDisposable } from 'common/Types';
7 |
8 | /**
9 | * Debounces calls to render terminal rows using animation frames.
10 | */
11 | export class RenderDebouncer implements IDisposable {
12 | private _rowStart: number | undefined;
13 | private _rowEnd: number | undefined;
14 | private _rowCount: number | undefined;
15 | private _animationFrame: number | undefined;
16 |
17 | constructor(
18 | private _renderCallback: (start: number, end: number) => void
19 | ) {
20 | }
21 |
22 | public dispose(): void {
23 | if (this._animationFrame) {
24 | window.cancelAnimationFrame(this._animationFrame);
25 | this._animationFrame = undefined;
26 | }
27 | }
28 |
29 | public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {
30 | this._rowCount = rowCount;
31 | // Get the min/max row start/end for the arg values
32 | rowStart = rowStart !== undefined ? rowStart : 0;
33 | rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1;
34 | // Set the properties to the updated values
35 | this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;
36 | this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;
37 |
38 | if (this._animationFrame) {
39 | return;
40 | }
41 |
42 | this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh());
43 | }
44 |
45 | private _innerRefresh(): void {
46 | // Make sure values are set
47 | if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {
48 | return;
49 | }
50 |
51 | // Clamp values
52 | const start = Math.max(this._rowStart, 0);
53 | const end = Math.min(this._rowEnd, this._rowCount - 1);
54 |
55 | // Reset debouncer (this happens before render callback as the render could trigger it again)
56 | this._rowStart = undefined;
57 | this._rowEnd = undefined;
58 | this._animationFrame = undefined;
59 |
60 | // Run render callback
61 | this._renderCallback(start, end);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/ScreenDprMonitor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { Disposable } from 'common/Lifecycle';
7 |
8 | export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRatio?: number) => void;
9 |
10 | /**
11 | * The screen device pixel ratio monitor allows listening for when the
12 | * window.devicePixelRatio value changes. This is done not with polling but with
13 | * the use of window.matchMedia to watch media queries. When the event fires,
14 | * the listener will be reattached using a different media query to ensure that
15 | * any further changes will register.
16 | *
17 | * The listener should fire on both window zoom changes and switching to a
18 | * monitor with a different DPI.
19 | */
20 | export class ScreenDprMonitor extends Disposable {
21 | private _currentDevicePixelRatio: number = window.devicePixelRatio;
22 | private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined;
23 | private _listener: ScreenDprListener | undefined;
24 | private _resolutionMediaMatchList: MediaQueryList | undefined;
25 |
26 | public setListener(listener: ScreenDprListener): void {
27 | if (this._listener) {
28 | this.clearListener();
29 | }
30 | this._listener = listener;
31 | this._outerListener = () => {
32 | if (!this._listener) {
33 | return;
34 | }
35 | this._listener(window.devicePixelRatio, this._currentDevicePixelRatio);
36 | this._updateDpr();
37 | };
38 | this._updateDpr();
39 | }
40 |
41 | public dispose(): void {
42 | super.dispose();
43 | this.clearListener();
44 | }
45 |
46 | private _updateDpr(): void {
47 | if (!this._outerListener) {
48 | return;
49 | }
50 |
51 | // Clear listeners for old DPR
52 | this._resolutionMediaMatchList?.removeListener(this._outerListener);
53 |
54 | // Add listeners for new DPR
55 | this._currentDevicePixelRatio = window.devicePixelRatio;
56 | this._resolutionMediaMatchList = window.matchMedia(`screen and (resolution: ${window.devicePixelRatio}dppx)`);
57 | this._resolutionMediaMatchList.addListener(this._outerListener);
58 | }
59 |
60 | public clearListener(): void {
61 | if (!this._resolutionMediaMatchList || !this._listener || !this._outerListener) {
62 | return;
63 | }
64 | this._resolutionMediaMatchList.removeListener(this._outerListener);
65 | this._resolutionMediaMatchList = undefined;
66 | this._listener = undefined;
67 | this._outerListener = undefined;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/input/Mouse.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | export function getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
7 | const rect = element.getBoundingClientRect();
8 | return [event.clientX - rect.left, event.clientY - rect.top];
9 | }
10 |
11 | /**
12 | * Gets coordinates within the terminal for a particular mouse event. The result
13 | * is returned as an array in the form [x, y] instead of an object as it's a
14 | * little faster and this function is used in some low level code.
15 | * @param event The mouse event.
16 | * @param element The terminal's container element.
17 | * @param colCount The number of columns in the terminal.
18 | * @param rowCount The number of rows n the terminal.
19 | * @param isSelection Whether the request is for the selection or not. This will
20 | * apply an offset to the x value such that the left half of the cell will
21 | * select that cell and the right half will select the next cell.
22 | */
23 | export function getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined {
24 | // Coordinates cannot be measured if there are no valid
25 | if (!hasValidCharSize) {
26 | return undefined;
27 | }
28 |
29 | const coords = getCoordsRelativeToElement(event, element);
30 | if (!coords) {
31 | return undefined;
32 | }
33 |
34 | coords[0] = Math.ceil((coords[0] + (isSelection ? actualCellWidth / 2 : 0)) / actualCellWidth);
35 | coords[1] = Math.ceil(coords[1] / actualCellHeight);
36 |
37 | // Ensure coordinates are within the terminal viewport. Note that selections
38 | // need an addition point of precision to cover the end point (as characters
39 | // cover half of one char and half of the next).
40 | coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));
41 | coords[1] = Math.min(Math.max(coords[1], 1), rowCount);
42 |
43 | return coords;
44 | }
45 |
46 | /**
47 | * Gets coordinates within the terminal for a particular mouse event, wrapping
48 | * them to the bounds of the terminal and adding 32 to both the x and y values
49 | * as expected by xterm.
50 | */
51 | export function getRawByteCoords(coords: [number, number] | undefined): { x: number, y: number } | undefined {
52 | if (!coords) {
53 | return undefined;
54 | }
55 |
56 | // xterm sends raw bytes and starts at 32 (SP) for each.
57 | return { x: coords[0] + 32, y: coords[1] + 32 };
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/public/AddonManager.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { ITerminalAddon, IDisposable, Terminal } from 'xterm';
7 |
8 | export interface ILoadedAddon {
9 | instance: ITerminalAddon;
10 | dispose: () => void;
11 | isDisposed: boolean;
12 | }
13 |
14 | export class AddonManager implements IDisposable {
15 | protected _addons: ILoadedAddon[] = [];
16 |
17 | constructor() {
18 | }
19 |
20 | public dispose(): void {
21 | for (let i = this._addons.length - 1; i >= 0; i--) {
22 | this._addons[i].instance.dispose();
23 | }
24 | }
25 |
26 | public loadAddon(terminal: Terminal, instance: ITerminalAddon): void {
27 | const loadedAddon: ILoadedAddon = {
28 | instance,
29 | dispose: instance.dispose,
30 | isDisposed: false
31 | };
32 | this._addons.push(loadedAddon);
33 | instance.dispose = () => this._wrappedAddonDispose(loadedAddon);
34 | instance.activate(terminal);
35 | }
36 |
37 | private _wrappedAddonDispose(loadedAddon: ILoadedAddon): void {
38 | if (loadedAddon.isDisposed) {
39 | // Do nothing if already disposed
40 | return;
41 | }
42 | let index = -1;
43 | for (let i = 0; i < this._addons.length; i++) {
44 | if (this._addons[i] === loadedAddon) {
45 | index = i;
46 | break;
47 | }
48 | }
49 | if (index === -1) {
50 | throw new Error('Could not dispose an addon that has not been loaded');
51 | }
52 | loadedAddon.isDisposed = true;
53 | loadedAddon.dispose.apply(loadedAddon.instance);
54 | this._addons.splice(index, 1);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/GridCache.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | export class GridCache {
7 | public cache: (T | undefined)[][];
8 |
9 | public constructor() {
10 | this.cache = [];
11 | }
12 |
13 | public resize(width: number, height: number): void {
14 | for (let x = 0; x < width; x++) {
15 | if (this.cache.length <= x) {
16 | this.cache.push([]);
17 | }
18 | for (let y = this.cache[x].length; y < height; y++) {
19 | this.cache[x].push(undefined);
20 | }
21 | this.cache[x].length = height;
22 | }
23 | this.cache.length = width;
24 | }
25 |
26 | public clear(): void {
27 | for (let x = 0; x < this.cache.length; x++) {
28 | for (let y = 0; y < this.cache[x].length; y++) {
29 | this.cache[x][y] = undefined;
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/LinkRenderLayer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IRenderDimensions } from 'browser/renderer/Types';
7 | import { BaseRenderLayer } from './BaseRenderLayer';
8 | import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
9 | import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils';
10 | import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
11 | import { IBufferService, IOptionsService } from 'common/services/Services';
12 |
13 | export class LinkRenderLayer extends BaseRenderLayer {
14 | private _state: ILinkifierEvent | undefined;
15 |
16 | constructor(
17 | container: HTMLElement,
18 | zIndex: number,
19 | colors: IColorSet,
20 | rendererId: number,
21 | linkifier: ILinkifier,
22 | linkifier2: ILinkifier2,
23 | bufferService: IBufferService,
24 | optionsService: IOptionsService
25 | ) {
26 | super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
27 | linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
28 | linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
29 |
30 | linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
31 | linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
32 | }
33 |
34 | public resize(dim: IRenderDimensions): void {
35 | super.resize(dim);
36 | // Resizing the canvas discards the contents of the canvas so clear state
37 | this._state = undefined;
38 | }
39 |
40 | public reset(): void {
41 | this._clearCurrentLink();
42 | }
43 |
44 | private _clearCurrentLink(): void {
45 | if (this._state) {
46 | this._clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1);
47 | const middleRowCount = this._state.y2 - this._state.y1 - 1;
48 | if (middleRowCount > 0) {
49 | this._clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount);
50 | }
51 | this._clearCells(0, this._state.y2, this._state.x2, 1);
52 | this._state = undefined;
53 | }
54 | }
55 |
56 | private _onShowLinkUnderline(e: ILinkifierEvent): void {
57 | if (e.fg === INVERTED_DEFAULT_COLOR) {
58 | this._ctx.fillStyle = this._colors.background.css;
59 | } else if (e.fg && is256Color(e.fg)) {
60 | // 256 color support
61 | this._ctx.fillStyle = this._colors.ansi[e.fg].css;
62 | } else {
63 | this._ctx.fillStyle = this._colors.foreground.css;
64 | }
65 |
66 | if (e.y1 === e.y2) {
67 | // Single line link
68 | this._fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1);
69 | } else {
70 | // Multi-line link
71 | this._fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1);
72 | for (let y = e.y1 + 1; y < e.y2; y++) {
73 | this._fillBottomLineAtCells(0, y, e.cols);
74 | }
75 | this._fillBottomLineAtCells(0, e.y2, e.x2);
76 | }
77 | this._state = e;
78 | }
79 |
80 | private _onHideLinkUnderline(e: ILinkifierEvent): void {
81 | this._clearCurrentLink();
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/RendererUtils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | export function throwIfFalsy(value: T | undefined | null): T {
7 | if (!value) {
8 | throw new Error('value must not be falsy');
9 | }
10 | return value;
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/SelectionRenderLayer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IRenderDimensions } from 'browser/renderer/Types';
7 | import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
8 | import { IColorSet } from 'browser/Types';
9 | import { IBufferService, IOptionsService } from 'common/services/Services';
10 |
11 | interface ISelectionState {
12 | start?: [number, number];
13 | end?: [number, number];
14 | columnSelectMode?: boolean;
15 | ydisp?: number;
16 | }
17 |
18 | export class SelectionRenderLayer extends BaseRenderLayer {
19 | private _state!: ISelectionState;
20 |
21 | constructor(
22 | container: HTMLElement,
23 | zIndex: number,
24 | colors: IColorSet,
25 | rendererId: number,
26 | bufferService: IBufferService,
27 | optionsService: IOptionsService
28 | ) {
29 | super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
30 | this._clearState();
31 | }
32 |
33 | private _clearState(): void {
34 | this._state = {
35 | start: undefined,
36 | end: undefined,
37 | columnSelectMode: undefined,
38 | ydisp: undefined
39 | };
40 | }
41 |
42 | public resize(dim: IRenderDimensions): void {
43 | super.resize(dim);
44 | // Resizing the canvas discards the contents of the canvas so clear state
45 | this._clearState();
46 | }
47 |
48 | public reset(): void {
49 | if (this._state.start && this._state.end) {
50 | this._clearState();
51 | this._clearAll();
52 | }
53 | }
54 |
55 | public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
56 | // Selection has not changed
57 | if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
58 | return;
59 | }
60 |
61 | // Remove all selections
62 | this._clearAll();
63 |
64 | // Selection does not exist
65 | if (!start || !end) {
66 | this._clearState();
67 | return;
68 | }
69 |
70 | // Translate from buffer position to viewport position
71 | const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
72 | const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
73 | const viewportCappedStartRow = Math.max(viewportStartRow, 0);
74 | const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
75 |
76 | // No need to draw the selection
77 | if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
78 | return;
79 | }
80 |
81 | this._ctx.fillStyle = this._colors.selectionTransparent.css;
82 |
83 | if (columnSelectMode) {
84 | const startCol = start[0];
85 | const width = end[0] - startCol;
86 | const height = viewportCappedEndRow - viewportCappedStartRow + 1;
87 | this._fillCells(startCol, viewportCappedStartRow, width, height);
88 | } else {
89 | // Draw first row
90 | const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
91 | const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
92 | this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
93 |
94 | // Draw middle rows
95 | const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
96 | this._fillCells(0, viewportCappedStartRow + 1, this._bufferService.cols, middleRowsCount);
97 |
98 | // Draw final row
99 | if (viewportCappedStartRow !== viewportCappedEndRow) {
100 | // Only draw viewportEndRow if it's not the same as viewportStartRow
101 | const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
102 | this._fillCells(0, viewportCappedEndRow, endCol, 1);
103 | }
104 | }
105 |
106 | // Save state for next render
107 | this._state.start = [start[0], start[1]];
108 | this._state.end = [end[0], end[1]];
109 | this._state.columnSelectMode = columnSelectMode;
110 | this._state.ydisp = this._bufferService.buffer.ydisp;
111 | }
112 |
113 | private _didStateChange(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean, ydisp: number): boolean {
114 | return !this._areCoordinatesEqual(start, this._state.start) ||
115 | !this._areCoordinatesEqual(end, this._state.end) ||
116 | columnSelectMode !== this._state.columnSelectMode ||
117 | ydisp !== this._state.ydisp;
118 | }
119 |
120 | private _areCoordinatesEqual(coord1: [number, number] | undefined, coord2: [number, number] | undefined): boolean {
121 | if (!coord1 || !coord2) {
122 | return false;
123 | }
124 |
125 | return coord1[0] === coord2[0] && coord1[1] === coord2[1];
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/Types.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IDisposable } from 'common/Types';
7 | import { IColorSet } from 'browser/Types';
8 | import { IEvent } from 'common/EventEmitter';
9 |
10 | export type CharacterJoinerHandler = (text: string) => [number, number][];
11 |
12 | export interface IRenderDimensions {
13 | scaledCharWidth: number;
14 | scaledCharHeight: number;
15 | scaledCellWidth: number;
16 | scaledCellHeight: number;
17 | scaledCharLeft: number;
18 | scaledCharTop: number;
19 | scaledCanvasWidth: number;
20 | scaledCanvasHeight: number;
21 | canvasWidth: number;
22 | canvasHeight: number;
23 | actualCellWidth: number;
24 | actualCellHeight: number;
25 | }
26 |
27 | export interface IRequestRedrawEvent {
28 | start: number;
29 | end: number;
30 | }
31 |
32 | /**
33 | * Note that IRenderer implementations should emit the refresh event after
34 | * rendering rows to the screen.
35 | */
36 | export interface IRenderer extends IDisposable {
37 | readonly dimensions: IRenderDimensions;
38 |
39 | /**
40 | * Fires when the renderer is requesting to be redrawn on the next animation
41 | * frame but is _not_ a result of content changing (eg. selection changes).
42 | */
43 | readonly onRequestRedraw: IEvent;
44 |
45 | dispose(): void;
46 | setColors(colors: IColorSet): void;
47 | onDevicePixelRatioChange(): void;
48 | onResize(cols: number, rows: number): void;
49 | onCharSizeChanged(): void;
50 | onBlur(): void;
51 | onFocus(): void;
52 | onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
53 | onCursorMove(): void;
54 | onOptionsChanged(): void;
55 | clear(): void;
56 | renderRows(start: number, end: number): void;
57 | registerCharacterJoiner(handler: CharacterJoinerHandler): number;
58 | deregisterCharacterJoiner(joinerId: number): boolean;
59 | }
60 |
61 | export interface ICharacterJoiner {
62 | id: number;
63 | handler: CharacterJoinerHandler;
64 | }
65 |
66 | export interface ICharacterJoinerRegistry {
67 | registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
68 | deregisterCharacterJoiner(joinerId: number): boolean;
69 | getJoinedCharacters(row: number): [number, number][];
70 | }
71 |
72 | export interface IRenderLayer extends IDisposable {
73 | /**
74 | * Called when the terminal loses focus.
75 | */
76 | onBlur(): void;
77 |
78 | /**
79 | * * Called when the terminal gets focus.
80 | */
81 | onFocus(): void;
82 |
83 | /**
84 | * Called when the cursor is moved.
85 | */
86 | onCursorMove(): void;
87 |
88 | /**
89 | * Called when options change.
90 | */
91 | onOptionsChanged(): void;
92 |
93 | /**
94 | * Called when the theme changes.
95 | */
96 | setColors(colorSet: IColorSet): void;
97 |
98 | /**
99 | * Called when the data in the grid has changed (or needs to be rendered
100 | * again).
101 | */
102 | onGridChanged(startRow: number, endRow: number): void;
103 |
104 | /**
105 | * Calls when the selection changes.
106 | */
107 | onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
108 |
109 | /**
110 | * Registers a handler to join characters to render as a group
111 | */
112 | registerCharacterJoiner?(joiner: ICharacterJoiner): void;
113 |
114 | /**
115 | * Deregisters the specified character joiner handler
116 | */
117 | deregisterCharacterJoiner?(joinerId: number): void;
118 |
119 | /**
120 | * Resize the render layer.
121 | */
122 | resize(dim: IRenderDimensions): void;
123 |
124 | /**
125 | * Clear the state of the render layer.
126 | */
127 | reset(): void;
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/atlas/BaseCharAtlas.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
7 | import { IDisposable } from 'common/Types';
8 |
9 | export abstract class BaseCharAtlas implements IDisposable {
10 | private _didWarmUp: boolean = false;
11 |
12 | public dispose(): void { }
13 |
14 | /**
15 | * Perform any work needed to warm the cache before it can be used. May be called multiple times.
16 | * Implement _doWarmUp instead if you only want to get called once.
17 | */
18 | public warmUp(): void {
19 | if (!this._didWarmUp) {
20 | this._doWarmUp();
21 | this._didWarmUp = true;
22 | }
23 | }
24 |
25 | /**
26 | * Perform any work needed to warm the cache before it can be used. Used by the default
27 | * implementation of warmUp(), and will only be called once.
28 | */
29 | protected _doWarmUp(): void { }
30 |
31 | /**
32 | * Called when we start drawing a new frame.
33 | *
34 | * TODO: We rely on this getting called by TextRenderLayer. This should really be called by
35 | * Renderer instead, but we need to make Renderer the source-of-truth for the char atlas, instead
36 | * of BaseRenderLayer.
37 | */
38 | public beginFrame(): void { }
39 |
40 | /**
41 | * May be called before warmUp finishes, however it is okay for the implementation to
42 | * do nothing and return false in that case.
43 | *
44 | * @param ctx Where to draw the character onto.
45 | * @param glyph Information about what to draw
46 | * @param x The position on the context to start drawing at
47 | * @param y The position on the context to start drawing at
48 | * @returns The success state. True if we drew the character.
49 | */
50 | public abstract draw(
51 | ctx: CanvasRenderingContext2D,
52 | glyph: IGlyphIdentifier,
53 | x: number,
54 | y: number
55 | ): boolean;
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/atlas/CharAtlasCache.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { generateConfig, configEquals } from 'browser/renderer/atlas/CharAtlasUtils';
7 | import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
8 | import { DynamicCharAtlas } from 'browser/renderer/atlas/DynamicCharAtlas';
9 | import { ICharAtlasConfig } from 'browser/renderer/atlas/Types';
10 | import { IColorSet } from 'browser/Types';
11 | import { ITerminalOptions } from 'common/services/Services';
12 |
13 | interface ICharAtlasCacheEntry {
14 | atlas: BaseCharAtlas;
15 | config: ICharAtlasConfig;
16 | // N.B. This implementation potentially holds onto copies of the terminal forever, so
17 | // this may cause memory leaks.
18 | ownedBy: number[];
19 | }
20 |
21 | const charAtlasCache: ICharAtlasCacheEntry[] = [];
22 |
23 | /**
24 | * Acquires a char atlas, either generating a new one or returning an existing
25 | * one that is in use by another terminal.
26 | */
27 | export function acquireCharAtlas(
28 | options: ITerminalOptions,
29 | rendererId: number,
30 | colors: IColorSet,
31 | scaledCharWidth: number,
32 | scaledCharHeight: number
33 | ): BaseCharAtlas {
34 | const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, options, colors);
35 |
36 | // Check to see if the renderer already owns this config
37 | for (let i = 0; i < charAtlasCache.length; i++) {
38 | const entry = charAtlasCache[i];
39 | const ownedByIndex = entry.ownedBy.indexOf(rendererId);
40 | if (ownedByIndex >= 0) {
41 | if (configEquals(entry.config, newConfig)) {
42 | return entry.atlas;
43 | }
44 | // The configs differ, release the renderer from the entry
45 | if (entry.ownedBy.length === 1) {
46 | entry.atlas.dispose();
47 | charAtlasCache.splice(i, 1);
48 | } else {
49 | entry.ownedBy.splice(ownedByIndex, 1);
50 | }
51 | break;
52 | }
53 | }
54 |
55 | // Try match a char atlas from the cache
56 | for (let i = 0; i < charAtlasCache.length; i++) {
57 | const entry = charAtlasCache[i];
58 | if (configEquals(entry.config, newConfig)) {
59 | // Add the renderer to the cache entry and return
60 | entry.ownedBy.push(rendererId);
61 | return entry.atlas;
62 | }
63 | }
64 |
65 | const newEntry: ICharAtlasCacheEntry = {
66 | atlas: new DynamicCharAtlas(
67 | document,
68 | newConfig
69 | ),
70 | config: newConfig,
71 | ownedBy: [rendererId]
72 | };
73 | charAtlasCache.push(newEntry);
74 | return newEntry.atlas;
75 | }
76 |
77 | /**
78 | * Removes a terminal reference from the cache, allowing its memory to be freed.
79 | */
80 | export function removeTerminalFromCache(rendererId: number): void {
81 | for (let i = 0; i < charAtlasCache.length; i++) {
82 | const index = charAtlasCache[i].ownedBy.indexOf(rendererId);
83 | if (index !== -1) {
84 | if (charAtlasCache[i].ownedBy.length === 1) {
85 | // Remove the cache entry if it's the only renderer
86 | charAtlasCache[i].atlas.dispose();
87 | charAtlasCache.splice(i, 1);
88 | } else {
89 | // Remove the reference from the cache entry
90 | charAtlasCache[i].ownedBy.splice(index, 1);
91 | }
92 | break;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/atlas/CharAtlasUtils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { ICharAtlasConfig } from 'browser/renderer/atlas/Types';
7 | import { DEFAULT_COLOR } from 'common/buffer/Constants';
8 | import { IColorSet, IPartialColorSet } from 'browser/Types';
9 | import { ITerminalOptions } from 'common/services/Services';
10 |
11 | export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: ITerminalOptions, colors: IColorSet): ICharAtlasConfig {
12 | // null out some fields that don't matter
13 | const clonedColors = {
14 | foreground: colors.foreground,
15 | background: colors.background,
16 | cursor: undefined,
17 | cursorAccent: undefined,
18 | selection: undefined,
19 | // For the static char atlas, we only use the first 16 colors, but we need all 256 for the
20 | // dynamic character atlas.
21 | ansi: colors.ansi.slice(0, 16)
22 | };
23 | return {
24 | devicePixelRatio: window.devicePixelRatio,
25 | scaledCharWidth,
26 | scaledCharHeight,
27 | fontFamily: options.fontFamily,
28 | fontSize: options.fontSize,
29 | fontWeight: options.fontWeight,
30 | fontWeightBold: options.fontWeightBold,
31 | allowTransparency: options.allowTransparency,
32 | colors: clonedColors
33 | };
34 | }
35 |
36 | export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
37 | for (let i = 0; i < a.colors.ansi.length; i++) {
38 | if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) {
39 | return false;
40 | }
41 | }
42 | return a.devicePixelRatio === b.devicePixelRatio &&
43 | a.fontFamily === b.fontFamily &&
44 | a.fontSize === b.fontSize &&
45 | a.fontWeight === b.fontWeight &&
46 | a.fontWeightBold === b.fontWeightBold &&
47 | a.allowTransparency === b.allowTransparency &&
48 | a.scaledCharWidth === b.scaledCharWidth &&
49 | a.scaledCharHeight === b.scaledCharHeight &&
50 | a.colors.foreground === b.colors.foreground &&
51 | a.colors.background === b.colors.background;
52 | }
53 |
54 | export function is256Color(colorCode: number): boolean {
55 | return colorCode < DEFAULT_COLOR;
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/atlas/Constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | export const INVERTED_DEFAULT_COLOR = 257;
7 | export const DIM_OPACITY = 0.5;
8 |
9 | export const CHAR_ATLAS_CELL_SPACING = 1;
10 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/atlas/LRUMap.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | interface ILinkedListNode {
7 | prev: ILinkedListNode | null;
8 | next: ILinkedListNode | null;
9 | key: number | null;
10 | value: T | null;
11 | }
12 |
13 | export class LRUMap {
14 | private _map: { [key: number]: ILinkedListNode } = {};
15 | private _head: ILinkedListNode | null = null;
16 | private _tail: ILinkedListNode | null = null;
17 | private _nodePool: ILinkedListNode[] = [];
18 | public size: number = 0;
19 |
20 | constructor(public capacity: number) { }
21 |
22 | private _unlinkNode(node: ILinkedListNode): void {
23 | const prev = node.prev;
24 | const next = node.next;
25 | if (node === this._head) {
26 | this._head = next;
27 | }
28 | if (node === this._tail) {
29 | this._tail = prev;
30 | }
31 | if (prev !== null) {
32 | prev.next = next;
33 | }
34 | if (next !== null) {
35 | next.prev = prev;
36 | }
37 | }
38 |
39 | private _appendNode(node: ILinkedListNode): void {
40 | const tail = this._tail;
41 | if (tail !== null) {
42 | tail.next = node;
43 | }
44 | node.prev = tail;
45 | node.next = null;
46 | this._tail = node;
47 | if (this._head === null) {
48 | this._head = node;
49 | }
50 | }
51 |
52 | /**
53 | * Preallocate a bunch of linked-list nodes. Allocating these nodes ahead of time means that
54 | * they're more likely to live next to each other in memory, which seems to improve performance.
55 | *
56 | * Each empty object only consumes about 60 bytes of memory, so this is pretty cheap, even for
57 | * large maps.
58 | */
59 | public prealloc(count: number): void {
60 | const nodePool = this._nodePool;
61 | for (let i = 0; i < count; i++) {
62 | nodePool.push({
63 | prev: null,
64 | next: null,
65 | key: null,
66 | value: null
67 | });
68 | }
69 | }
70 |
71 | public get(key: number): T | null {
72 | // This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However,
73 | // it's faster than calling hasOwnProperty, and in our case, it would never overlap.
74 | const node = this._map[key];
75 | if (node !== undefined) {
76 | this._unlinkNode(node);
77 | this._appendNode(node);
78 | return node.value;
79 | }
80 | return null;
81 | }
82 |
83 | /**
84 | * Gets a value from a key without marking it as the most recently used item.
85 | */
86 | public peekValue(key: number): T | null {
87 | const node = this._map[key];
88 | if (node !== undefined) {
89 | return node.value;
90 | }
91 | return null;
92 | }
93 |
94 | public peek(): T | null {
95 | const head = this._head;
96 | return head === null ? null : head.value;
97 | }
98 |
99 | public set(key: number, value: T): void {
100 | // This is unsafe: See note above.
101 | let node = this._map[key];
102 | if (node !== undefined) {
103 | // already exists, we just need to mutate it and move it to the end of the list
104 | node = this._map[key];
105 | this._unlinkNode(node);
106 | node.value = value;
107 | } else if (this.size >= this.capacity) {
108 | // we're out of space: recycle the head node, move it to the tail
109 | node = this._head!;
110 | this._unlinkNode(node);
111 | delete this._map[node.key!];
112 | node.key = key;
113 | node.value = value;
114 | this._map[key] = node;
115 | } else {
116 | // make a new element
117 | const nodePool = this._nodePool;
118 | if (nodePool.length > 0) {
119 | // use a preallocated node if we can
120 | node = nodePool.pop()!;
121 | node.key = key;
122 | node.value = value;
123 | } else {
124 | node = {
125 | prev: null,
126 | next: null,
127 | key,
128 | value
129 | };
130 | }
131 | this._map[key] = node;
132 | this.size++;
133 | }
134 | this._appendNode(node);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/atlas/Types.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { FontWeight } from 'common/services/Services';
7 | import { IPartialColorSet } from 'browser/Types';
8 |
9 | export interface IGlyphIdentifier {
10 | chars: string;
11 | code: number;
12 | bg: number;
13 | fg: number;
14 | bold: boolean;
15 | dim: boolean;
16 | italic: boolean;
17 | }
18 |
19 | export interface ICharAtlasConfig {
20 | devicePixelRatio: number;
21 | fontSize: number;
22 | fontFamily: string;
23 | fontWeight: FontWeight;
24 | fontWeightBold: FontWeight;
25 | scaledCharWidth: number;
26 | scaledCharHeight: number;
27 | allowTransparency: boolean;
28 | colors: IPartialColorSet;
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/renderer/dom/DomRendererRowFactory.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IBufferLine } from 'common/Types';
7 | import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
8 | import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
9 | import { CellData } from 'common/buffer/CellData';
10 | import { IOptionsService } from 'common/services/Services';
11 | import { color, rgba } from 'browser/Color';
12 | import { IColorSet, IColor } from 'browser/Types';
13 |
14 | export const BOLD_CLASS = 'xterm-bold';
15 | export const DIM_CLASS = 'xterm-dim';
16 | export const ITALIC_CLASS = 'xterm-italic';
17 | export const UNDERLINE_CLASS = 'xterm-underline';
18 | export const CURSOR_CLASS = 'xterm-cursor';
19 | export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink';
20 | export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block';
21 | export const CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar';
22 | export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline';
23 |
24 | export class DomRendererRowFactory {
25 | private _workCell: CellData = new CellData();
26 |
27 | constructor(
28 | private readonly _document: Document,
29 | private readonly _optionsService: IOptionsService,
30 | private _colors: IColorSet
31 | ) {
32 | }
33 |
34 | public setColors(colors: IColorSet): void {
35 | this._colors = colors;
36 | }
37 |
38 | public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {
39 | const fragment = this._document.createDocumentFragment();
40 |
41 | // Find the line length first, this prevents the need to output a bunch of
42 | // empty cells at the end. This cannot easily be integrated into the main
43 | // loop below because of the colCount feature (which can be removed after we
44 | // properly support reflow and disallow data to go beyond the right-side of
45 | // the viewport).
46 | let lineLength = 0;
47 | for (let x = Math.min(lineData.length, cols) - 1; x >= 0; x--) {
48 | if (lineData.loadCell(x, this._workCell).getCode() !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) {
49 | lineLength = x + 1;
50 | break;
51 | }
52 | }
53 |
54 | for (let x = 0; x < lineLength; x++) {
55 | lineData.loadCell(x, this._workCell);
56 | const width = this._workCell.getWidth();
57 |
58 | // The character to the left is a wide character, drawing is owned by the char at x-1
59 | if (width === 0) {
60 | continue;
61 | }
62 |
63 | const charElement = this._document.createElement('span');
64 | if (width > 1) {
65 | charElement.style.width = `${cellWidth * width}px`;
66 | }
67 |
68 | if (isCursorRow && x === cursorX) {
69 | charElement.classList.add(CURSOR_CLASS);
70 |
71 | if (cursorBlink) {
72 | charElement.classList.add(CURSOR_BLINK_CLASS);
73 | }
74 |
75 | switch (cursorStyle) {
76 | case 'bar':
77 | charElement.classList.add(CURSOR_STYLE_BAR_CLASS);
78 | break;
79 | case 'underline':
80 | charElement.classList.add(CURSOR_STYLE_UNDERLINE_CLASS);
81 | break;
82 | default:
83 | charElement.classList.add(CURSOR_STYLE_BLOCK_CLASS);
84 | break;
85 | }
86 | }
87 |
88 | if (this._workCell.isBold()) {
89 | charElement.classList.add(BOLD_CLASS);
90 | }
91 |
92 | if (this._workCell.isItalic()) {
93 | charElement.classList.add(ITALIC_CLASS);
94 | }
95 |
96 | if (this._workCell.isDim()) {
97 | charElement.classList.add(DIM_CLASS);
98 | }
99 |
100 | if (this._workCell.isUnderline()) {
101 | charElement.classList.add(UNDERLINE_CLASS);
102 | }
103 |
104 | if (this._workCell.isInvisible()) {
105 | charElement.textContent = WHITESPACE_CELL_CHAR;
106 | } else {
107 | charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR;
108 | }
109 |
110 | let fg = this._workCell.getFgColor();
111 | let fgColorMode = this._workCell.getFgColorMode();
112 | let bg = this._workCell.getBgColor();
113 | let bgColorMode = this._workCell.getBgColorMode();
114 | const isInverse = !!this._workCell.isInverse();
115 | if (isInverse) {
116 | const temp = fg;
117 | fg = bg;
118 | bg = temp;
119 | const temp2 = fgColorMode;
120 | fgColorMode = bgColorMode;
121 | bgColorMode = temp2;
122 | }
123 |
124 | // Foreground
125 | switch (fgColorMode) {
126 | case Attributes.CM_P16:
127 | case Attributes.CM_P256:
128 | if (this._workCell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) {
129 | fg += 8;
130 | }
131 | if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) {
132 | charElement.classList.add(`xterm-fg-${fg}`);
133 | }
134 | break;
135 | case Attributes.CM_RGB:
136 | const color = rgba.toColor(
137 | (fg >> 16) & 0xFF,
138 | (fg >> 8) & 0xFF,
139 | (fg ) & 0xFF
140 | );
141 | if (!this._applyMinimumContrast(charElement, this._colors.background, color)) {
142 | this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`);
143 | }
144 | break;
145 | case Attributes.CM_DEFAULT:
146 | default:
147 | if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground)) {
148 | if (isInverse) {
149 | charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
150 | }
151 | }
152 | }
153 |
154 | // Background
155 | switch (bgColorMode) {
156 | case Attributes.CM_P16:
157 | case Attributes.CM_P256:
158 | charElement.classList.add(`xterm-bg-${bg}`);
159 | break;
160 | case Attributes.CM_RGB:
161 | this._addStyle(charElement, `background-color:#${padStart(bg.toString(16), '0', 6)}`);
162 | break;
163 | case Attributes.CM_DEFAULT:
164 | default:
165 | if (isInverse) {
166 | charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
167 | }
168 | }
169 |
170 | fragment.appendChild(charElement);
171 | }
172 | return fragment;
173 | }
174 |
175 | private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean {
176 | if (this._optionsService.options.minimumContrastRatio === 1) {
177 | return false;
178 | }
179 |
180 | // Try get from cache first
181 | let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg);
182 |
183 | // Calculate and store in cache
184 | if (adjustedColor === undefined) {
185 | adjustedColor = color.ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio);
186 | this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null);
187 | }
188 |
189 | if (adjustedColor) {
190 | this._addStyle(element, `color:${adjustedColor.css}`);
191 | return true;
192 | }
193 |
194 | return false;
195 | }
196 |
197 | private _addStyle(element: HTMLElement, style: string): void {
198 | element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`);
199 | }
200 | }
201 |
202 | function padStart(text: string, padChar: string, length: number): string {
203 | while (text.length < length) {
204 | text = padChar + text;
205 | }
206 | return text;
207 | }
208 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/selection/SelectionModel.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IBufferService } from 'common/services/Services';
7 |
8 | /**
9 | * Represents a selection within the buffer. This model only cares about column
10 | * and row coordinates, not wide characters.
11 | */
12 | export class SelectionModel {
13 | /**
14 | * Whether select all is currently active.
15 | */
16 | public isSelectAllActive: boolean = false;
17 |
18 | /**
19 | * The minimal length of the selection from the start position. When double
20 | * clicking on a word, the word will be selected which makes the selection
21 | * start at the start of the word and makes this variable the length.
22 | */
23 | public selectionStartLength: number = 0;
24 |
25 | /**
26 | * The [x, y] position the selection starts at.
27 | */
28 | public selectionStart: [number, number] | undefined;
29 |
30 | /**
31 | * The [x, y] position the selection ends at.
32 | */
33 | public selectionEnd: [number, number] | undefined;
34 |
35 | constructor(
36 | private _bufferService: IBufferService
37 | ) {
38 | }
39 |
40 | /**
41 | * Clears the current selection.
42 | */
43 | public clearSelection(): void {
44 | this.selectionStart = undefined;
45 | this.selectionEnd = undefined;
46 | this.isSelectAllActive = false;
47 | this.selectionStartLength = 0;
48 | }
49 |
50 | /**
51 | * The final selection start, taking into consideration select all.
52 | */
53 | public get finalSelectionStart(): [number, number] | undefined {
54 | if (this.isSelectAllActive) {
55 | return [0, 0];
56 | }
57 |
58 | if (!this.selectionEnd || !this.selectionStart) {
59 | return this.selectionStart;
60 | }
61 |
62 | return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart;
63 | }
64 |
65 | /**
66 | * The final selection end, taking into consideration select all, double click
67 | * word selection and triple click line selection.
68 | */
69 | public get finalSelectionEnd(): [number, number] | undefined {
70 | if (this.isSelectAllActive) {
71 | return [this._bufferService.cols, this._bufferService.buffer.ybase + this._bufferService.rows - 1];
72 | }
73 |
74 | if (!this.selectionStart) {
75 | return undefined;
76 | }
77 |
78 | // Use the selection start + length if the end doesn't exist or they're reversed
79 | if (!this.selectionEnd || this.areSelectionValuesReversed()) {
80 | const startPlusLength = this.selectionStart[0] + this.selectionStartLength;
81 | if (startPlusLength > this._bufferService.cols) {
82 | return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)];
83 | }
84 | return [startPlusLength, this.selectionStart[1]];
85 | }
86 |
87 | // Ensure the the word/line is selected after a double/triple click
88 | if (this.selectionStartLength) {
89 | // Select the larger of the two when start and end are on the same line
90 | if (this.selectionEnd[1] === this.selectionStart[1]) {
91 | return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]];
92 | }
93 | }
94 | return this.selectionEnd;
95 | }
96 |
97 | /**
98 | * Returns whether the selection start and end are reversed.
99 | */
100 | public areSelectionValuesReversed(): boolean {
101 | const start = this.selectionStart;
102 | const end = this.selectionEnd;
103 | if (!start || !end) {
104 | return false;
105 | }
106 | return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);
107 | }
108 |
109 | /**
110 | * Handle the buffer being trimmed, adjust the selection position.
111 | * @param amount The amount the buffer is being trimmed.
112 | * @return Whether a refresh is necessary.
113 | */
114 | public onTrim(amount: number): boolean {
115 | // Adjust the selection position based on the trimmed amount.
116 | if (this.selectionStart) {
117 | this.selectionStart[1] -= amount;
118 | }
119 | if (this.selectionEnd) {
120 | this.selectionEnd[1] -= amount;
121 | }
122 |
123 | // The selection has moved off the buffer, clear it.
124 | if (this.selectionEnd && this.selectionEnd[1] < 0) {
125 | this.clearSelection();
126 | return true;
127 | }
128 |
129 | // If the selection start is trimmed, ensure the start column is 0.
130 | if (this.selectionStart && this.selectionStart[1] < 0) {
131 | this.selectionStart[1] = 0;
132 | }
133 | return false;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/selection/Types.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | export interface ISelectionRedrawRequestEvent {
7 | start: [number, number] | undefined;
8 | end: [number, number] | undefined;
9 | columnSelectMode: boolean;
10 | }
11 |
12 | export interface ISelectionRequestScrollLinesEvent {
13 | amount: number;
14 | suppressScrollEvent: boolean;
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/services/CharSizeService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IOptionsService } from 'common/services/Services';
7 | import { IEvent, EventEmitter } from 'common/EventEmitter';
8 | import { ICharSizeService } from 'browser/services/Services';
9 |
10 | export class CharSizeService implements ICharSizeService {
11 | public serviceBrand: undefined;
12 |
13 | public width: number = 0;
14 | public height: number = 0;
15 | private _measureStrategy: IMeasureStrategy;
16 |
17 | public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }
18 |
19 | private _onCharSizeChange = new EventEmitter();
20 | public get onCharSizeChange(): IEvent { return this._onCharSizeChange.event; }
21 |
22 | constructor(
23 | document: Document,
24 | parentElement: HTMLElement,
25 | @IOptionsService private readonly _optionsService: IOptionsService
26 | ) {
27 | this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService);
28 | }
29 |
30 | public measure(): void {
31 | const result = this._measureStrategy.measure();
32 | if (result.width !== this.width || result.height !== this.height) {
33 | this.width = result.width;
34 | this.height = result.height;
35 | this._onCharSizeChange.fire();
36 | }
37 | }
38 | }
39 |
40 | interface IMeasureStrategy {
41 | measure(): IReadonlyMeasureResult;
42 | }
43 |
44 | interface IReadonlyMeasureResult {
45 | readonly width: number;
46 | readonly height: number;
47 | }
48 |
49 | interface IMeasureResult {
50 | width: number;
51 | height: number;
52 | }
53 |
54 | // TODO: For supporting browsers we should also provide a CanvasCharDimensionsProvider that uses ctx.measureText
55 | class DomMeasureStrategy implements IMeasureStrategy {
56 | private _result: IMeasureResult = { width: 0, height: 0 };
57 | private _measureElement: HTMLElement;
58 |
59 | constructor(
60 | private _document: Document,
61 | private _parentElement: HTMLElement,
62 | private _optionsService: IOptionsService
63 | ) {
64 | this._measureElement = this._document.createElement('span');
65 | this._measureElement.classList.add('xterm-char-measure-element');
66 | this._measureElement.textContent = 'W';
67 | this._measureElement.setAttribute('aria-hidden', 'true');
68 | this._parentElement.appendChild(this._measureElement);
69 | }
70 |
71 | public measure(): IReadonlyMeasureResult {
72 | this._measureElement.style.fontFamily = this._optionsService.options.fontFamily;
73 | this._measureElement.style.fontSize = `${this._optionsService.options.fontSize}px`;
74 |
75 | // Note that this triggers a synchronous layout
76 | const geometry = this._measureElement.getBoundingClientRect();
77 |
78 | // If values are 0 then the element is likely currently display:none, in which case we should
79 | // retain the previous value.
80 | if (geometry.width !== 0 && geometry.height !== 0) {
81 | this._result.width = geometry.width;
82 | this._result.height = Math.ceil(geometry.height);
83 | }
84 |
85 | return this._result;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/services/CoreBrowserService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { ICoreBrowserService } from './Services';
7 |
8 | export class CoreBrowserService implements ICoreBrowserService {
9 | public serviceBrand: undefined;
10 |
11 | constructor(
12 | private _textarea: HTMLTextAreaElement
13 | ) {
14 | }
15 |
16 | public get isFocused(): boolean {
17 | return document.activeElement === this._textarea && document.hasFocus();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/services/MouseService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { ICharSizeService, IRenderService, IMouseService } from './Services';
7 | import { getCoords, getRawByteCoords } from 'browser/input/Mouse';
8 |
9 | export class MouseService implements IMouseService {
10 | public serviceBrand: undefined;
11 |
12 | constructor(
13 | @IRenderService private readonly _renderService: IRenderService,
14 | @ICharSizeService private readonly _charSizeService: ICharSizeService
15 | ) {
16 | }
17 |
18 | public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
19 | return getCoords(
20 | event,
21 | element,
22 | colCount,
23 | rowCount,
24 | this._charSizeService.hasValidSize,
25 | this._renderService.dimensions.actualCellWidth,
26 | this._renderService.dimensions.actualCellHeight,
27 | isSelection
28 | );
29 | }
30 |
31 | public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined {
32 | const coords = this.getCoords(event, element, colCount, rowCount);
33 | return getRawByteCoords(coords);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/services/RenderService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
7 | import { RenderDebouncer } from 'browser/RenderDebouncer';
8 | import { EventEmitter, IEvent } from 'common/EventEmitter';
9 | import { Disposable } from 'common/Lifecycle';
10 | import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
11 | import { addDisposableDomListener } from 'browser/Lifecycle';
12 | import { IColorSet } from 'browser/Types';
13 | import { IOptionsService, IBufferService } from 'common/services/Services';
14 | import { ICharSizeService, IRenderService } from 'browser/services/Services';
15 |
16 | interface ISelectionState {
17 | start: [number, number] | undefined;
18 | end: [number, number] | undefined;
19 | columnSelectMode: boolean;
20 | }
21 |
22 | export class RenderService extends Disposable implements IRenderService {
23 | public serviceBrand: undefined;
24 |
25 | private _renderDebouncer: RenderDebouncer;
26 | private _screenDprMonitor: ScreenDprMonitor;
27 |
28 | private _isPaused: boolean = false;
29 | private _needsFullRefresh: boolean = false;
30 | private _isNextRenderRedrawOnly: boolean = true;
31 | private _needsSelectionRefresh: boolean = false;
32 | private _canvasWidth: number = 0;
33 | private _canvasHeight: number = 0;
34 | private _selectionState: ISelectionState = {
35 | start: undefined,
36 | end: undefined,
37 | columnSelectMode: false
38 | };
39 |
40 | private _onDimensionsChange = new EventEmitter();
41 | public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; }
42 | private _onRender = new EventEmitter<{ start: number, end: number }>();
43 | public get onRenderedBufferChange(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
44 | private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>();
45 | public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; }
46 |
47 | public get dimensions(): IRenderDimensions { return this._renderer.dimensions; }
48 |
49 | constructor(
50 | private _renderer: IRenderer,
51 | private _rowCount: number,
52 | screenElement: HTMLElement,
53 | @IOptionsService optionsService: IOptionsService,
54 | @ICharSizeService charSizeService: ICharSizeService,
55 | @IBufferService bufferService: IBufferService
56 | ) {
57 | super();
58 |
59 | this.register({ dispose: () => this._renderer.dispose() });
60 |
61 | this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end));
62 | this.register(this._renderDebouncer);
63 |
64 | this._screenDprMonitor = new ScreenDprMonitor();
65 | this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange());
66 | this.register(this._screenDprMonitor);
67 |
68 | this.register(bufferService.onResize(e => this._fullRefresh()));
69 | this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
70 | this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
71 |
72 | // No need to register this as renderer is explicitly disposed in RenderService.dispose
73 | this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
74 |
75 | // dprchange should handle this case, we need this as well for browsers that don't support the
76 | // matchMedia query.
77 | this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange()));
78 |
79 | // Detect whether IntersectionObserver is detected and enable renderer pause
80 | // and resume based on terminal visibility if so
81 | if ('IntersectionObserver' in window) {
82 | const observer = new IntersectionObserver(e => this._onIntersectionChange(e[e.length - 1]), { threshold: 0 });
83 | observer.observe(screenElement);
84 | this.register({ dispose: () => observer.disconnect() });
85 | }
86 | }
87 |
88 | private _onIntersectionChange(entry: IntersectionObserverEntry): void {
89 | this._isPaused = entry.intersectionRatio === 0;
90 | if (!this._isPaused && this._needsFullRefresh) {
91 | this.refreshRows(0, this._rowCount - 1);
92 | this._needsFullRefresh = false;
93 | }
94 | }
95 |
96 | public refreshRows(start: number, end: number, isRedrawOnly: boolean = false): void {
97 | if (this._isPaused) {
98 | this._needsFullRefresh = true;
99 | return;
100 | }
101 | if (!isRedrawOnly) {
102 | this._isNextRenderRedrawOnly = false;
103 | }
104 | this._renderDebouncer.refresh(start, end, this._rowCount);
105 | }
106 |
107 | private _renderRows(start: number, end: number): void {
108 | this._renderer.renderRows(start, end);
109 |
110 | // Update selection if needed
111 | if (this._needsSelectionRefresh) {
112 | this._renderer.onSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode);
113 | this._needsSelectionRefresh = false;
114 | }
115 |
116 | // Fire render event only if it was not a redraw
117 | if (!this._isNextRenderRedrawOnly) {
118 | this._onRender.fire({ start, end });
119 | }
120 | this._isNextRenderRedrawOnly = true;
121 | }
122 |
123 | public resize(cols: number, rows: number): void {
124 | this._rowCount = rows;
125 | this._fireOnCanvasResize();
126 | }
127 |
128 | public changeOptions(): void {
129 | this._renderer.onOptionsChanged();
130 | this.refreshRows(0, this._rowCount - 1);
131 | this._fireOnCanvasResize();
132 | }
133 |
134 | private _fireOnCanvasResize(): void {
135 | // Don't fire the event if the dimensions haven't changed
136 | if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) {
137 | return;
138 | }
139 | this._onDimensionsChange.fire(this._renderer.dimensions);
140 | }
141 |
142 | public dispose(): void {
143 | super.dispose();
144 | }
145 |
146 | public setRenderer(renderer: IRenderer): void {
147 | // TODO: RenderService should be the only one to dispose the renderer
148 | this._renderer.dispose();
149 | this._renderer = renderer;
150 | this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
151 |
152 | // Force a refresh
153 | this._needsSelectionRefresh = true;
154 | this._fullRefresh();
155 | }
156 |
157 | private _fullRefresh(): void {
158 | if (this._isPaused) {
159 | this._needsFullRefresh = true;
160 | } else {
161 | this.refreshRows(0, this._rowCount - 1);
162 | }
163 | }
164 |
165 | public setColors(colors: IColorSet): void {
166 | this._renderer.setColors(colors);
167 | this._fullRefresh();
168 | }
169 |
170 | public onDevicePixelRatioChange(): void {
171 | this._renderer.onDevicePixelRatioChange();
172 | this.refreshRows(0, this._rowCount - 1);
173 | }
174 |
175 | public onResize(cols: number, rows: number): void {
176 | this._renderer.onResize(cols, rows);
177 | this._fullRefresh();
178 | }
179 |
180 | // TODO: Is this useful when we have onResize?
181 | public onCharSizeChanged(): void {
182 | this._renderer.onCharSizeChanged();
183 | }
184 |
185 | public onBlur(): void {
186 | this._renderer.onBlur();
187 | }
188 |
189 | public onFocus(): void {
190 | this._renderer.onFocus();
191 | }
192 |
193 | public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
194 | this._selectionState.start = start;
195 | this._selectionState.end = end;
196 | this._selectionState.columnSelectMode = columnSelectMode;
197 | this._renderer.onSelectionChanged(start, end, columnSelectMode);
198 | }
199 |
200 | public onCursorMove(): void {
201 | this._renderer.onCursorMove();
202 | }
203 |
204 | public clear(): void {
205 | this._renderer.clear();
206 | }
207 |
208 | public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
209 | return this._renderer.registerCharacterJoiner(handler);
210 | }
211 |
212 | public deregisterCharacterJoiner(joinerId: number): boolean {
213 | return this._renderer.deregisterCharacterJoiner(joinerId);
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/services/Services.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IEvent } from 'common/EventEmitter';
7 | import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types';
8 | import { IColorSet } from 'browser/Types';
9 | import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
10 | import { createDecorator } from 'common/services/ServiceRegistry';
11 | import { IDisposable } from 'common/Types';
12 |
13 | export const ICharSizeService = createDecorator('CharSizeService');
14 | export interface ICharSizeService {
15 | serviceBrand: undefined;
16 |
17 | readonly width: number;
18 | readonly height: number;
19 | readonly hasValidSize: boolean;
20 |
21 | readonly onCharSizeChange: IEvent;
22 |
23 | measure(): void;
24 | }
25 |
26 | export const ICoreBrowserService = createDecorator('CoreBrowserService');
27 | export interface ICoreBrowserService {
28 | serviceBrand: undefined;
29 |
30 | readonly isFocused: boolean;
31 | }
32 |
33 | export const IMouseService = createDecorator('MouseService');
34 | export interface IMouseService {
35 | serviceBrand: undefined;
36 |
37 | getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined;
38 | getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined;
39 | }
40 |
41 | export const IRenderService = createDecorator('RenderService');
42 | export interface IRenderService extends IDisposable {
43 | serviceBrand: undefined;
44 |
45 | onDimensionsChange: IEvent;
46 | /**
47 | * Fires when buffer changes are rendered. This does not fire when only cursor
48 | * or selections are rendered.
49 | */
50 | onRenderedBufferChange: IEvent<{ start: number, end: number }>;
51 | onRefreshRequest: IEvent<{ start: number, end: number }>;
52 |
53 | dimensions: IRenderDimensions;
54 |
55 | refreshRows(start: number, end: number): void;
56 | resize(cols: number, rows: number): void;
57 | changeOptions(): void;
58 | setRenderer(renderer: IRenderer): void;
59 | setColors(colors: IColorSet): void;
60 | onDevicePixelRatioChange(): void;
61 | onResize(cols: number, rows: number): void;
62 | // TODO: Is this useful when we have onResize?
63 | onCharSizeChanged(): void;
64 | onBlur(): void;
65 | onFocus(): void;
66 | onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
67 | onCursorMove(): void;
68 | clear(): void;
69 | registerCharacterJoiner(handler: CharacterJoinerHandler): number;
70 | deregisterCharacterJoiner(joinerId: number): boolean;
71 | }
72 |
73 | export const ISelectionService = createDecorator('SelectionService');
74 | export interface ISelectionService {
75 | serviceBrand: undefined;
76 |
77 | readonly selectionText: string;
78 | readonly hasSelection: boolean;
79 | readonly selectionStart: [number, number] | undefined;
80 | readonly selectionEnd: [number, number] | undefined;
81 |
82 | readonly onLinuxMouseSelection: IEvent;
83 | readonly onRequestRedraw: IEvent;
84 | readonly onRequestScrollLines: IEvent;
85 | readonly onSelectionChange: IEvent;
86 |
87 | disable(): void;
88 | enable(): void;
89 | reset(): void;
90 | setSelection(row: number, col: number, length: number): void;
91 | selectAll(): void;
92 | selectLines(start: number, end: number): void;
93 | clearSelection(): void;
94 | isClickInSelection(event: MouseEvent): boolean;
95 | selectWordAtCursor(event: MouseEvent): void;
96 | shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean;
97 | shouldForceSelection(event: MouseEvent): boolean;
98 | refresh(isLinuxMouseSelection?: boolean): void;
99 | onMouseDown(event: MouseEvent): void;
100 | }
101 |
102 | export const ISoundService = createDecorator('SoundService');
103 | export interface ISoundService {
104 | serviceBrand: undefined;
105 |
106 | playBellSound(): void;
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/services/SoundService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IOptionsService } from 'common/services/Services';
7 | import { ISoundService } from 'browser/services/Services';
8 |
9 | export class SoundService implements ISoundService {
10 | public serviceBrand: undefined;
11 |
12 | private static _audioContext: AudioContext;
13 |
14 | public static get audioContext(): AudioContext | null {
15 | if (!SoundService._audioContext) {
16 | const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext;
17 | if (!audioContextCtor) {
18 | console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version');
19 | return null;
20 | }
21 | SoundService._audioContext = new audioContextCtor();
22 | }
23 | return SoundService._audioContext;
24 | }
25 |
26 | constructor(
27 | @IOptionsService private _optionsService: IOptionsService
28 | ) {
29 | }
30 |
31 | public playBellSound(): void {
32 | const ctx = SoundService.audioContext;
33 | if (!ctx) {
34 | return;
35 | }
36 | const bellAudioSource = ctx.createBufferSource();
37 | ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.options.bellSound)), (buffer) => {
38 | bellAudioSource.buffer = buffer;
39 | bellAudioSource.connect(ctx.destination);
40 | bellAudioSource.start(0);
41 | });
42 | }
43 |
44 | private _base64ToArrayBuffer(base64: string): ArrayBuffer {
45 | const binaryString = window.atob(base64);
46 | const len = binaryString.length;
47 | const bytes = new Uint8Array(len);
48 |
49 | for (let i = 0; i < len; i++) {
50 | bytes[i] = binaryString.charCodeAt(i);
51 | }
52 |
53 | return bytes.buffer;
54 | }
55 |
56 | private _removeMimeType(dataURI: string): string {
57 | // Split the input to get the mime-type and the data itself
58 | const splitUri = dataURI.split(',');
59 |
60 | // Return only the data
61 | return splitUri[1];
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/browser/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig-library-base",
3 | "compilerOptions": {
4 | "lib": [
5 | "dom",
6 | "es2015",
7 | ],
8 | "outDir": "../../out",
9 | "types": [
10 | "../../node_modules/@types/mocha"
11 | ],
12 | "baseUrl": "..",
13 | "paths": {
14 | "common/*": [ "./common/*" ]
15 | }
16 | },
17 | "include": [
18 | "./**/*",
19 | "../../typings/xterm.d.ts"
20 | ],
21 | "references": [
22 | { "path": "../common" }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/CircularList.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { ICircularList } from 'common/Types';
7 | import { EventEmitter, IEvent } from 'common/EventEmitter';
8 |
9 | export interface IInsertEvent {
10 | index: number;
11 | amount: number;
12 | }
13 |
14 | export interface IDeleteEvent {
15 | index: number;
16 | amount: number;
17 | }
18 |
19 | /**
20 | * Represents a circular list; a list with a maximum size that wraps around when push is called,
21 | * overriding values at the start of the list.
22 | */
23 | export class CircularList implements ICircularList {
24 | protected _array: (T | undefined)[];
25 | private _startIndex: number;
26 | private _length: number;
27 |
28 | public onDeleteEmitter = new EventEmitter();
29 | public get onDelete(): IEvent { return this.onDeleteEmitter.event; }
30 | public onInsertEmitter = new EventEmitter();
31 | public get onInsert(): IEvent { return this.onInsertEmitter.event; }
32 | public onTrimEmitter = new EventEmitter();
33 | public get onTrim(): IEvent { return this.onTrimEmitter.event; }
34 |
35 | constructor(
36 | private _maxLength: number
37 | ) {
38 | this._array = new Array(this._maxLength);
39 | this._startIndex = 0;
40 | this._length = 0;
41 | }
42 |
43 | public get maxLength(): number {
44 | return this._maxLength;
45 | }
46 |
47 | public set maxLength(newMaxLength: number) {
48 | // There was no change in maxLength, return early.
49 | if (this._maxLength === newMaxLength) {
50 | return;
51 | }
52 |
53 | // Reconstruct array, starting at index 0. Only transfer values from the
54 | // indexes 0 to length.
55 | const newArray = new Array(newMaxLength);
56 | for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {
57 | newArray[i] = this._array[this._getCyclicIndex(i)];
58 | }
59 | this._array = newArray;
60 | this._maxLength = newMaxLength;
61 | this._startIndex = 0;
62 | }
63 |
64 | public get length(): number {
65 | return this._length;
66 | }
67 |
68 | public set length(newLength: number) {
69 | if (newLength > this._length) {
70 | for (let i = this._length; i < newLength; i++) {
71 | this._array[i] = undefined;
72 | }
73 | }
74 | this._length = newLength;
75 | }
76 |
77 | /**
78 | * Gets the value at an index.
79 | *
80 | * Note that for performance reasons there is no bounds checking here, the index reference is
81 | * circular so this should always return a value and never throw.
82 | * @param index The index of the value to get.
83 | * @return The value corresponding to the index.
84 | */
85 | public get(index: number): T | undefined {
86 | return this._array[this._getCyclicIndex(index)];
87 | }
88 |
89 | /**
90 | * Sets the value at an index.
91 | *
92 | * Note that for performance reasons there is no bounds checking here, the index reference is
93 | * circular so this should always return a value and never throw.
94 | * @param index The index to set.
95 | * @param value The value to set.
96 | */
97 | public set(index: number, value: T | undefined): void {
98 | this._array[this._getCyclicIndex(index)] = value;
99 | }
100 |
101 | /**
102 | * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0
103 | * if the maximum length is reached.
104 | * @param value The value to push onto the list.
105 | */
106 | public push(value: T): void {
107 | this._array[this._getCyclicIndex(this._length)] = value;
108 | if (this._length === this._maxLength) {
109 | this._startIndex = ++this._startIndex % this._maxLength;
110 | this.onTrimEmitter.fire(1);
111 | } else {
112 | this._length++;
113 | }
114 | }
115 |
116 | /**
117 | * Advance ringbuffer index and return current element for recycling.
118 | * Note: The buffer must be full for this method to work.
119 | * @throws When the buffer is not full.
120 | */
121 | public recycle(): T {
122 | if (this._length !== this._maxLength) {
123 | throw new Error('Can only recycle when the buffer is full');
124 | }
125 | this._startIndex = ++this._startIndex % this._maxLength;
126 | this.onTrimEmitter.fire(1);
127 | return this._array[this._getCyclicIndex(this._length - 1)]!;
128 | }
129 |
130 | /**
131 | * Ringbuffer is at max length.
132 | */
133 | public get isFull(): boolean {
134 | return this._length === this._maxLength;
135 | }
136 |
137 | /**
138 | * Removes and returns the last value on the list.
139 | * @return The popped value.
140 | */
141 | public pop(): T | undefined {
142 | return this._array[this._getCyclicIndex(this._length-- - 1)];
143 | }
144 |
145 | /**
146 | * Deletes and/or inserts items at a particular index (in that order). Unlike
147 | * Array.prototype.splice, this operation does not return the deleted items as a new array in
148 | * order to save creating a new array. Note that this operation may shift all values in the list
149 | * in the worst case.
150 | * @param start The index to delete and/or insert.
151 | * @param deleteCount The number of elements to delete.
152 | * @param items The items to insert.
153 | */
154 | public splice(start: number, deleteCount: number, ...items: T[]): void {
155 | // Delete items
156 | if (deleteCount) {
157 | for (let i = start; i < this._length - deleteCount; i++) {
158 | this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];
159 | }
160 | this._length -= deleteCount;
161 | }
162 |
163 | // Add items
164 | for (let i = this._length - 1; i >= start; i--) {
165 | this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];
166 | }
167 | for (let i = 0; i < items.length; i++) {
168 | this._array[this._getCyclicIndex(start + i)] = items[i];
169 | }
170 |
171 | // Adjust length as needed
172 | if (this._length + items.length > this._maxLength) {
173 | const countToTrim = (this._length + items.length) - this._maxLength;
174 | this._startIndex += countToTrim;
175 | this._length = this._maxLength;
176 | this.onTrimEmitter.fire(countToTrim);
177 | } else {
178 | this._length += items.length;
179 | }
180 | }
181 |
182 | /**
183 | * Trims a number of items from the start of the list.
184 | * @param count The number of items to remove.
185 | */
186 | public trimStart(count: number): void {
187 | if (count > this._length) {
188 | count = this._length;
189 | }
190 | this._startIndex += count;
191 | this._length -= count;
192 | this.onTrimEmitter.fire(count);
193 | }
194 |
195 | public shiftElements(start: number, count: number, offset: number): void {
196 | if (count <= 0) {
197 | return;
198 | }
199 | if (start < 0 || start >= this._length) {
200 | throw new Error('start argument out of range');
201 | }
202 | if (start + offset < 0) {
203 | throw new Error('Cannot shift elements in list beyond index 0');
204 | }
205 |
206 | if (offset > 0) {
207 | for (let i = count - 1; i >= 0; i--) {
208 | this.set(start + i + offset, this.get(start + i));
209 | }
210 | const expandListBy = (start + count + offset) - this._length;
211 | if (expandListBy > 0) {
212 | this._length += expandListBy;
213 | while (this._length > this._maxLength) {
214 | this._length--;
215 | this._startIndex++;
216 | this.onTrimEmitter.fire(1);
217 | }
218 | }
219 | } else {
220 | for (let i = 0; i < count; i++) {
221 | this.set(start + i + offset, this.get(start + i));
222 | }
223 | }
224 | }
225 |
226 | /**
227 | * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the
228 | * backing array to get the element associated with the regular index.
229 | * @param index The regular index.
230 | * @returns The cyclic index.
231 | */
232 | private _getCyclicIndex(index: number): number {
233 | return (this._startIndex + index) % this._maxLength;
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/Clone.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | /*
7 | * A simple utility for cloning values
8 | */
9 | export function clone(val: T, depth: number = 5): T {
10 | if (typeof val !== 'object') {
11 | return val;
12 | }
13 |
14 | // If we're cloning an array, use an array as the base, otherwise use an object
15 | const clonedObject: any = Array.isArray(val) ? [] : {};
16 |
17 | for (const key in val) {
18 | // Recursively clone eack item unless we're at the maximum depth
19 | clonedObject[key] = depth <= 1 ? val[key] : (val[key] ? clone(val[key], depth - 1) : val[key]);
20 | }
21 |
22 | return clonedObject as T;
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/EventEmitter.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IDisposable } from 'common/Types';
7 |
8 | interface IListener {
9 | (arg1: T, arg2: U): void;
10 | }
11 |
12 | export interface IEvent {
13 | (listener: (arg1: T, arg2: U) => any): IDisposable;
14 | }
15 |
16 | export interface IEventEmitter {
17 | event: IEvent;
18 | fire(arg1: T, arg2: U): void;
19 | dispose(): void;
20 | }
21 |
22 | export class EventEmitter implements IEventEmitter {
23 | private _listeners: IListener[] = [];
24 | private _event?: IEvent;
25 | private _disposed: boolean = false;
26 |
27 | public get event(): IEvent {
28 | if (!this._event) {
29 | this._event = (listener: (arg1: T, arg2: U) => any) => {
30 | this._listeners.push(listener);
31 | const disposable = {
32 | dispose: () => {
33 | if (!this._disposed) {
34 | for (let i = 0; i < this._listeners.length; i++) {
35 | if (this._listeners[i] === listener) {
36 | this._listeners.splice(i, 1);
37 | return;
38 | }
39 | }
40 | }
41 | }
42 | };
43 | return disposable;
44 | };
45 | }
46 | return this._event;
47 | }
48 |
49 | public fire(arg1: T, arg2: U): void {
50 | const queue: IListener[] = [];
51 | for (let i = 0; i < this._listeners.length; i++) {
52 | queue.push(this._listeners[i]);
53 | }
54 | for (let i = 0; i < queue.length; i++) {
55 | queue[i].call(undefined, arg1, arg2);
56 | }
57 | }
58 |
59 | public dispose(): void {
60 | if (this._listeners) {
61 | this._listeners.length = 0;
62 | }
63 | this._disposed = true;
64 | }
65 | }
66 |
67 | export function forwardEvent(from: IEvent, to: IEventEmitter): IDisposable {
68 | return from(e => to.fire(e));
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/Lifecycle.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IDisposable } from 'common/Types';
7 |
8 | /**
9 | * A base class that can be extended to provide convenience methods for managing the lifecycle of an
10 | * object and its components.
11 | */
12 | export abstract class Disposable implements IDisposable {
13 | protected _disposables: IDisposable[] = [];
14 | protected _isDisposed: boolean = false;
15 |
16 | constructor() {
17 | }
18 |
19 | /**
20 | * Disposes the object, triggering the `dispose` method on all registered IDisposables.
21 | */
22 | public dispose(): void {
23 | this._isDisposed = true;
24 | for (const d of this._disposables) {
25 | d.dispose();
26 | }
27 | this._disposables.length = 0;
28 | }
29 |
30 | /**
31 | * Registers a disposable object.
32 | * @param d The disposable to register.
33 | * @returns The disposable.
34 | */
35 | public register(d: T): T {
36 | this._disposables.push(d);
37 | return d;
38 | }
39 |
40 | /**
41 | * Unregisters a disposable object if it has been registered, if not do
42 | * nothing.
43 | * @param d The disposable to unregister.
44 | */
45 | public unregister(d: T): void {
46 | const index = this._disposables.indexOf(d);
47 | if (index !== -1) {
48 | this._disposables.splice(index, 1);
49 | }
50 | }
51 | }
52 |
53 | /**
54 | * Dispose of all disposables in an array and set its length to 0.
55 | */
56 | export function disposeArray(disposables: IDisposable[]): void {
57 | for (const d of disposables) {
58 | d.dispose();
59 | }
60 | disposables.length = 0;
61 | }
62 |
63 | /**
64 | * Creates a disposable that will dispose of an array of disposables when disposed.
65 | */
66 | export function getDisposeArrayDisposable(array: IDisposable[]): IDisposable {
67 | return { dispose: () => disposeArray(array) };
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/Platform.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | interface INavigator {
7 | userAgent: string;
8 | language: string;
9 | platform: string;
10 | }
11 |
12 | // We're declaring a navigator global here as we expect it in all runtimes (node and browser), but
13 | // we want this module to live in common.
14 | declare const navigator: INavigator;
15 |
16 | const isNode = (typeof navigator === 'undefined') ? true : false;
17 | const userAgent = (isNode) ? 'node' : navigator.userAgent;
18 | const platform = (isNode) ? 'node' : navigator.platform;
19 |
20 | export const isFirefox = !!~userAgent.indexOf('Firefox');
21 | export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
22 |
23 | // Find the users platform. We use this to interpret the meta key
24 | // and ISO third level shifts.
25 | // http://stackoverflow.com/q/19877924/577598
26 | export const isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);
27 | export const isIpad = platform === 'iPad';
28 | export const isIphone = platform === 'iPhone';
29 | export const isWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);
30 | export const isLinux = platform.indexOf('Linux') >= 0;
31 |
32 | /**
33 | * Return if the given array contains the given element
34 | * @param arr The array to search for the given element.
35 | * @param el The element to look for into the array
36 | */
37 | function contains(arr: any[], el: any): boolean {
38 | return arr.indexOf(el) >= 0;
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/TypedArrayUtils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
7 |
8 |
9 | /**
10 | * polyfill for TypedArray.fill
11 | * This is needed to support .fill in all safari versions and IE 11.
12 | */
13 | export function fill(array: T, value: number, start?: number, end?: number): T {
14 | // all modern engines that support .fill
15 | if (array.fill) {
16 | return array.fill(value, start, end) as T;
17 | }
18 | return fillFallback(array, value, start, end);
19 | }
20 |
21 | export function fillFallback(array: T, value: number, start: number = 0, end: number = array.length): T {
22 | // safari and IE 11
23 | // since IE 11 does not support Array.prototype.fill either
24 | // we cannot use the suggested polyfill from MDN
25 | // instead we simply fall back to looping
26 | if (start >= array.length) {
27 | return array;
28 | }
29 | start = (array.length + start) % array.length;
30 | if (end >= array.length) {
31 | end = array.length;
32 | } else {
33 | end = (array.length + end) % array.length;
34 | }
35 | for (let i = start; i < end; ++i) {
36 | array[i] = value;
37 | }
38 | return array;
39 | }
40 |
41 | /**
42 | * Concat two typed arrays `a` and `b`.
43 | * Returns a new typed array.
44 | */
45 | export function concat(a: T, b: T): T {
46 | const result = new (a.constructor as any)(a.length + b.length);
47 | result.set(a);
48 | result.set(b, a.length);
49 | return result;
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/WindowsMode.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
7 | import { IBufferService } from 'common/services/Services';
8 |
9 | export function updateWindowsModeWrappedState(bufferService: IBufferService): void {
10 | // Winpty does not support wraparound mode which means that lines will never
11 | // be marked as wrapped. This causes issues for things like copying a line
12 | // retaining the wrapped new line characters or if consumers are listening
13 | // in on the data stream.
14 | //
15 | // The workaround for this is to listen to every incoming line feed and mark
16 | // the line as wrapped if the last character in the previous line is not a
17 | // space. This is certainly not without its problems, but generally on
18 | // Windows when text reaches the end of the terminal it's likely going to be
19 | // wrapped.
20 | const line = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y - 1);
21 | const lastChar = line?.get(bufferService.cols - 1);
22 |
23 | const nextLine = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y);
24 | if (nextLine && lastChar) {
25 | nextLine.isWrapped = (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/buffer/AttributeData.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IAttributeData, IColorRGB, IExtendedAttrs } from 'common/Types';
7 | import { Attributes, FgFlags, BgFlags, UnderlineStyle } from 'common/buffer/Constants';
8 |
9 | export class AttributeData implements IAttributeData {
10 | public static toColorRGB(value: number): IColorRGB {
11 | return [
12 | value >>> Attributes.RED_SHIFT & 255,
13 | value >>> Attributes.GREEN_SHIFT & 255,
14 | value & 255
15 | ];
16 | }
17 |
18 | public static fromColorRGB(value: IColorRGB): number {
19 | return (value[0] & 255) << Attributes.RED_SHIFT | (value[1] & 255) << Attributes.GREEN_SHIFT | value[2] & 255;
20 | }
21 |
22 | public clone(): IAttributeData {
23 | const newObj = new AttributeData();
24 | newObj.fg = this.fg;
25 | newObj.bg = this.bg;
26 | newObj.extended = this.extended.clone();
27 | return newObj;
28 | }
29 |
30 | // data
31 | public fg = 0;
32 | public bg = 0;
33 | public extended = new ExtendedAttrs();
34 |
35 | // flags
36 | public isInverse(): number { return this.fg & FgFlags.INVERSE; }
37 | public isBold(): number { return this.fg & FgFlags.BOLD; }
38 | public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; }
39 | public isBlink(): number { return this.fg & FgFlags.BLINK; }
40 | public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; }
41 | public isItalic(): number { return this.bg & BgFlags.ITALIC; }
42 | public isDim(): number { return this.bg & BgFlags.DIM; }
43 |
44 | // color modes
45 | public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; }
46 | public getBgColorMode(): number { return this.bg & Attributes.CM_MASK; }
47 | public isFgRGB(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_RGB; }
48 | public isBgRGB(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_RGB; }
49 | public isFgPalette(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.fg & Attributes.CM_MASK) === Attributes.CM_P256; }
50 | public isBgPalette(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.bg & Attributes.CM_MASK) === Attributes.CM_P256; }
51 | public isFgDefault(): boolean { return (this.fg & Attributes.CM_MASK) === 0; }
52 | public isBgDefault(): boolean { return (this.bg & Attributes.CM_MASK) === 0; }
53 | public isAttributeDefault(): boolean { return this.fg === 0 && this.bg === 0; }
54 |
55 | // colors
56 | public getFgColor(): number {
57 | switch (this.fg & Attributes.CM_MASK) {
58 | case Attributes.CM_P16:
59 | case Attributes.CM_P256: return this.fg & Attributes.PCOLOR_MASK;
60 | case Attributes.CM_RGB: return this.fg & Attributes.RGB_MASK;
61 | default: return -1; // CM_DEFAULT defaults to -1
62 | }
63 | }
64 | public getBgColor(): number {
65 | switch (this.bg & Attributes.CM_MASK) {
66 | case Attributes.CM_P16:
67 | case Attributes.CM_P256: return this.bg & Attributes.PCOLOR_MASK;
68 | case Attributes.CM_RGB: return this.bg & Attributes.RGB_MASK;
69 | default: return -1; // CM_DEFAULT defaults to -1
70 | }
71 | }
72 |
73 | // extended attrs
74 | public hasExtendedAttrs(): number {
75 | return this.bg & BgFlags.HAS_EXTENDED;
76 | }
77 | public updateExtended(): void {
78 | if (this.extended.isEmpty()) {
79 | this.bg &= ~BgFlags.HAS_EXTENDED;
80 | } else {
81 | this.bg |= BgFlags.HAS_EXTENDED;
82 | }
83 | }
84 | public getUnderlineColor(): number {
85 | if ((this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor) {
86 | switch (this.extended.underlineColor & Attributes.CM_MASK) {
87 | case Attributes.CM_P16:
88 | case Attributes.CM_P256: return this.extended.underlineColor & Attributes.PCOLOR_MASK;
89 | case Attributes.CM_RGB: return this.extended.underlineColor & Attributes.RGB_MASK;
90 | default: return this.getFgColor();
91 | }
92 | }
93 | return this.getFgColor();
94 | }
95 | public getUnderlineColorMode(): number {
96 | return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
97 | ? this.extended.underlineColor & Attributes.CM_MASK
98 | : this.getFgColorMode();
99 | }
100 | public isUnderlineColorRGB(): boolean {
101 | return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
102 | ? (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_RGB
103 | : this.isFgRGB();
104 | }
105 | public isUnderlineColorPalette(): boolean {
106 | return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
107 | ? (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_P16
108 | || (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_P256
109 | : this.isFgPalette();
110 | }
111 | public isUnderlineColorDefault(): boolean {
112 | return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
113 | ? (this.extended.underlineColor & Attributes.CM_MASK) === 0
114 | : this.isFgDefault();
115 | }
116 | public getUnderlineStyle(): UnderlineStyle {
117 | return this.fg & FgFlags.UNDERLINE
118 | ? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE)
119 | : UnderlineStyle.NONE;
120 | }
121 | }
122 |
123 |
124 | /**
125 | * Extended attributes for a cell.
126 | * Holds information about different underline styles and color.
127 | */
128 | export class ExtendedAttrs implements IExtendedAttrs {
129 | constructor(
130 | // underline style, NONE is empty
131 | public underlineStyle: UnderlineStyle = UnderlineStyle.NONE,
132 | // underline color, -1 is empty (same as FG)
133 | public underlineColor: number = -1
134 | ) {}
135 |
136 | public clone(): IExtendedAttrs {
137 | return new ExtendedAttrs(this.underlineStyle, this.underlineColor);
138 | }
139 |
140 | /**
141 | * Convenient method to indicate whether the object holds no additional information,
142 | * that needs to be persistant in the buffer.
143 | */
144 | public isEmpty(): boolean {
145 | return this.underlineStyle === UnderlineStyle.NONE;
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/buffer/BufferSet.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IBuffer, IBufferSet } from 'common/buffer/Types';
7 | import { IAttributeData } from 'common/Types';
8 | import { Buffer } from 'common/buffer/Buffer';
9 | import { EventEmitter, IEvent } from 'common/EventEmitter';
10 | import { IOptionsService, IBufferService } from 'common/services/Services';
11 | import { Disposable } from 'common/Lifecycle';
12 |
13 | /**
14 | * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and
15 | * provides also utilities for working with them.
16 | */
17 | export class BufferSet extends Disposable implements IBufferSet {
18 | private _normal: Buffer;
19 | private _alt: Buffer;
20 | private _activeBuffer: Buffer;
21 |
22 |
23 | private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>());
24 | public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; }
25 |
26 | /**
27 | * Create a new BufferSet for the given terminal.
28 | * @param _terminal - The terminal the BufferSet will belong to
29 | */
30 | constructor(
31 | optionsService: IOptionsService,
32 | bufferService: IBufferService
33 | ) {
34 | super();
35 |
36 | this._normal = new Buffer(true, optionsService, bufferService);
37 | this._normal.fillViewportRows();
38 |
39 | // The alt buffer should never have scrollback.
40 | // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
41 | this._alt = new Buffer(false, optionsService, bufferService);
42 | this._activeBuffer = this._normal;
43 |
44 | this.setupTabStops();
45 | }
46 |
47 | /**
48 | * Returns the alt Buffer of the BufferSet
49 | */
50 | public get alt(): Buffer {
51 | return this._alt;
52 | }
53 |
54 | /**
55 | * Returns the normal Buffer of the BufferSet
56 | */
57 | public get active(): Buffer {
58 | return this._activeBuffer;
59 | }
60 |
61 | /**
62 | * Returns the currently active Buffer of the BufferSet
63 | */
64 | public get normal(): Buffer {
65 | return this._normal;
66 | }
67 |
68 | /**
69 | * Sets the normal Buffer of the BufferSet as its currently active Buffer
70 | */
71 | public activateNormalBuffer(): void {
72 | if (this._activeBuffer === this._normal) {
73 | return;
74 | }
75 | this._normal.x = this._alt.x;
76 | this._normal.y = this._alt.y;
77 | // The alt buffer should always be cleared when we switch to the normal
78 | // buffer. This frees up memory since the alt buffer should always be new
79 | // when activated.
80 | this._alt.clear();
81 | this._activeBuffer = this._normal;
82 | this._onBufferActivate.fire({
83 | activeBuffer: this._normal,
84 | inactiveBuffer: this._alt
85 | });
86 | }
87 |
88 | /**
89 | * Sets the alt Buffer of the BufferSet as its currently active Buffer
90 | */
91 | public activateAltBuffer(fillAttr?: IAttributeData): void {
92 | if (this._activeBuffer === this._alt) {
93 | return;
94 | }
95 | // Since the alt buffer is always cleared when the normal buffer is
96 | // activated, we want to fill it when switching to it.
97 | this._alt.fillViewportRows(fillAttr);
98 | this._alt.x = this._normal.x;
99 | this._alt.y = this._normal.y;
100 | this._activeBuffer = this._alt;
101 | this._onBufferActivate.fire({
102 | activeBuffer: this._alt,
103 | inactiveBuffer: this._normal
104 | });
105 | }
106 |
107 | /**
108 | * Resizes both normal and alt buffers, adjusting their data accordingly.
109 | * @param newCols The new number of columns.
110 | * @param newRows The new number of rows.
111 | */
112 | public resize(newCols: number, newRows: number): void {
113 | this._normal.resize(newCols, newRows);
114 | this._alt.resize(newCols, newRows);
115 | }
116 |
117 | /**
118 | * Setup the tab stops.
119 | * @param i The index to start setting up tab stops from.
120 | */
121 | public setupTabStops(i?: number): void {
122 | this._normal.setupTabStops(i);
123 | this._alt.setupTabStops(i);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/buffer/CellData.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { CharData, ICellData, IExtendedAttrs } from 'common/Types';
7 | import { stringFromCodePoint } from 'common/input/TextDecoder';
8 | import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, Content } from 'common/buffer/Constants';
9 | import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
10 |
11 | /**
12 | * CellData - represents a single Cell in the terminal buffer.
13 | */
14 | export class CellData extends AttributeData implements ICellData {
15 | /** Helper to create CellData from CharData. */
16 | public static fromCharData(value: CharData): CellData {
17 | const obj = new CellData();
18 | obj.setFromCharData(value);
19 | return obj;
20 | }
21 | /** Primitives from terminal buffer. */
22 | public content = 0;
23 | public fg = 0;
24 | public bg = 0;
25 | public extended: IExtendedAttrs = new ExtendedAttrs();
26 | public combinedData = '';
27 | /** Whether cell contains a combined string. */
28 | public isCombined(): number {
29 | return this.content & Content.IS_COMBINED_MASK;
30 | }
31 | /** Width of the cell. */
32 | public getWidth(): number {
33 | return this.content >> Content.WIDTH_SHIFT;
34 | }
35 | /** JS string of the content. */
36 | public getChars(): string {
37 | if (this.content & Content.IS_COMBINED_MASK) {
38 | return this.combinedData;
39 | }
40 | if (this.content & Content.CODEPOINT_MASK) {
41 | return stringFromCodePoint(this.content & Content.CODEPOINT_MASK);
42 | }
43 | return '';
44 | }
45 | /**
46 | * Codepoint of cell
47 | * Note this returns the UTF32 codepoint of single chars,
48 | * if content is a combined string it returns the codepoint
49 | * of the last char in string to be in line with code in CharData.
50 | * */
51 | public getCode(): number {
52 | return (this.isCombined())
53 | ? this.combinedData.charCodeAt(this.combinedData.length - 1)
54 | : this.content & Content.CODEPOINT_MASK;
55 | }
56 | /** Set data from CharData */
57 | public setFromCharData(value: CharData): void {
58 | this.fg = value[CHAR_DATA_ATTR_INDEX];
59 | this.bg = 0;
60 | let combined = false;
61 | // surrogates and combined strings need special treatment
62 | if (value[CHAR_DATA_CHAR_INDEX].length > 2) {
63 | combined = true;
64 | }
65 | else if (value[CHAR_DATA_CHAR_INDEX].length === 2) {
66 | const code = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0);
67 | // if the 2-char string is a surrogate create single codepoint
68 | // everything else is combined
69 | if (0xD800 <= code && code <= 0xDBFF) {
70 | const second = value[CHAR_DATA_CHAR_INDEX].charCodeAt(1);
71 | if (0xDC00 <= second && second <= 0xDFFF) {
72 | this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
73 | }
74 | else {
75 | combined = true;
76 | }
77 | }
78 | else {
79 | combined = true;
80 | }
81 | }
82 | else {
83 | this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
84 | }
85 | if (combined) {
86 | this.combinedData = value[CHAR_DATA_CHAR_INDEX];
87 | this.content = Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
88 | }
89 | }
90 | /** Get data as CharData. */
91 | public getAsCharData(): CharData {
92 | return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/buffer/Constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | export const DEFAULT_COLOR = 256;
7 | export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
8 |
9 | export const CHAR_DATA_ATTR_INDEX = 0;
10 | export const CHAR_DATA_CHAR_INDEX = 1;
11 | export const CHAR_DATA_WIDTH_INDEX = 2;
12 | export const CHAR_DATA_CODE_INDEX = 3;
13 |
14 | /**
15 | * Null cell - a real empty cell (containing nothing).
16 | * Note that code should always be 0 for a null cell as
17 | * several test condition of the buffer line rely on this.
18 | */
19 | export const NULL_CELL_CHAR = '';
20 | export const NULL_CELL_WIDTH = 1;
21 | export const NULL_CELL_CODE = 0;
22 |
23 | /**
24 | * Whitespace cell.
25 | * This is meant as a replacement for empty cells when needed
26 | * during rendering lines to preserve correct aligment.
27 | */
28 | export const WHITESPACE_CELL_CHAR = ' ';
29 | export const WHITESPACE_CELL_WIDTH = 1;
30 | export const WHITESPACE_CELL_CODE = 32;
31 |
32 | /**
33 | * Bitmasks for accessing data in `content`.
34 | */
35 | export const enum Content {
36 | /**
37 | * bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)
38 | * read: `codepoint = content & Content.codepointMask;`
39 | * write: `content |= codepoint & Content.codepointMask;`
40 | * shortcut if precondition `codepoint <= 0x10FFFF` is met:
41 | * `content |= codepoint;`
42 | */
43 | CODEPOINT_MASK = 0x1FFFFF,
44 |
45 | /**
46 | * bit 22 flag indication whether a cell contains combined content
47 | * read: `isCombined = content & Content.isCombined;`
48 | * set: `content |= Content.isCombined;`
49 | * clear: `content &= ~Content.isCombined;`
50 | */
51 | IS_COMBINED_MASK = 0x200000, // 1 << 21
52 |
53 | /**
54 | * bit 1..22 mask to check whether a cell contains any string data
55 | * we need to check for codepoint and isCombined bits to see
56 | * whether a cell contains anything
57 | * read: `isEmpty = !(content & Content.hasContent)`
58 | */
59 | HAS_CONTENT_MASK = 0x3FFFFF,
60 |
61 | /**
62 | * bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2)
63 | * read: `width = (content & Content.widthMask) >> Content.widthShift;`
64 | * `hasWidth = content & Content.widthMask;`
65 | * as long as wcwidth is highest value in `content`:
66 | * `width = content >> Content.widthShift;`
67 | * write: `content |= (width << Content.widthShift) & Content.widthMask;`
68 | * shortcut if precondition `0 <= width <= 3` is met:
69 | * `content |= width << Content.widthShift;`
70 | */
71 | WIDTH_MASK = 0xC00000, // 3 << 22
72 | WIDTH_SHIFT = 22
73 | }
74 |
75 | export const enum Attributes {
76 | /**
77 | * bit 1..8 blue in RGB, color in P256 and P16
78 | */
79 | BLUE_MASK = 0xFF,
80 | BLUE_SHIFT = 0,
81 | PCOLOR_MASK = 0xFF,
82 | PCOLOR_SHIFT = 0,
83 |
84 | /**
85 | * bit 9..16 green in RGB
86 | */
87 | GREEN_MASK = 0xFF00,
88 | GREEN_SHIFT = 8,
89 |
90 | /**
91 | * bit 17..24 red in RGB
92 | */
93 | RED_MASK = 0xFF0000,
94 | RED_SHIFT = 16,
95 |
96 | /**
97 | * bit 25..26 color mode: DEFAULT (0) | P16 (1) | P256 (2) | RGB (3)
98 | */
99 | CM_MASK = 0x3000000,
100 | CM_DEFAULT = 0,
101 | CM_P16 = 0x1000000,
102 | CM_P256 = 0x2000000,
103 | CM_RGB = 0x3000000,
104 |
105 | /**
106 | * bit 1..24 RGB room
107 | */
108 | RGB_MASK = 0xFFFFFF
109 | }
110 |
111 | export const enum FgFlags {
112 | /**
113 | * bit 27..31 (32th bit unused)
114 | */
115 | INVERSE = 0x4000000,
116 | BOLD = 0x8000000,
117 | UNDERLINE = 0x10000000,
118 | BLINK = 0x20000000,
119 | INVISIBLE = 0x40000000
120 | }
121 |
122 | export const enum BgFlags {
123 | /**
124 | * bit 27..32 (upper 3 unused)
125 | */
126 | ITALIC = 0x4000000,
127 | DIM = 0x8000000,
128 | HAS_EXTENDED = 0x10000000
129 | }
130 |
131 | export const enum UnderlineStyle {
132 | NONE = 0,
133 | SINGLE = 1,
134 | DOUBLE = 2,
135 | CURLY = 3,
136 | DOTTED = 4,
137 | DASHED = 5
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/buffer/Marker.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { EventEmitter, IEvent } from 'common/EventEmitter';
7 | import { Disposable } from 'common/Lifecycle';
8 | import { IMarker } from 'common/Types';
9 |
10 | export class Marker extends Disposable implements IMarker {
11 | private static _nextId = 1;
12 |
13 | private _id: number = Marker._nextId++;
14 | public isDisposed: boolean = false;
15 |
16 | public get id(): number { return this._id; }
17 |
18 | private _onDispose = new EventEmitter();
19 | public get onDispose(): IEvent { return this._onDispose.event; }
20 |
21 | constructor(
22 | public line: number
23 | ) {
24 | super();
25 | }
26 |
27 | public dispose(): void {
28 | if (this.isDisposed) {
29 | return;
30 | }
31 | this.isDisposed = true;
32 | this.line = -1;
33 | // Emit before super.dispose such that dispose listeners get a change to react
34 | this._onDispose.fire();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/buffer/Types.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IAttributeData, ICircularList, IBufferLine, ICellData, IMarker, ICharset, IDisposable } from 'common/Types';
7 | import { IEvent } from 'common/EventEmitter';
8 |
9 | // BufferIndex denotes a position in the buffer: [rowIndex, colIndex]
10 | export type BufferIndex = [number, number];
11 |
12 | export interface IBufferStringIteratorResult {
13 | range: {first: number, last: number};
14 | content: string;
15 | }
16 |
17 | export interface IBufferStringIterator {
18 | hasNext(): boolean;
19 | next(): IBufferStringIteratorResult;
20 | }
21 |
22 | export interface IBuffer {
23 | readonly lines: ICircularList;
24 | ydisp: number;
25 | ybase: number;
26 | y: number;
27 | x: number;
28 | tabs: any;
29 | scrollBottom: number;
30 | scrollTop: number;
31 | hasScrollback: boolean;
32 | savedY: number;
33 | savedX: number;
34 | savedCharset: ICharset | undefined;
35 | savedCurAttrData: IAttributeData;
36 | isCursorInViewport: boolean;
37 | markers: IMarker[];
38 | translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;
39 | getWrappedRangeForLine(y: number): { first: number, last: number };
40 | nextStop(x?: number): number;
41 | prevStop(x?: number): number;
42 | getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine;
43 | stringIndexToBufferIndex(lineIndex: number, stringIndex: number, trimRight?: boolean): number[];
44 | iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator;
45 | getNullCell(attr?: IAttributeData): ICellData;
46 | getWhitespaceCell(attr?: IAttributeData): ICellData;
47 | addMarker(y: number): IMarker;
48 | }
49 |
50 | export interface IBufferSet extends IDisposable {
51 | alt: IBuffer;
52 | normal: IBuffer;
53 | active: IBuffer;
54 |
55 | onBufferActivate: IEvent<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>;
56 |
57 | activateNormalBuffer(): void;
58 | activateAltBuffer(fillAttr?: IAttributeData): void;
59 | resize(newCols: number, newRows: number): void;
60 | setupTabStops(i?: number): void;
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/data/Charsets.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { ICharset } from 'common/Types';
7 |
8 | /**
9 | * The character sets supported by the terminal. These enable several languages
10 | * to be represented within the terminal with only 8-bit encoding. See ISO 2022
11 | * for a discussion on character sets. Only VT100 character sets are supported.
12 | */
13 | export const CHARSETS: { [key: string]: ICharset | undefined } = {};
14 |
15 | /**
16 | * The default character set, US.
17 | */
18 | export const DEFAULT_CHARSET: ICharset | undefined = CHARSETS['B'];
19 |
20 | /**
21 | * DEC Special Character and Line Drawing Set.
22 | * Reference: http://vt100.net/docs/vt102-ug/table5-13.html
23 | * A lot of curses apps use this if they see TERM=xterm.
24 | * testing: echo -e '\e(0a\e(B'
25 | * The xterm output sometimes seems to conflict with the
26 | * reference above. xterm seems in line with the reference
27 | * when running vttest however.
28 | * The table below now uses xterm's output from vttest.
29 | */
30 | CHARSETS['0'] = {
31 | '`': '\u25c6', // '◆'
32 | 'a': '\u2592', // '▒'
33 | 'b': '\u2409', // '␉' (HT)
34 | 'c': '\u240c', // '␌' (FF)
35 | 'd': '\u240d', // '␍' (CR)
36 | 'e': '\u240a', // '␊' (LF)
37 | 'f': '\u00b0', // '°'
38 | 'g': '\u00b1', // '±'
39 | 'h': '\u2424', // '' (NL)
40 | 'i': '\u240b', // '␋' (VT)
41 | 'j': '\u2518', // '┘'
42 | 'k': '\u2510', // '┐'
43 | 'l': '\u250c', // '┌'
44 | 'm': '\u2514', // '└'
45 | 'n': '\u253c', // '┼'
46 | 'o': '\u23ba', // '⎺'
47 | 'p': '\u23bb', // '⎻'
48 | 'q': '\u2500', // '─'
49 | 'r': '\u23bc', // '⎼'
50 | 's': '\u23bd', // '⎽'
51 | 't': '\u251c', // '├'
52 | 'u': '\u2524', // '┤'
53 | 'v': '\u2534', // '┴'
54 | 'w': '\u252c', // '┬'
55 | 'x': '\u2502', // '│'
56 | 'y': '\u2264', // '≤'
57 | 'z': '\u2265', // '≥'
58 | '{': '\u03c0', // 'π'
59 | '|': '\u2260', // '≠'
60 | '}': '\u00a3', // '£'
61 | '~': '\u00b7' // '·'
62 | };
63 |
64 | /**
65 | * British character set
66 | * ESC (A
67 | * Reference: http://vt100.net/docs/vt220-rm/table2-5.html
68 | */
69 | CHARSETS['A'] = {
70 | '#': '£'
71 | };
72 |
73 | /**
74 | * United States character set
75 | * ESC (B
76 | */
77 | CHARSETS['B'] = undefined;
78 |
79 | /**
80 | * Dutch character set
81 | * ESC (4
82 | * Reference: http://vt100.net/docs/vt220-rm/table2-6.html
83 | */
84 | CHARSETS['4'] = {
85 | '#': '£',
86 | '@': '¾',
87 | '[': 'ij',
88 | '\\': '½',
89 | ']': '|',
90 | '{': '¨',
91 | '|': 'f',
92 | '}': '¼',
93 | '~': '´'
94 | };
95 |
96 | /**
97 | * Finnish character set
98 | * ESC (C or ESC (5
99 | * Reference: http://vt100.net/docs/vt220-rm/table2-7.html
100 | */
101 | CHARSETS['C'] =
102 | CHARSETS['5'] = {
103 | '[': 'Ä',
104 | '\\': 'Ö',
105 | ']': 'Å',
106 | '^': 'Ü',
107 | '`': 'é',
108 | '{': 'ä',
109 | '|': 'ö',
110 | '}': 'å',
111 | '~': 'ü'
112 | };
113 |
114 | /**
115 | * French character set
116 | * ESC (R
117 | * Reference: http://vt100.net/docs/vt220-rm/table2-8.html
118 | */
119 | CHARSETS['R'] = {
120 | '#': '£',
121 | '@': 'à',
122 | '[': '°',
123 | '\\': 'ç',
124 | ']': '§',
125 | '{': 'é',
126 | '|': 'ù',
127 | '}': 'è',
128 | '~': '¨'
129 | };
130 |
131 | /**
132 | * French Canadian character set
133 | * ESC (Q
134 | * Reference: http://vt100.net/docs/vt220-rm/table2-9.html
135 | */
136 | CHARSETS['Q'] = {
137 | '@': 'à',
138 | '[': 'â',
139 | '\\': 'ç',
140 | ']': 'ê',
141 | '^': 'î',
142 | '`': 'ô',
143 | '{': 'é',
144 | '|': 'ù',
145 | '}': 'è',
146 | '~': 'û'
147 | };
148 |
149 | /**
150 | * German character set
151 | * ESC (K
152 | * Reference: http://vt100.net/docs/vt220-rm/table2-10.html
153 | */
154 | CHARSETS['K'] = {
155 | '@': '§',
156 | '[': 'Ä',
157 | '\\': 'Ö',
158 | ']': 'Ü',
159 | '{': 'ä',
160 | '|': 'ö',
161 | '}': 'ü',
162 | '~': 'ß'
163 | };
164 |
165 | /**
166 | * Italian character set
167 | * ESC (Y
168 | * Reference: http://vt100.net/docs/vt220-rm/table2-11.html
169 | */
170 | CHARSETS['Y'] = {
171 | '#': '£',
172 | '@': '§',
173 | '[': '°',
174 | '\\': 'ç',
175 | ']': 'é',
176 | '`': 'ù',
177 | '{': 'à',
178 | '|': 'ò',
179 | '}': 'è',
180 | '~': 'ì'
181 | };
182 |
183 | /**
184 | * Norwegian/Danish character set
185 | * ESC (E or ESC (6
186 | * Reference: http://vt100.net/docs/vt220-rm/table2-12.html
187 | */
188 | CHARSETS['E'] =
189 | CHARSETS['6'] = {
190 | '@': 'Ä',
191 | '[': 'Æ',
192 | '\\': 'Ø',
193 | ']': 'Å',
194 | '^': 'Ü',
195 | '`': 'ä',
196 | '{': 'æ',
197 | '|': 'ø',
198 | '}': 'å',
199 | '~': 'ü'
200 | };
201 |
202 | /**
203 | * Spanish character set
204 | * ESC (Z
205 | * Reference: http://vt100.net/docs/vt220-rm/table2-13.html
206 | */
207 | CHARSETS['Z'] = {
208 | '#': '£',
209 | '@': '§',
210 | '[': '¡',
211 | '\\': 'Ñ',
212 | ']': '¿',
213 | '{': '°',
214 | '|': 'ñ',
215 | '}': 'ç'
216 | };
217 |
218 | /**
219 | * Swedish character set
220 | * ESC (H or ESC (7
221 | * Reference: http://vt100.net/docs/vt220-rm/table2-14.html
222 | */
223 | CHARSETS['H'] =
224 | CHARSETS['7'] = {
225 | '@': 'É',
226 | '[': 'Ä',
227 | '\\': 'Ö',
228 | ']': 'Å',
229 | '^': 'Ü',
230 | '`': 'é',
231 | '{': 'ä',
232 | '|': 'ö',
233 | '}': 'å',
234 | '~': 'ü'
235 | };
236 |
237 | /**
238 | * Swiss character set
239 | * ESC (=
240 | * Reference: http://vt100.net/docs/vt220-rm/table2-15.html
241 | */
242 | CHARSETS['='] = {
243 | '#': 'ù',
244 | '@': 'à',
245 | '[': 'é',
246 | '\\': 'ç',
247 | ']': 'ê',
248 | '^': 'î',
249 | // eslint-disable-next-line @typescript-eslint/naming-convention
250 | '_': 'è',
251 | '`': 'ô',
252 | '{': 'ä',
253 | '|': 'ö',
254 | '}': 'ü',
255 | '~': 'û'
256 | };
257 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/data/EscapeSequences.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | /**
7 | * C0 control codes
8 | * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes
9 | */
10 | export namespace C0 {
11 | /** Null (Caret = ^@, C = \0) */
12 | export const NUL = '\x00';
13 | /** Start of Heading (Caret = ^A) */
14 | export const SOH = '\x01';
15 | /** Start of Text (Caret = ^B) */
16 | export const STX = '\x02';
17 | /** End of Text (Caret = ^C) */
18 | export const ETX = '\x03';
19 | /** End of Transmission (Caret = ^D) */
20 | export const EOT = '\x04';
21 | /** Enquiry (Caret = ^E) */
22 | export const ENQ = '\x05';
23 | /** Acknowledge (Caret = ^F) */
24 | export const ACK = '\x06';
25 | /** Bell (Caret = ^G, C = \a) */
26 | export const BEL = '\x07';
27 | /** Backspace (Caret = ^H, C = \b) */
28 | export const BS = '\x08';
29 | /** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \t) */
30 | export const HT = '\x09';
31 | /** Line Feed (Caret = ^J, C = \n) */
32 | export const LF = '\x0a';
33 | /** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \v) */
34 | export const VT = '\x0b';
35 | /** Form Feed (Caret = ^L, C = \f) */
36 | export const FF = '\x0c';
37 | /** Carriage Return (Caret = ^M, C = \r) */
38 | export const CR = '\x0d';
39 | /** Shift Out (Caret = ^N) */
40 | export const SO = '\x0e';
41 | /** Shift In (Caret = ^O) */
42 | export const SI = '\x0f';
43 | /** Data Link Escape (Caret = ^P) */
44 | export const DLE = '\x10';
45 | /** Device Control One (XON) (Caret = ^Q) */
46 | export const DC1 = '\x11';
47 | /** Device Control Two (Caret = ^R) */
48 | export const DC2 = '\x12';
49 | /** Device Control Three (XOFF) (Caret = ^S) */
50 | export const DC3 = '\x13';
51 | /** Device Control Four (Caret = ^T) */
52 | export const DC4 = '\x14';
53 | /** Negative Acknowledge (Caret = ^U) */
54 | export const NAK = '\x15';
55 | /** Synchronous Idle (Caret = ^V) */
56 | export const SYN = '\x16';
57 | /** End of Transmission Block (Caret = ^W) */
58 | export const ETB = '\x17';
59 | /** Cancel (Caret = ^X) */
60 | export const CAN = '\x18';
61 | /** End of Medium (Caret = ^Y) */
62 | export const EM = '\x19';
63 | /** Substitute (Caret = ^Z) */
64 | export const SUB = '\x1a';
65 | /** Escape (Caret = ^[, C = \e) */
66 | export const ESC = '\x1b';
67 | /** File Separator (Caret = ^\) */
68 | export const FS = '\x1c';
69 | /** Group Separator (Caret = ^]) */
70 | export const GS = '\x1d';
71 | /** Record Separator (Caret = ^^) */
72 | export const RS = '\x1e';
73 | /** Unit Separator (Caret = ^_) */
74 | export const US = '\x1f';
75 | /** Space */
76 | export const SP = '\x20';
77 | /** Delete (Caret = ^?) */
78 | export const DEL = '\x7f';
79 | }
80 |
81 | /**
82 | * C1 control codes
83 | * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes
84 | */
85 | export namespace C1 {
86 | /** padding character */
87 | export const PAD = '\x80';
88 | /** High Octet Preset */
89 | export const HOP = '\x81';
90 | /** Break Permitted Here */
91 | export const BPH = '\x82';
92 | /** No Break Here */
93 | export const NBH = '\x83';
94 | /** Index */
95 | export const IND = '\x84';
96 | /** Next Line */
97 | export const NEL = '\x85';
98 | /** Start of Selected Area */
99 | export const SSA = '\x86';
100 | /** End of Selected Area */
101 | export const ESA = '\x87';
102 | /** Horizontal Tabulation Set */
103 | export const HTS = '\x88';
104 | /** Horizontal Tabulation With Justification */
105 | export const HTJ = '\x89';
106 | /** Vertical Tabulation Set */
107 | export const VTS = '\x8a';
108 | /** Partial Line Down */
109 | export const PLD = '\x8b';
110 | /** Partial Line Up */
111 | export const PLU = '\x8c';
112 | /** Reverse Index */
113 | export const RI = '\x8d';
114 | /** Single-Shift 2 */
115 | export const SS2 = '\x8e';
116 | /** Single-Shift 3 */
117 | export const SS3 = '\x8f';
118 | /** Device Control String */
119 | export const DCS = '\x90';
120 | /** Private Use 1 */
121 | export const PU1 = '\x91';
122 | /** Private Use 2 */
123 | export const PU2 = '\x92';
124 | /** Set Transmit State */
125 | export const STS = '\x93';
126 | /** Destructive backspace, intended to eliminate ambiguity about meaning of BS. */
127 | export const CCH = '\x94';
128 | /** Message Waiting */
129 | export const MW = '\x95';
130 | /** Start of Protected Area */
131 | export const SPA = '\x96';
132 | /** End of Protected Area */
133 | export const EPA = '\x97';
134 | /** Start of String */
135 | export const SOS = '\x98';
136 | /** Single Graphic Character Introducer */
137 | export const SGCI = '\x99';
138 | /** Single Character Introducer */
139 | export const SCI = '\x9a';
140 | /** Control Sequence Introducer */
141 | export const CSI = '\x9b';
142 | /** String Terminator */
143 | export const ST = '\x9c';
144 | /** Operating System Command */
145 | export const OSC = '\x9d';
146 | /** Privacy Message */
147 | export const PM = '\x9e';
148 | /** Application Program Command */
149 | export const APC = '\x9f';
150 | }
151 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/input/UnicodeV6.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 | import { IUnicodeVersionProvider } from 'common/services/Services';
6 | import { fill } from 'common/TypedArrayUtils';
7 |
8 | type CharWidth = 0 | 1 | 2;
9 |
10 | const BMP_COMBINING = [
11 | [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
12 | [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
13 | [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
14 | [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
15 | [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
16 | [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
17 | [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
18 | [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
19 | [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
20 | [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
21 | [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
22 | [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
23 | [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
24 | [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
25 | [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
26 | [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
27 | [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
28 | [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
29 | [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
30 | [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
31 | [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
32 | [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
33 | [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
34 | [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
35 | [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
36 | [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
37 | [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
38 | [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
39 | [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
40 | [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
41 | [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
42 | [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
43 | [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
44 | [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
45 | [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
46 | [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
47 | [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
48 | [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
49 | [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
50 | [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
51 | [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
52 | [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
53 | [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB]
54 | ];
55 | const HIGH_COMBINING = [
56 | [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
57 | [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
58 | [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
59 | [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
60 | [0xE0100, 0xE01EF]
61 | ];
62 |
63 | // BMP lookup table, lazy initialized during first addon loading
64 | let table: Uint8Array;
65 |
66 | function bisearch(ucs: number, data: number[][]): boolean {
67 | let min = 0;
68 | let max = data.length - 1;
69 | let mid;
70 | if (ucs < data[0][0] || ucs > data[max][1]) {
71 | return false;
72 | }
73 | while (max >= min) {
74 | mid = (min + max) >> 1;
75 | if (ucs > data[mid][1]) {
76 | min = mid + 1;
77 | } else if (ucs < data[mid][0]) {
78 | max = mid - 1;
79 | } else {
80 | return true;
81 | }
82 | }
83 | return false;
84 | }
85 |
86 | export class UnicodeV6 implements IUnicodeVersionProvider {
87 | public readonly version = '6';
88 |
89 | constructor() {
90 | // init lookup table once
91 | if (!table) {
92 | table = new Uint8Array(65536);
93 | fill(table, 1);
94 | table[0] = 0;
95 | // control chars
96 | fill(table, 0, 1, 32);
97 | fill(table, 0, 0x7f, 0xa0);
98 |
99 | // apply wide char rules first
100 | // wide chars
101 | fill(table, 2, 0x1100, 0x1160);
102 | table[0x2329] = 2;
103 | table[0x232a] = 2;
104 | fill(table, 2, 0x2e80, 0xa4d0);
105 | table[0x303f] = 1; // wrongly in last line
106 |
107 | fill(table, 2, 0xac00, 0xd7a4);
108 | fill(table, 2, 0xf900, 0xfb00);
109 | fill(table, 2, 0xfe10, 0xfe1a);
110 | fill(table, 2, 0xfe30, 0xfe70);
111 | fill(table, 2, 0xff00, 0xff61);
112 | fill(table, 2, 0xffe0, 0xffe7);
113 |
114 | // apply combining last to ensure we overwrite
115 | // wrongly wide set chars:
116 | // the original algo evals combining first and falls
117 | // through to wide check so we simply do here the opposite
118 | // combining 0
119 | for (let r = 0; r < BMP_COMBINING.length; ++r) {
120 | fill(table, 0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1);
121 | }
122 | }
123 | }
124 |
125 | public wcwidth(num: number): CharWidth {
126 | if (num < 32) return 0;
127 | if (num < 127) return 1;
128 | if (num < 65536) return table[num] as CharWidth;
129 | if (bisearch(num, HIGH_COMBINING)) return 0;
130 | if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2;
131 | return 1;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/input/WriteBuffer.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
4 | * @license MIT
5 | */
6 |
7 | declare const setTimeout: (handler: () => void, timeout?: number) => void;
8 |
9 | /**
10 | * Safety watermark to avoid memory exhaustion and browser engine crash on fast data input.
11 | * Enable flow control to avoid this limit and make sure that your backend correctly
12 | * propagates this to the underlying pty. (see docs for further instructions)
13 | * Since this limit is meant as a safety parachute to prevent browser crashs,
14 | * it is set to a very high number. Typically xterm.js gets unresponsive with
15 | * a 100 times lower number (>500 kB).
16 | */
17 | const DISCARD_WATERMARK = 50000000; // ~50 MB
18 |
19 | /**
20 | * The max number of ms to spend on writes before allowing the renderer to
21 | * catch up with a 0ms setTimeout. A value of < 33 to keep us close to
22 | * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
23 | * depends on the time it takes for the renderer to draw the frame.
24 | */
25 | const WRITE_TIMEOUT_MS = 12;
26 |
27 | /**
28 | * Threshold of max held chunks in the write buffer, that were already processed.
29 | * This is a tradeoff between extensive write buffer shifts (bad runtime) and high
30 | * memory consumption by data thats not used anymore.
31 | */
32 | const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
33 |
34 | export class WriteBuffer {
35 | private _writeBuffer: (string | Uint8Array)[] = [];
36 | private _callbacks: ((() => void) | undefined)[] = [];
37 | private _pendingData = 0;
38 | private _bufferOffset = 0;
39 |
40 | constructor(private _action: (data: string | Uint8Array) => void) { }
41 |
42 | public writeSync(data: string | Uint8Array): void {
43 | // force sync processing on pending data chunks to avoid in-band data scrambling
44 | // does the same as innerWrite but without event loop
45 | if (this._writeBuffer.length) {
46 | for (let i = this._bufferOffset; i < this._writeBuffer.length; ++i) {
47 | const data = this._writeBuffer[i];
48 | const cb = this._callbacks[i];
49 | this._action(data);
50 | if (cb) cb();
51 | }
52 | // reset all to avoid reprocessing of chunks with scheduled innerWrite call
53 | this._writeBuffer = [];
54 | this._callbacks = [];
55 | this._pendingData = 0;
56 | // stop scheduled innerWrite by offset > length condition
57 | this._bufferOffset = 0x7FFFFFFF;
58 | }
59 | // handle current data chunk
60 | this._action(data);
61 | }
62 |
63 | public write(data: string | Uint8Array, callback?: () => void): void {
64 | if (this._pendingData > DISCARD_WATERMARK) {
65 | throw new Error('write data discarded, use flow control to avoid losing data');
66 | }
67 |
68 | // schedule chunk processing for next event loop run
69 | if (!this._writeBuffer.length) {
70 | this._bufferOffset = 0;
71 | setTimeout(() => this._innerWrite());
72 | }
73 |
74 | this._pendingData += data.length;
75 | this._writeBuffer.push(data);
76 | this._callbacks.push(callback);
77 | }
78 |
79 | protected _innerWrite(): void {
80 | const startTime = Date.now();
81 | while (this._writeBuffer.length > this._bufferOffset) {
82 | const data = this._writeBuffer[this._bufferOffset];
83 | const cb = this._callbacks[this._bufferOffset];
84 | this._bufferOffset++;
85 |
86 | this._action(data);
87 | this._pendingData -= data.length;
88 | if (cb) cb();
89 |
90 | if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
91 | break;
92 | }
93 | }
94 | if (this._writeBuffer.length > this._bufferOffset) {
95 | // Allow renderer to catch up before processing the next batch
96 | // trim already processed chunks if we are above threshold
97 | if (this._bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
98 | this._writeBuffer = this._writeBuffer.slice(this._bufferOffset);
99 | this._callbacks = this._callbacks.slice(this._bufferOffset);
100 | this._bufferOffset = 0;
101 | }
102 | setTimeout(() => this._innerWrite(), 0);
103 | } else {
104 | this._writeBuffer = [];
105 | this._callbacks = [];
106 | this._pendingData = 0;
107 | this._bufferOffset = 0;
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/parser/Constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | /**
7 | * Internal states of EscapeSequenceParser.
8 | */
9 | export const enum ParserState {
10 | GROUND = 0,
11 | ESCAPE = 1,
12 | ESCAPE_INTERMEDIATE = 2,
13 | CSI_ENTRY = 3,
14 | CSI_PARAM = 4,
15 | CSI_INTERMEDIATE = 5,
16 | CSI_IGNORE = 6,
17 | SOS_PM_APC_STRING = 7,
18 | OSC_STRING = 8,
19 | DCS_ENTRY = 9,
20 | DCS_PARAM = 10,
21 | DCS_IGNORE = 11,
22 | DCS_INTERMEDIATE = 12,
23 | DCS_PASSTHROUGH = 13
24 | }
25 |
26 | /**
27 | * Internal actions of EscapeSequenceParser.
28 | */
29 | export const enum ParserAction {
30 | IGNORE = 0,
31 | ERROR = 1,
32 | PRINT = 2,
33 | EXECUTE = 3,
34 | OSC_START = 4,
35 | OSC_PUT = 5,
36 | OSC_END = 6,
37 | CSI_DISPATCH = 7,
38 | PARAM = 8,
39 | COLLECT = 9,
40 | ESC_DISPATCH = 10,
41 | CLEAR = 11,
42 | DCS_HOOK = 12,
43 | DCS_PUT = 13,
44 | DCS_UNHOOK = 14
45 | }
46 |
47 | /**
48 | * Internal states of OscParser.
49 | */
50 | export const enum OscState {
51 | START = 0,
52 | ID = 1,
53 | PAYLOAD = 2,
54 | ABORT = 3
55 | }
56 |
57 | // payload limit for OSC and DCS
58 | export const PAYLOAD_LIMIT = 10000000;
59 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/parser/DcsParser.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IDisposable } from 'common/Types';
7 | import { IDcsHandler, IParams, IHandlerCollection, IDcsParser, DcsFallbackHandlerType } from 'common/parser/Types';
8 | import { utf32ToString } from 'common/input/TextDecoder';
9 | import { Params } from 'common/parser/Params';
10 | import { PAYLOAD_LIMIT } from 'common/parser/Constants';
11 |
12 | const EMPTY_HANDLERS: IDcsHandler[] = [];
13 |
14 | export class DcsParser implements IDcsParser {
15 | private _handlers: IHandlerCollection = Object.create(null);
16 | private _active: IDcsHandler[] = EMPTY_HANDLERS;
17 | private _ident: number = 0;
18 | private _handlerFb: DcsFallbackHandlerType = () => {};
19 |
20 | public dispose(): void {
21 | this._handlers = Object.create(null);
22 | this._handlerFb = () => {};
23 | }
24 |
25 | public addHandler(ident: number, handler: IDcsHandler): IDisposable {
26 | if (this._handlers[ident] === undefined) {
27 | this._handlers[ident] = [];
28 | }
29 | const handlerList = this._handlers[ident];
30 | handlerList.push(handler);
31 | return {
32 | dispose: () => {
33 | const handlerIndex = handlerList.indexOf(handler);
34 | if (handlerIndex !== -1) {
35 | handlerList.splice(handlerIndex, 1);
36 | }
37 | }
38 | };
39 | }
40 |
41 | public setHandler(ident: number, handler: IDcsHandler): void {
42 | this._handlers[ident] = [handler];
43 | }
44 |
45 | public clearHandler(ident: number): void {
46 | if (this._handlers[ident]) delete this._handlers[ident];
47 | }
48 |
49 | public setHandlerFallback(handler: DcsFallbackHandlerType): void {
50 | this._handlerFb = handler;
51 | }
52 |
53 | public reset(): void {
54 | if (this._active.length) {
55 | this.unhook(false);
56 | }
57 | this._active = EMPTY_HANDLERS;
58 | this._ident = 0;
59 | }
60 |
61 | public hook(ident: number, params: IParams): void {
62 | // always reset leftover handlers
63 | this.reset();
64 | this._ident = ident;
65 | this._active = this._handlers[ident] || EMPTY_HANDLERS;
66 | if (!this._active.length) {
67 | this._handlerFb(this._ident, 'HOOK', params);
68 | } else {
69 | for (let j = this._active.length - 1; j >= 0; j--) {
70 | this._active[j].hook(params);
71 | }
72 | }
73 | }
74 |
75 | public put(data: Uint32Array, start: number, end: number): void {
76 | if (!this._active.length) {
77 | this._handlerFb(this._ident, 'PUT', utf32ToString(data, start, end));
78 | } else {
79 | for (let j = this._active.length - 1; j >= 0; j--) {
80 | this._active[j].put(data, start, end);
81 | }
82 | }
83 | }
84 |
85 | public unhook(success: boolean): void {
86 | if (!this._active.length) {
87 | this._handlerFb(this._ident, 'UNHOOK', success);
88 | } else {
89 | let j = this._active.length - 1;
90 | for (; j >= 0; j--) {
91 | if (this._active[j].unhook(success) !== false) {
92 | break;
93 | }
94 | }
95 | j--;
96 | // cleanup left over handlers
97 | for (; j >= 0; j--) {
98 | this._active[j].unhook(false);
99 | }
100 | }
101 | this._active = EMPTY_HANDLERS;
102 | this._ident = 0;
103 | }
104 | }
105 |
106 | /**
107 | * Convenient class to create a DCS handler from a single callback function.
108 | * Note: The payload is currently limited to 50 MB (hardcoded).
109 | */
110 | export class DcsHandler implements IDcsHandler {
111 | private _data = '';
112 | private _params: IParams | undefined;
113 | private _hitLimit: boolean = false;
114 |
115 | constructor(private _handler: (data: string, params: IParams) => any) {}
116 |
117 | public hook(params: IParams): void {
118 | this._params = params.clone();
119 | this._data = '';
120 | this._hitLimit = false;
121 | }
122 |
123 | public put(data: Uint32Array, start: number, end: number): void {
124 | if (this._hitLimit) {
125 | return;
126 | }
127 | this._data += utf32ToString(data, start, end);
128 | if (this._data.length > PAYLOAD_LIMIT) {
129 | this._data = '';
130 | this._hitLimit = true;
131 | }
132 | }
133 |
134 | public unhook(success: boolean): any {
135 | let ret;
136 | if (this._hitLimit) {
137 | ret = false;
138 | } else if (success) {
139 | ret = this._handler(this._data, this._params || new Params());
140 | }
141 | this._params = undefined;
142 | this._data = '';
143 | this._hitLimit = false;
144 | return ret;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/parser/OscParser.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IOscHandler, IHandlerCollection, OscFallbackHandlerType, IOscParser } from 'common/parser/Types';
7 | import { OscState, PAYLOAD_LIMIT } from 'common/parser/Constants';
8 | import { utf32ToString } from 'common/input/TextDecoder';
9 | import { IDisposable } from 'common/Types';
10 |
11 |
12 | export class OscParser implements IOscParser {
13 | private _state = OscState.START;
14 | private _id = -1;
15 | private _handlers: IHandlerCollection = Object.create(null);
16 | private _handlerFb: OscFallbackHandlerType = () => { };
17 |
18 | public addHandler(ident: number, handler: IOscHandler): IDisposable {
19 | if (this._handlers[ident] === undefined) {
20 | this._handlers[ident] = [];
21 | }
22 | const handlerList = this._handlers[ident];
23 | handlerList.push(handler);
24 | return {
25 | dispose: () => {
26 | const handlerIndex = handlerList.indexOf(handler);
27 | if (handlerIndex !== -1) {
28 | handlerList.splice(handlerIndex, 1);
29 | }
30 | }
31 | };
32 | }
33 | public setHandler(ident: number, handler: IOscHandler): void {
34 | this._handlers[ident] = [handler];
35 | }
36 | public clearHandler(ident: number): void {
37 | if (this._handlers[ident]) delete this._handlers[ident];
38 | }
39 | public setHandlerFallback(handler: OscFallbackHandlerType): void {
40 | this._handlerFb = handler;
41 | }
42 |
43 | public dispose(): void {
44 | this._handlers = Object.create(null);
45 | this._handlerFb = () => {};
46 | }
47 |
48 | public reset(): void {
49 | // cleanup handlers if payload was already sent
50 | if (this._state === OscState.PAYLOAD) {
51 | this.end(false);
52 | }
53 | this._id = -1;
54 | this._state = OscState.START;
55 | }
56 |
57 | private _start(): void {
58 | const handlers = this._handlers[this._id];
59 | if (!handlers) {
60 | this._handlerFb(this._id, 'START');
61 | } else {
62 | for (let j = handlers.length - 1; j >= 0; j--) {
63 | handlers[j].start();
64 | }
65 | }
66 | }
67 |
68 | private _put(data: Uint32Array, start: number, end: number): void {
69 | const handlers = this._handlers[this._id];
70 | if (!handlers) {
71 | this._handlerFb(this._id, 'PUT', utf32ToString(data, start, end));
72 | } else {
73 | for (let j = handlers.length - 1; j >= 0; j--) {
74 | handlers[j].put(data, start, end);
75 | }
76 | }
77 | }
78 |
79 | private _end(success: boolean): void {
80 | // other than the old code we always have to call .end
81 | // to keep the bubbling we use `success` to indicate
82 | // whether a handler should execute
83 | const handlers = this._handlers[this._id];
84 | if (!handlers) {
85 | this._handlerFb(this._id, 'END', success);
86 | } else {
87 | let j = handlers.length - 1;
88 | for (; j >= 0; j--) {
89 | if (handlers[j].end(success) !== false) {
90 | break;
91 | }
92 | }
93 | j--;
94 | // cleanup left over handlers
95 | for (; j >= 0; j--) {
96 | handlers[j].end(false);
97 | }
98 | }
99 | }
100 |
101 | public start(): void {
102 | // always reset leftover handlers
103 | this.reset();
104 | this._id = -1;
105 | this._state = OscState.ID;
106 | }
107 |
108 | /**
109 | * Put data to current OSC command.
110 | * Expects the identifier of the OSC command in the form
111 | * OSC id ; payload ST/BEL
112 | * Payload chunks are not further processed and get
113 | * directly passed to the handlers.
114 | */
115 | public put(data: Uint32Array, start: number, end: number): void {
116 | if (this._state === OscState.ABORT) {
117 | return;
118 | }
119 | if (this._state === OscState.ID) {
120 | while (start < end) {
121 | const code = data[start++];
122 | if (code === 0x3b) {
123 | this._state = OscState.PAYLOAD;
124 | this._start();
125 | break;
126 | }
127 | if (code < 0x30 || 0x39 < code) {
128 | this._state = OscState.ABORT;
129 | return;
130 | }
131 | if (this._id === -1) {
132 | this._id = 0;
133 | }
134 | this._id = this._id * 10 + code - 48;
135 | }
136 | }
137 | if (this._state === OscState.PAYLOAD && end - start > 0) {
138 | this._put(data, start, end);
139 | }
140 | }
141 |
142 | /**
143 | * Indicates end of an OSC command.
144 | * Whether the OSC got aborted or finished normally
145 | * is indicated by `success`.
146 | */
147 | public end(success: boolean): void {
148 | if (this._state === OscState.START) {
149 | return;
150 | }
151 | // do nothing if command was faulty
152 | if (this._state !== OscState.ABORT) {
153 | // if we are still in ID state and get an early end
154 | // means that the command has no payload thus we still have
155 | // to announce START and send END right after
156 | if (this._state === OscState.ID) {
157 | this._start();
158 | }
159 | this._end(success);
160 | }
161 | this._id = -1;
162 | this._state = OscState.START;
163 | }
164 | }
165 |
166 | /**
167 | * Convenient class to allow attaching string based handler functions
168 | * as OSC handlers.
169 | */
170 | export class OscHandler implements IOscHandler {
171 | private _data = '';
172 | private _hitLimit: boolean = false;
173 |
174 | constructor(private _handler: (data: string) => any) {}
175 |
176 | public start(): void {
177 | this._data = '';
178 | this._hitLimit = false;
179 | }
180 |
181 | public put(data: Uint32Array, start: number, end: number): void {
182 | if (this._hitLimit) {
183 | return;
184 | }
185 | this._data += utf32ToString(data, start, end);
186 | if (this._data.length > PAYLOAD_LIMIT) {
187 | this._data = '';
188 | this._hitLimit = true;
189 | }
190 | }
191 |
192 | public end(success: boolean): any {
193 | let ret;
194 | if (this._hitLimit) {
195 | ret = false;
196 | } else if (success) {
197 | ret = this._handler(this._data);
198 | }
199 | this._data = '';
200 | this._hitLimit = false;
201 | return ret;
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/services/BufferService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IBufferService, IOptionsService } from 'common/services/Services';
7 | import { BufferSet } from 'common/buffer/BufferSet';
8 | import { IBufferSet, IBuffer } from 'common/buffer/Types';
9 | import { EventEmitter, IEvent } from 'common/EventEmitter';
10 | import { Disposable } from 'common/Lifecycle';
11 |
12 | export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars
13 | export const MINIMUM_ROWS = 1;
14 |
15 | export class BufferService extends Disposable implements IBufferService {
16 | public serviceBrand: any;
17 |
18 | public cols: number;
19 | public rows: number;
20 | public buffers: IBufferSet;
21 | /** Whether the user is scrolling (locks the scroll position) */
22 | public isUserScrolling: boolean = false;
23 |
24 | private _onResize = new EventEmitter<{ cols: number, rows: number }>();
25 | public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }
26 |
27 | public get buffer(): IBuffer { return this.buffers.active; }
28 |
29 | constructor(
30 | @IOptionsService private _optionsService: IOptionsService
31 | ) {
32 | super();
33 | this.cols = Math.max(_optionsService.options.cols, MINIMUM_COLS);
34 | this.rows = Math.max(_optionsService.options.rows, MINIMUM_ROWS);
35 | this.buffers = new BufferSet(_optionsService, this);
36 | }
37 |
38 | public dispose(): void {
39 | super.dispose();
40 | this.buffers.dispose();
41 | }
42 |
43 | public resize(cols: number, rows: number): void {
44 | this.cols = cols;
45 | this.rows = rows;
46 | this.buffers.resize(cols, rows);
47 | this.buffers.setupTabStops(this.cols);
48 | this._onResize.fire({ cols, rows });
49 | }
50 |
51 | public reset(): void {
52 | this.buffers.dispose();
53 | this.buffers = new BufferSet(this._optionsService, this);
54 | this.isUserScrolling = false;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/services/CharsetService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { ICharsetService } from 'common/services/Services';
7 | import { ICharset } from 'common/Types';
8 |
9 | export class CharsetService implements ICharsetService {
10 | public serviceBrand: any;
11 |
12 | public charset: ICharset | undefined;
13 | public glevel: number = 0;
14 |
15 | private _charsets: (ICharset | undefined)[] = [];
16 |
17 | public reset(): void {
18 | this.charset = undefined;
19 | this._charsets = [];
20 | this.glevel = 0;
21 | }
22 |
23 | public setgLevel(g: number): void {
24 | this.glevel = g;
25 | this.charset = this._charsets[g];
26 | }
27 |
28 | public setgCharset(g: number, charset: ICharset | undefined): void {
29 | this._charsets[g] = charset;
30 | if (this.glevel === g) {
31 | this.charset = charset;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/services/CoreService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services';
7 | import { EventEmitter, IEvent } from 'common/EventEmitter';
8 | import { IDecPrivateModes, IModes } from 'common/Types';
9 | import { clone } from 'common/Clone';
10 | import { Disposable } from 'common/Lifecycle';
11 |
12 | const DEFAULT_MODES: IModes = Object.freeze({
13 | insertMode: false
14 | });
15 |
16 | const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({
17 | applicationCursorKeys: false,
18 | applicationKeypad: false,
19 | bracketedPasteMode: false,
20 | origin: false,
21 | reverseWraparound: false,
22 | sendFocus: false,
23 | wraparound: true // defaults: xterm - true, vt100 - false
24 | });
25 |
26 | export class CoreService extends Disposable implements ICoreService {
27 | public serviceBrand: any;
28 |
29 | public isCursorInitialized: boolean = false;
30 | public isCursorHidden: boolean = false;
31 | public modes: IModes;
32 | public decPrivateModes: IDecPrivateModes;
33 |
34 | // Circular dependency, this must be unset or memory will leak after Terminal.dispose
35 | private _scrollToBottom: (() => void) | undefined;
36 |
37 | private _onData = this.register(new EventEmitter());
38 | public get onData(): IEvent { return this._onData.event; }
39 | private _onUserInput = this.register(new EventEmitter());
40 | public get onUserInput(): IEvent { return this._onUserInput.event; }
41 | private _onBinary = this.register(new EventEmitter());
42 | public get onBinary(): IEvent { return this._onBinary.event; }
43 |
44 | constructor(
45 | // TODO: Move this into a service
46 | scrollToBottom: () => void,
47 | @IBufferService private readonly _bufferService: IBufferService,
48 | @ILogService private readonly _logService: ILogService,
49 | @IOptionsService private readonly _optionsService: IOptionsService
50 | ) {
51 | super();
52 | this._scrollToBottom = scrollToBottom;
53 | this.register({ dispose: () => this._scrollToBottom = undefined });
54 | this.modes = clone(DEFAULT_MODES);
55 | this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES);
56 | }
57 |
58 | public reset(): void {
59 | this.modes = clone(DEFAULT_MODES);
60 | this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES);
61 | }
62 |
63 | public triggerDataEvent(data: string, wasUserInput: boolean = false): void {
64 | // Prevents all events to pty process if stdin is disabled
65 | if (this._optionsService.options.disableStdin) {
66 | return;
67 | }
68 |
69 | // Input is being sent to the terminal, the terminal should focus the prompt.
70 | const buffer = this._bufferService.buffer;
71 | if (buffer.ybase !== buffer.ydisp) {
72 | this._scrollToBottom!();
73 | }
74 |
75 | // Fire onUserInput so listeners can react as well (eg. clear selection)
76 | if (wasUserInput) {
77 | this._onUserInput.fire();
78 | }
79 |
80 | // Fire onData API
81 | this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
82 | this._onData.fire(data);
83 | }
84 |
85 | public triggerBinaryEvent(data: string): void {
86 | if (this._optionsService.options.disableStdin) {
87 | return;
88 | }
89 | this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
90 | this._onBinary.fire(data);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/services/DirtyRowService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IBufferService, IDirtyRowService } from 'common/services/Services';
7 |
8 | export class DirtyRowService implements IDirtyRowService {
9 | public serviceBrand: any;
10 |
11 | private _start!: number;
12 | private _end!: number;
13 |
14 | public get start(): number { return this._start; }
15 | public get end(): number { return this._end; }
16 |
17 | constructor(
18 | @IBufferService private readonly _bufferService: IBufferService
19 | ) {
20 | this.clearRange();
21 | }
22 |
23 | public clearRange(): void {
24 | this._start = this._bufferService.buffer.y;
25 | this._end = this._bufferService.buffer.y;
26 | }
27 |
28 | public markDirty(y: number): void {
29 | if (y < this._start) {
30 | this._start = y;
31 | } else if (y > this._end) {
32 | this._end = y;
33 | }
34 | }
35 |
36 | public markRangeDirty(y1: number, y2: number): void {
37 | if (y1 > y2) {
38 | const temp = y1;
39 | y1 = y2;
40 | y2 = temp;
41 | }
42 | if (y1 < this._start) {
43 | this._start = y1;
44 | }
45 | if (y2 > this._end) {
46 | this._end = y2;
47 | }
48 | }
49 |
50 | public markAllDirty(): void {
51 | this.markRangeDirty(0, this._bufferService.rows - 1);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/services/InstantiationService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | *
5 | * This was heavily inspired from microsoft/vscode's dependency injection system (MIT).
6 | */
7 | /*---------------------------------------------------------------------------------------------
8 | * Copyright (c) Microsoft Corporation. All rights reserved.
9 | * Licensed under the MIT License. See License.txt in the project root for license information.
10 | *--------------------------------------------------------------------------------------------*/
11 |
12 | import { IInstantiationService, IServiceIdentifier } from 'common/services/Services';
13 | import { getServiceDependencies } from 'common/services/ServiceRegistry';
14 |
15 | export class ServiceCollection {
16 |
17 | private _entries = new Map, any>();
18 |
19 | constructor(...entries: [IServiceIdentifier, any][]) {
20 | for (const [id, service] of entries) {
21 | this.set(id, service);
22 | }
23 | }
24 |
25 | public set(id: IServiceIdentifier, instance: T): T {
26 | const result = this._entries.get(id);
27 | this._entries.set(id, instance);
28 | return result;
29 | }
30 |
31 | public forEach(callback: (id: IServiceIdentifier, instance: any) => any): void {
32 | this._entries.forEach((value, key) => callback(key, value));
33 | }
34 |
35 | public has(id: IServiceIdentifier): boolean {
36 | return this._entries.has(id);
37 | }
38 |
39 | public get(id: IServiceIdentifier): T | undefined {
40 | return this._entries.get(id);
41 | }
42 | }
43 |
44 | export class InstantiationService implements IInstantiationService {
45 | private readonly _services: ServiceCollection = new ServiceCollection();
46 |
47 | constructor() {
48 | this._services.set(IInstantiationService, this);
49 | }
50 |
51 | public setService(id: IServiceIdentifier, instance: T): void {
52 | this._services.set(id, instance);
53 | }
54 |
55 | public getService(id: IServiceIdentifier): T | undefined {
56 | return this._services.get(id);
57 | }
58 |
59 | public createInstance(ctor: any, ...args: any[]): T {
60 | const serviceDependencies = getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
61 |
62 | const serviceArgs: any[] = [];
63 | for (const dependency of serviceDependencies) {
64 | const service = this._services.get(dependency.id);
65 | if (!service) {
66 | throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`);
67 | }
68 | serviceArgs.push(service);
69 | }
70 |
71 | const firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : args.length;
72 |
73 | // check for argument mismatches, adjust static args if needed
74 | if (args.length !== firstServiceArgPos) {
75 | throw new Error(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);
76 | }
77 |
78 | // now create the instance
79 | return new ctor(...[...args, ...serviceArgs]);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/services/LogService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { ILogService, IOptionsService } from 'common/services/Services';
7 |
8 | type LogType = (message?: any, ...optionalParams: any[]) => void;
9 |
10 | interface IConsole {
11 | log: LogType;
12 | error: LogType;
13 | info: LogType;
14 | trace: LogType;
15 | warn: LogType;
16 | }
17 |
18 | // console is available on both node.js and browser contexts but the common
19 | // module doesn't depend on them so we need to explicitly declare it.
20 | declare const console: IConsole;
21 |
22 |
23 | export enum LogLevel {
24 | DEBUG = 0,
25 | INFO = 1,
26 | WARN = 2,
27 | ERROR = 3,
28 | OFF = 4
29 | }
30 |
31 | const optionsKeyToLogLevel: { [key: string]: LogLevel } = {
32 | debug: LogLevel.DEBUG,
33 | info: LogLevel.INFO,
34 | warn: LogLevel.WARN,
35 | error: LogLevel.ERROR,
36 | off: LogLevel.OFF
37 | };
38 |
39 | const LOG_PREFIX = 'xterm.js: ';
40 |
41 | export class LogService implements ILogService {
42 | public serviceBrand: any;
43 |
44 | private _logLevel!: LogLevel;
45 |
46 | constructor(
47 | @IOptionsService private readonly _optionsService: IOptionsService
48 | ) {
49 | this._updateLogLevel();
50 | this._optionsService.onOptionChange(key => {
51 | if (key === 'logLevel') {
52 | this._updateLogLevel();
53 | }
54 | });
55 | }
56 |
57 | private _updateLogLevel(): void {
58 | this._logLevel = optionsKeyToLogLevel[this._optionsService.options.logLevel];
59 | }
60 |
61 | private _evalLazyOptionalParams(optionalParams: any[]): void {
62 | for (let i = 0; i < optionalParams.length; i++) {
63 | if (typeof optionalParams[i] === 'function') {
64 | optionalParams[i] = optionalParams[i]();
65 | }
66 | }
67 | }
68 |
69 | private _log(type: LogType, message: string, optionalParams: any[]): void {
70 | this._evalLazyOptionalParams(optionalParams);
71 | type.call(console, LOG_PREFIX + message, ...optionalParams);
72 | }
73 |
74 | public debug(message: string, ...optionalParams: any[]): void {
75 | if (this._logLevel <= LogLevel.DEBUG) {
76 | this._log(console.log, message, optionalParams);
77 | }
78 | }
79 |
80 | public info(message: string, ...optionalParams: any[]): void {
81 | if (this._logLevel <= LogLevel.INFO) {
82 | this._log(console.info, message, optionalParams);
83 | }
84 | }
85 |
86 | public warn(message: string, ...optionalParams: any[]): void {
87 | if (this._logLevel <= LogLevel.WARN) {
88 | this._log(console.warn, message, optionalParams);
89 | }
90 | }
91 |
92 | public error(message: string, ...optionalParams: any[]): void {
93 | if (this._logLevel <= LogLevel.ERROR) {
94 | this._log(console.error, message, optionalParams);
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/services/OptionsService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 |
6 | import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/services/Services';
7 | import { EventEmitter, IEvent } from 'common/EventEmitter';
8 | import { isMac } from 'common/Platform';
9 | import { clone } from 'common/Clone';
10 |
11 | // Source: https://freesound.org/people/altemark/sounds/45759/
12 | // This sound is released under the Creative Commons Attribution 3.0 Unported
13 | // (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
14 | // made, apart from the conversion to base64.
15 | export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq';
16 |
17 | // TODO: Freeze?
18 | export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
19 | cols: 80,
20 | rows: 24,
21 | cursorBlink: false,
22 | cursorStyle: 'block',
23 | cursorWidth: 1,
24 | bellSound: DEFAULT_BELL_SOUND,
25 | bellStyle: 'none',
26 | drawBoldTextInBrightColors: true,
27 | fastScrollModifier: 'alt',
28 | fastScrollSensitivity: 5,
29 | fontFamily: 'courier-new, courier, monospace',
30 | fontSize: 15,
31 | fontWeight: 'normal',
32 | fontWeightBold: 'bold',
33 | lineHeight: 1.0,
34 | linkTooltipHoverDuration: 500,
35 | letterSpacing: 0,
36 | logLevel: 'info',
37 | scrollback: 1000,
38 | scrollSensitivity: 1,
39 | screenReaderMode: false,
40 | macOptionIsMeta: false,
41 | macOptionClickForcesSelection: false,
42 | minimumContrastRatio: 1,
43 | disableStdin: false,
44 | allowProposedApi: true,
45 | allowTransparency: false,
46 | tabStopWidth: 8,
47 | theme: {},
48 | rightClickSelectsWord: isMac,
49 | rendererType: 'canvas',
50 | windowOptions: {},
51 | windowsMode: false,
52 | wordSeparator: ' ()[]{}\',"`',
53 |
54 | convertEol: false,
55 | termName: 'xterm',
56 | cancelEvents: false
57 | });
58 |
59 | /**
60 | * The set of options that only have an effect when set in the Terminal constructor.
61 | */
62 | const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
63 |
64 | export class OptionsService implements IOptionsService {
65 | public serviceBrand: any;
66 |
67 | public options: ITerminalOptions;
68 |
69 | private _onOptionChange = new EventEmitter();
70 | public get onOptionChange(): IEvent { return this._onOptionChange.event; }
71 |
72 | constructor(options: IPartialTerminalOptions) {
73 | this.options = clone(DEFAULT_OPTIONS);
74 | for (const k of Object.keys(options)) {
75 | if (k in this.options) {
76 | const newValue = options[k as keyof IPartialTerminalOptions] as any;
77 | this.options[k] = newValue;
78 | }
79 | }
80 | }
81 |
82 | public setOption(key: string, value: any): void {
83 | if (!(key in DEFAULT_OPTIONS)) {
84 | throw new Error('No option with key "' + key + '"');
85 | }
86 | if (CONSTRUCTOR_ONLY_OPTIONS.indexOf(key) !== -1) {
87 | throw new Error(`Option "${key}" can only be set in the constructor`);
88 | }
89 | if (this.options[key] === value) {
90 | return;
91 | }
92 |
93 | value = this._sanitizeAndValidateOption(key, value);
94 |
95 | // Don't fire an option change event if they didn't change
96 | if (this.options[key] === value) {
97 | return;
98 | }
99 |
100 | this.options[key] = value;
101 | this._onOptionChange.fire(key);
102 | }
103 |
104 | private _sanitizeAndValidateOption(key: string, value: any): any {
105 | switch (key) {
106 | case 'bellStyle':
107 | case 'cursorStyle':
108 | case 'fontWeight':
109 | case 'fontWeightBold':
110 | case 'rendererType':
111 | case 'wordSeparator':
112 | if (!value) {
113 | value = DEFAULT_OPTIONS[key];
114 | }
115 | break;
116 | case 'cursorWidth':
117 | value = Math.floor(value);
118 | // Fall through for bounds check
119 | case 'lineHeight':
120 | case 'tabStopWidth':
121 | if (value < 1) {
122 | throw new Error(`${key} cannot be less than 1, value: ${value}`);
123 | }
124 | break;
125 | case 'minimumContrastRatio':
126 | value = Math.max(1, Math.min(21, Math.round(value * 10) / 10));
127 | break;
128 | case 'scrollback':
129 | value = Math.min(value, 4294967295);
130 | if (value < 0) {
131 | throw new Error(`${key} cannot be less than 0, value: ${value}`);
132 | }
133 | break;
134 | case 'fastScrollSensitivity':
135 | case 'scrollSensitivity':
136 | if (value <= 0) {
137 | throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`);
138 | }
139 | break;
140 | }
141 | return value;
142 | }
143 |
144 | public getOption(key: string): any {
145 | if (!(key in DEFAULT_OPTIONS)) {
146 | throw new Error(`No option with key "${key}"`);
147 | }
148 | return this.options[key];
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/services/ServiceRegistry.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | *
5 | * This was heavily inspired from microsoft/vscode's dependency injection system (MIT).
6 | */
7 | /*---------------------------------------------------------------------------------------------
8 | * Copyright (c) Microsoft Corporation. All rights reserved.
9 | * Licensed under the MIT License. See License.txt in the project root for license information.
10 | *--------------------------------------------------------------------------------------------*/
11 |
12 | import { IServiceIdentifier } from 'common/services/Services';
13 |
14 | const DI_TARGET = 'di$target';
15 | const DI_DEPENDENCIES = 'di$dependencies';
16 |
17 | export const serviceRegistry: Map> = new Map();
18 |
19 | export function getServiceDependencies(ctor: any): { id: IServiceIdentifier, index: number, optional: boolean }[] {
20 | return ctor[DI_DEPENDENCIES] || [];
21 | }
22 |
23 | export function createDecorator(id: string): IServiceIdentifier {
24 | if (serviceRegistry.has(id)) {
25 | return serviceRegistry.get(id)!;
26 | }
27 |
28 | const decorator: any = function (target: Function, key: string, index: number): any {
29 | if (arguments.length !== 3) {
30 | throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
31 | }
32 |
33 | storeServiceDependency(decorator, target, index);
34 | };
35 |
36 | decorator.toString = () => id;
37 |
38 | serviceRegistry.set(id, decorator);
39 | return decorator;
40 | }
41 |
42 | function storeServiceDependency(id: Function, target: Function, index: number): void {
43 | if ((target as any)[DI_TARGET] === target) {
44 | (target as any)[DI_DEPENDENCIES].push({ id, index });
45 | } else {
46 | (target as any)[DI_DEPENDENCIES] = [{ id, index }];
47 | (target as any)[DI_TARGET] = target;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/services/UnicodeService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 The xterm.js authors. All rights reserved.
3 | * @license MIT
4 | */
5 | import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services';
6 | import { EventEmitter, IEvent } from 'common/EventEmitter';
7 | import { UnicodeV6 } from 'common/input/UnicodeV6';
8 |
9 |
10 | export class UnicodeService implements IUnicodeService {
11 | public serviceBrand: any;
12 |
13 | private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null);
14 | private _active: string = '';
15 | private _activeProvider: IUnicodeVersionProvider;
16 | private _onChange = new EventEmitter();
17 | public get onChange(): IEvent { return this._onChange.event; }
18 |
19 | constructor() {
20 | const defaultProvider = new UnicodeV6();
21 | this.register(defaultProvider);
22 | this._active = defaultProvider.version;
23 | this._activeProvider = defaultProvider;
24 | }
25 |
26 | public get versions(): string[] {
27 | return Object.keys(this._providers);
28 | }
29 |
30 | public get activeVersion(): string {
31 | return this._active;
32 | }
33 |
34 | public set activeVersion(version: string) {
35 | if (!this._providers[version]) {
36 | throw new Error(`unknown Unicode version "${version}"`);
37 | }
38 | this._active = version;
39 | this._activeProvider = this._providers[version];
40 | this._onChange.fire(version);
41 | }
42 |
43 | public register(provider: IUnicodeVersionProvider): void {
44 | this._providers[provider.version] = provider;
45 | }
46 |
47 | /**
48 | * Unicode version dependent interface.
49 | */
50 | public wcwidth(num: number): number {
51 | return this._activeProvider.wcwidth(num);
52 | }
53 |
54 | public getStringCellWidth(s: string): number {
55 | let result = 0;
56 | const length = s.length;
57 | for (let i = 0; i < length; ++i) {
58 | let code = s.charCodeAt(i);
59 | // surrogate pair first
60 | if (0xD800 <= code && code <= 0xDBFF) {
61 | if (++i >= length) {
62 | // this should not happen with strings retrieved from
63 | // Buffer.translateToString as it converts from UTF-32
64 | // and therefore always should contain the second part
65 | // for any other string we still have to handle it somehow:
66 | // simply treat the lonely surrogate first as a single char (UCS-2 behavior)
67 | return result + this.wcwidth(code);
68 | }
69 | const second = s.charCodeAt(i);
70 | // convert surrogate pair to high codepoint only for valid second part (UTF-16)
71 | // otherwise treat them independently (UCS-2 behavior)
72 | if (0xDC00 <= second && second <= 0xDFFF) {
73 | code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
74 | } else {
75 | result += this.wcwidth(second);
76 | }
77 | }
78 | result += this.wcwidth(code);
79 | }
80 | return result;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/common/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig-library-base",
3 | "compilerOptions": {
4 | "lib": [
5 | "es2015"
6 | ],
7 | "outDir": "../../out",
8 | "types": [
9 | "../../node_modules/@types/mocha"
10 | ],
11 | "baseUrl": ".."
12 | },
13 | "include": [
14 | "./**/*",
15 | "../../typings/xterm.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/tsconfig-base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [ "es5" ],
5 | "rootDir": ".",
6 |
7 | "sourceMap": true,
8 | "removeComments": true,
9 | "pretty": true,
10 |
11 | "incremental": true,
12 | "experimentalDecorators": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/src/tsconfig-library-base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig-base.json",
3 | "compilerOptions": {
4 | "composite": true,
5 | "strict": true,
6 | "declarationMap": true,
7 | "experimentalDecorators": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/resources/static/xterm/tsconfig.all.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "include": [],
4 | "references": [
5 | { "path": "./src/browser" },
6 | { "path": "./test/api" },
7 | { "path": "./test/benchmark" },
8 | { "path": "./addons/xterm-addon-attach/src" },
9 | { "path": "./addons/xterm-addon-fit/src" },
10 | { "path": "./addons/xterm-addon-ligatures/src" },
11 | { "path": "./addons/xterm-addon-search/src" },
12 | { "path": "./addons/xterm-addon-serialize/src" },
13 | { "path": "./addons/xterm-addon-serialize/benchmark" },
14 | { "path": "./addons/xterm-addon-unicode11/src" },
15 | { "path": "./addons/xterm-addon-web-links/src" },
16 | { "path": "./addons/xterm-addon-webgl/src" }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------