From 0bd469f505d9dec1447fdf82c8e7772e0451fe7e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 16 Jan 2017 14:53:35 -0800 Subject: [PATCH 1/2] Implement CSI Ps SP q Set cursor style Fixes #480 --- demo/style.css | 4 ---- src/InputHandler.ts | 30 ++++++++++++++++++++++++++++++ src/Interfaces.ts | 1 + src/Parser.ts | 9 +++++++-- src/xterm.css | 36 ++++++++++++++++++++++++++++++++++-- src/xterm.js | 6 ++++++ 6 files changed, 78 insertions(+), 8 deletions(-) diff --git a/demo/style.css b/demo/style.css index 2f05e309a0..7138962123 100644 --- a/demo/style.css +++ b/demo/style.css @@ -20,7 +20,3 @@ h1 { color: #fafafa; padding: 2px; } - -#terminal-container .terminal:focus .terminal-cursor { - background-color: #fafafa; -} diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 00626cebf0..975d7c113d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1392,6 +1392,36 @@ export class InputHandler implements IInputHandler { this._terminal.charsets = [null]; // ?? } + /** + * CSI Ps SP q Set cursor style (DECSCUSR, VT520). + * Ps = 0 -> blinking block. + * Ps = 1 -> blinking block (default). + * Ps = 2 -> steady block. + * Ps = 3 -> blinking underline. + * Ps = 4 -> steady underline. + * Ps = 5 -> blinking bar (xterm). + * Ps = 6 -> steady bar (xterm). + */ + public setCursorStyle(params?: number[]): void { + const param = params[0] < 1 ? 1 : params[0]; + switch (param) { + case 1: + case 2: + this._terminal.setOption('cursorStyle', 'block'); + break; + case 3: + case 4: + this._terminal.setOption('cursorStyle', 'underline'); + break; + case 5: + case 6: + this._terminal.setOption('cursorStyle', 'bar'); + break; + } + const isBlinking = param % 2 === 1; + this._terminal.setOption('cursorBlink', isBlinking); + } + /** * CSI Ps ; Ps r * Set Scrolling Region [top;bottom] (default = full size of win- diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 8780928662..049a599ca7 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -79,6 +79,7 @@ export interface IInputHandler { /** CSI m */ charAttributes(params?: number[]): void; /** CSI n */ deviceStatus(params?: number[]): void; /** CSI p */ softReset(params?: number[]): void; + /** CSI q */ setCursorStyle(params?: number[]): void; /** CSI r */ setScrollRegion(params?: number[]): void; /** CSI s */ saveCursor(params?: number[]): void; /** CSI u */ restoreCursor(params?: number[]): void; diff --git a/src/Parser.ts b/src/Parser.ts index 860baaa6a6..02028005e6 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -95,7 +95,7 @@ csiParamStateHandler[' '] = (parser) => parser.setPostfix(' '); csiParamStateHandler['\''] = (parser) => parser.setPostfix('\''); csiParamStateHandler[';'] = (parser) => parser.finalizeParam(); -const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string) => void} = {}; +const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string) => void} = {}; csiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params); csiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params); csiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params); @@ -136,6 +136,11 @@ csiStateHandler['p'] = (handler, params, prefix) => { case '!': handler.softReset(params); break; } }; +csiStateHandler['q'] = (handler, params, prefix, postfix) => { + if (postfix === ' ') { + handler.setCursorStyle(params); + } +}; csiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params); csiStateHandler['s'] = (handler, params) => handler.saveCursor(params); csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params); @@ -472,7 +477,7 @@ export class Parser { case ParserState.CSI: if (ch in csiStateHandler) { - csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix); + csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix); } else { this._terminal.error('Unknown CSI code: %s.', ch); } diff --git a/src/xterm.css b/src/xterm.css index 623164a250..27638e1475 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -71,7 +71,7 @@ resize: none; } -.terminal .terminal-cursor { +.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor { background-color: #fff; color: #000; } @@ -82,7 +82,7 @@ background-color: transparent; } -.terminal.focus.xterm-cursor-blink .terminal-cursor { +.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink .terminal-cursor { animation: xterm-cursor-blink 1.2s infinite step-end; } @@ -97,6 +97,38 @@ } } +.terminal.xterm-cursor-style-bar .terminal-cursor, +.terminal.xterm-cursor-style-underline .terminal-cursor { + position: relative; +} +.terminal.xterm-cursor-style-bar .terminal-cursor::before, +.terminal.xterm-cursor-style-underline .terminal-cursor::before { + content: ""; + display: block; + position: absolute; + background-color: #fff; +} +.terminal.xterm-cursor-style-bar .terminal-cursor::before { + top: 0; + bottom: 0; + left: 0; + width: 1px; +} +.terminal.xterm-cursor-style-underline .terminal-cursor::before { + bottom: 0; + left: 0; + right: 0; + height: 1px; +} +.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink .terminal-cursor::before, +.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink .terminal-cursor::before { + animation: xterm-cursor-non-bar-blink 1.2s infinite step-end; +} +@keyframes xterm-cursor-non-bar-blink { + 0% { background-color: #fff; } + 50% { background-color: transparent; } +} + .terminal .composition-view { background: #000; color: #FFF; diff --git a/src/xterm.js b/src/xterm.js index 24a840c092..7f50295fc5 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -353,6 +353,7 @@ Terminal.defaults = { termName: 'xterm', geometry: [80, 24], cursorBlink: false, + cursorStyle: 'block', visualBell: false, popOnBell: false, scrollback: 1000, @@ -427,6 +428,11 @@ Terminal.prototype.setOption = function(key, value) { this.options[key] = value; switch (key) { case 'cursorBlink': this.element.classList.toggle('xterm-cursor-blink', value); break; + case 'cursorStyle': + // Style 'block' applies with no class + this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline'); + this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar'); + break; } }; From 9227aed90607820c5ca4ce92228a38ec0ad310ac Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 18 Jan 2017 10:30:35 -0800 Subject: [PATCH 2/2] Add InputHandler.setCursorStyle test --- src/InputHandler.test.ts | 49 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/InputHandler.test.ts diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts new file mode 100644 index 0000000000..ecc6bf9a1f --- /dev/null +++ b/src/InputHandler.test.ts @@ -0,0 +1,49 @@ +import { assert } from 'chai'; +import { InputHandler } from './InputHandler'; + +describe('InputHandler', () => { + describe('setCursorStyle', () => { + it('should call Terminal.setOption with correct params', () => { + let options = {}; + let terminal = { + setOption: (option, value) => options[option] = value + }; + let inputHandler = new InputHandler(terminal); + + inputHandler.setCursorStyle([0]); + assert.equal(options['cursorStyle'], 'block'); + assert.equal(options['cursorBlink'], true); + + options = {}; + inputHandler.setCursorStyle([1]); + assert.equal(options['cursorStyle'], 'block'); + assert.equal(options['cursorBlink'], true); + + options = {}; + inputHandler.setCursorStyle([2]); + assert.equal(options['cursorStyle'], 'block'); + assert.equal(options['cursorBlink'], false); + + options = {}; + inputHandler.setCursorStyle([3]); + assert.equal(options['cursorStyle'], 'underline'); + assert.equal(options['cursorBlink'], true); + + options = {}; + inputHandler.setCursorStyle([4]); + assert.equal(options['cursorStyle'], 'underline'); + assert.equal(options['cursorBlink'], false); + + options = {}; + inputHandler.setCursorStyle([5]); + assert.equal(options['cursorStyle'], 'bar'); + assert.equal(options['cursorBlink'], true); + + options = {}; + inputHandler.setCursorStyle([6]); + assert.equal(options['cursorStyle'], 'bar'); + assert.equal(options['cursorBlink'], false); + + }); + }); +});