terminal<\/span>/g) || []).length, 1, output);
- });
-
- it('empty terminal with default options', async () => {
- const output = serializeAddon.serializeAsHTML();
- assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output);
- });
-
- it('empty terminal with custom options', async () => {
- terminal.options.fontFamily = 'verdana';
- terminal.options.fontSize = 20;
- terminal.options.theme = {
- foreground: '#ff00ff',
- background: '#00ff00'
- };
- const output = serializeAddon.serializeAsHTML({
- includeGlobalBackground: true
+ it('empty terminal with no selection', () => {
+ const output = serializeAddon.serializeAsHTML({
+ onlySelection: true
+ });
+ assert.equal(output, '');
});
- assert.equal((output.match(/color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;/g) || []).length, 1, output);
- });
- it('empty terminal with background included', async () => {
- const output = serializeAddon.serializeAsHTML({
- includeGlobalBackground: true
+ it('basic terminal with selection', async () => {
+ await writeP(terminal, ' terminal ');
+ terminal.select(1, 0, 8);
+
+ const output = serializeAddon.serializeAsHTML({
+ onlySelection: true
+ });
+ assert.equal((output.match(/terminal<\/span><\/div>/g) || []).length, 1, output);
+ });
+
+ it('cells with bold styling', async () => {
+ await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' ');
+
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output);
+ });
+
+ it('cells with italic styling', async () => {
+ await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' ');
+
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output);
+ });
+
+ it('cells with inverse styling', async () => {
+ await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' ');
+
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output);
+ });
+
+ it('cells with underline styling', async () => {
+ await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' ');
+
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output);
+ });
+
+ it('cells with invisible styling', async () => {
+ await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' ');
+
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output);
+ });
+
+ it('cells with dim styling', async () => {
+ await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' ');
+
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output);
+ });
+
+ it('cells with strikethrough styling', async () => {
+ await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' ');
+
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output);
+ });
+
+ it('cells with combined styling', async () => {
+ await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' ');
+
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/ <\/span>/g) || []).length, 1, output);
+ assert.equal((output.match(/termi<\/span>/g) || []).length, 1, output);
+ assert.equal((output.match(/nal<\/span>/g) || []).length, 1, output);
+ });
+
+ it('cells with color styling', async () => {
+ await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' ');
+
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output);
+ });
+
+ it('cells with background styling', async () => {
+ await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' ');
+
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output);
+ });
+
+ it('empty terminal with default options', async () => {
+ const output = serializeAddon.serializeAsHTML();
+ assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output);
+ });
+
+ it('empty terminal with custom options', async () => {
+ terminal.options.fontFamily = 'verdana';
+ terminal.options.fontSize = 20;
+ terminal.options.theme = {
+ foreground: '#ff00ff',
+ background: '#00ff00'
+ };
+ const output = serializeAddon.serializeAsHTML({
+ includeGlobalBackground: true
+ });
+ assert.equal((output.match(/color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;/g) || []).length, 1, output);
+ });
+
+ it('empty terminal with background included', async () => {
+ const output = serializeAddon.serializeAsHTML({
+ includeGlobalBackground: true
+ });
+ assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output);
});
- assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output);
});
});
diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts
index 8d6f8b36..ed71e4e1 100644
--- a/addons/xterm-addon-serialize/src/SerializeAddon.ts
+++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts
@@ -7,6 +7,7 @@
import { Terminal, ITerminalAddon, IBuffer, IBufferCell, IBufferRange } from 'xterm';
import { IColorSet } from 'browser/Types';
+import { IAttributeData } from 'common/Types';
function constrain(value: number, low: number, high: number): number {
return Math.max(low, Math.min(value, high));
@@ -62,17 +63,17 @@ abstract class BaseSerializeHandler {
protected _serializeString(): string { return ''; }
}
-function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean {
+function equalFg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
return cell1.getFgColorMode() === cell2.getFgColorMode()
&& cell1.getFgColor() === cell2.getFgColor();
}
-function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean {
+function equalBg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
return cell1.getBgColorMode() === cell2.getBgColorMode()
&& cell1.getBgColor() === cell2.getBgColor();
}
-function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
+function equalFlags(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
return cell1.isInverse() === cell2.isInverse()
&& cell1.isBold() === cell2.isBold()
&& cell1.isUnderline() === cell2.isUnderline()
@@ -229,7 +230,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._nullCellCount = 0;
}
- private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] {
+ private _diffStyle(cell: IBufferCell | IAttributeData, oldCell: IBufferCell): number[] {
const sgrSeq: number[] = [];
const fgChanged = !equalFg(cell, oldCell);
const bgChanged = !equalBg(cell, oldCell);
@@ -393,6 +394,15 @@ class StringSerializeHandler extends BaseSerializeHandler {
moveRight(realCursorCol - this._lastCursorCol);
}
+ // Restore the cursor's current style, see https://github.com/xtermjs/xterm.js/issues/3677
+ // HACK: Internal API access since it's awkward to expose this in the API and serialize will
+ // likely be the only consumer
+ const curAttrData: IAttributeData = (this._terminal as any)._core._inputHandler._curAttrData;
+ const sgrSeq = this._diffStyle(curAttrData, this._cursorStyle);
+ if (sgrSeq.length > 0) {
+ content += `\u001b[${sgrSeq.join(';')}m`;
+ }
+
return content;
}
}
diff --git a/css/xterm.css b/css/xterm.css
index ded93074..9b9c47f8 100644
--- a/css/xterm.css
+++ b/css/xterm.css
@@ -180,6 +180,12 @@
}
.xterm-find-result-decoration {
- background-color: blue;
+ background-color: yellow;
opacity: 60%;
-}
\ No newline at end of file
+}
+.xterm-decoration-overview-ruler {
+ z-index: 7;
+ position: absolute;
+ top: 0;
+ right: 0;
+}
diff --git a/demo/client.ts b/demo/client.ts
index 65bdc6ce..8dd18da4 100644
--- a/demo/client.ts
+++ b/demo/client.ts
@@ -152,6 +152,7 @@ if (document.location.pathname === '/test') {
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
document.getElementById('load-test').addEventListener('click', loadTest);
document.getElementById('add-decoration').addEventListener('click', addDecoration);
+ document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler);
}
function createTerminal(): void {
@@ -549,9 +550,23 @@ function loadTest() {
}
function addDecoration() {
+ term.options['overviewRulerWidth'] = 15;
const marker = term.addMarker(1);
- const decoration = term.registerDecoration({ marker });
- decoration.onRender(() => {
- decoration.element.style.backgroundColor = 'red';
+ const decoration = term.registerDecoration({ marker, overviewRulerOptions: { color: '#ef2929'} });
+ decoration.onRender((e) => {
+ if (e.classList.value === 'xterm-decoration') {
+ e.style.backgroundColor = '#ef2929';
+ }
});
}
+
+function addOverviewRuler() {
+ term.options['overviewRulerWidth'] = 15;
+ term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }});
+ term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }});
+ term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }});
+ term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }});
+ term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }});
+ term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }});
+}
+
diff --git a/demo/index.html b/demo/index.html
index 590bf1e6..13748e9b 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -70,6 +70,7 @@
+