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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions demo/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,3 @@ h1 {
color: #fafafa;
padding: 2px;
}

#terminal-container .terminal:focus .terminal-cursor {
background-color: #fafafa;
}
49 changes: 49 additions & 0 deletions src/InputHandler.test.ts
Original file line number Diff line number Diff line change
@@ -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);

});
});
});
30 changes: 30 additions & 0 deletions src/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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-
Expand Down
1 change: 1 addition & 0 deletions src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 7 additions & 2 deletions src/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
36 changes: 34 additions & 2 deletions src/xterm.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}

Expand All @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/xterm.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ Terminal.defaults = {
termName: 'xterm',
geometry: [80, 24],
cursorBlink: false,
cursorStyle: 'block',
visualBell: false,
popOnBell: false,
scrollback: 1000,
Expand Down Expand Up @@ -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;
}
};

Expand Down