├── .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 | ![autoFit](https://raw.githubusercontent.com/mervynlam/Pictures/master/20200925100444.gif) 12 | 13 | 心跳检查 14 | 15 | ![heartbeat](https://raw.githubusercontent.com/mervynlam/Pictures/master/20200925101005.gif) 16 | 17 | 断开自动重连 18 | 19 | ![reconnect](https://raw.githubusercontent.com/mervynlam/Pictures/master/20200925101148.gif) 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 | ![连接](http://image.objectspace.cn/%E8%BF%9E%E6%8E%A5.png) 57 | 58 | - ### 连接成功 59 | 60 | ![连接成功](http://image.objectspace.cn/%E8%BF%9E%E6%8E%A5%E6%88%90%E5%8A%9F.png) 61 | 62 | - ### 命令操作 63 | 64 | ls命令: 65 | 66 | ![ls命令](http://image.objectspace.cn/ls%E5%91%BD%E4%BB%A4.png) 67 | 68 | vim编辑器: 69 | 70 | ![vim编辑器](http://image.objectspace.cn/vim%E7%BC%96%E8%BE%91%E5%99%A8.png) 71 | 72 | top命令: 73 | 74 | ![top命令](http://image.objectspace.cn/top%E5%91%BD%E4%BB%A4.png) 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 |
31 |
32 |
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 | --------------------------------------------------------------------------------