diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts
index 36886da181..cdd107e28e 100644
--- a/src/Terminal.test.ts
+++ b/src/Terminal.test.ts
@@ -1011,4 +1011,56 @@ describe('Terminal', () => {
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth
});
});
+
+ describe('get range as HTML', () => {
+ beforeEach(() => {
+ term.wraparoundMode = true;
+ term.write(Array(INIT_COLS + 1).join('0'));
+ term.write(Array(INIT_COLS + 1).join('1'));
+ term.write(Array(INIT_COLS + 1).join('2'));
+ (term as any)._colorManager = {
+ colors: {
+ foreground: { css: 'white' },
+ background: { css: 'black' },
+ ansi: {
+ 1: { css: 'red' },
+ 2: { css: 'green' },
+ 209: { css: '#ff875f' }
+ }
+ }
+ };
+ });
+
+ afterEach(() => {
+ term.clear();
+ });
+
+ it('should work within single lines', () => {
+ const html = term.getRangeAsHTML({ startRow: 1, startColumn: 10, endRow: 1, endColumn: 15 });
+ expect(html).eq('
');
+ });
+
+ it('should work within multiple lines', () => {
+ const html = term.getRangeAsHTML({ startRow: 0, startColumn: INIT_COLS - 5, endRow: 1, endColumn: 5 });
+ expect(html).eq('');
+ });
+
+ it('should work with multiple styles', () => {
+ term.write('1\x1b[1m2');
+ const html = term.getRangeAsHTML({ startRow: 3, startColumn: 0, endRow: 3, endColumn: 2 });
+ expect(html).eq('');
+ });
+
+ it('should work with italics and underlines', () => {
+ term.write('\x1b[3mitalic\x1b[0m\x1b[4munderline');
+ const html = term.getRangeAsHTML({ startRow: 3, startColumn: 0, endRow: 3, endColumn: 15 });
+ expect(html).eq('');
+ });
+
+ it('should work with ANSI palette, 256 color palette and TrueColor', () => {
+ term.write('\x1b[31mred\x1b[0m\x1b[42mgreenbg\x1b[0m\x1b[38;5;209msalmon1\x1b[38;2;255;100;0mtruecolor');
+ const html = term.getRangeAsHTML({ startRow: 3, startColumn: 0, endRow: 3, endColumn: 26 });
+ expect(html).eq('redgreenbgsalmon1truecolor
');
+ });
+ });
});
diff --git a/src/Terminal.ts b/src/Terminal.ts
index ccb41527fc..41e4fabd66 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -44,7 +44,7 @@ import { DomRenderer } from './renderer/dom/DomRenderer';
import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData } from 'common/Types';
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
import { EventEmitter, IEvent } from 'common/EventEmitter';
-import { Attributes, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
+import { Attributes, AttributeData, CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { applyWindowsMode } from './WindowsMode';
import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
@@ -1904,6 +1904,72 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.buffer.tabs[this.buffer.x] = true;
}
+ public getRangeAsHTML(range: ISelectionPosition): string {
+ const fontFamily = this.optionsService.getOption('fontFamily');
+ let html = ``;
+ if (range.startRow === range.endRow) {
+ html += this._getRowAsHTML(range.startRow, range.startColumn, range.endColumn);
+ } else {
+ html += this._getRowAsHTML(range.startRow, range.startColumn, this.cols);
+ for (let y = range.startRow + 1; y < range.endRow; y++) {
+ html += this._getRowAsHTML(y, 0, this.cols);
+ }
+ html += this._getRowAsHTML(range.endRow, 0, range.endColumn);
+ }
+ html += '
';
+ return html;
+ }
+
+ private _getCSSColor(mode: Attributes, color: number): string | null {
+ if (mode === Attributes.CM_RGB) {
+ let css = '#';
+ for (const channel of AttributeData.toColorRGB(color)) {
+ if (channel < 16) {
+ css += '0';
+ }
+ css += channel.toString(16);
+ }
+ return css;
+ }
+ if (mode === Attributes.CM_P16 || mode === Attributes.CM_P256) {
+ return this._colorManager.colors.ansi[color].css;
+ }
+ return null;
+ }
+
+ private _getRowAsHTML(y: number, start: number, end: number): string {
+ let html = '';
+ let lastStyle = null;
+ const line = this.buffers.active.lines.get(y);
+ const cell = new CellData();
+ for (let i = start; i < end; i++) {
+ line.loadCell(i, cell);
+ const fg = this._getCSSColor(cell.getFgColorMode(), cell.getFgColor()) || this._colorManager.colors.foreground.css;
+ const bg = this._getCSSColor(cell.getBgColorMode(), cell.getBgColor()) || this._colorManager.colors.background.css;
+
+ let style = `color: ${fg}; background: ${bg};`;
+ if (cell.isBold()) {
+ style += ' font-weight: bold;';
+ }
+ if (cell.isItalic()) {
+ style += ' font-style: italic;';
+ }
+ if (cell.isUnderline()) {
+ style += ' text-decoration: underline;';
+ }
+ if (style !== lastStyle) {
+ if (lastStyle) {
+ html += '';
+ }
+ html += ``;
+ lastStyle = style;
+ }
+ html += line.getString(i) || ' ';
+ }
+ html += '
';
+ return html;
+ }
+
// TODO: Remove cancel function and cancelEvents option
public cancel(ev: Event, force?: boolean): boolean {
if (!this.options.cancelEvents && !force) {
diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts
index 6b92bf0b9c..9b94781ed8 100644
--- a/src/TestUtils.test.ts
+++ b/src/TestUtils.test.ts
@@ -121,6 +121,9 @@ export class MockTerminal implements ITerminal {
writeUtf8(data: Uint8Array): void {
throw new Error('Method not implemented.');
}
+ getRangeAsHTML(range: ISelectionPosition): string {
+ throw new Error('Method not implemented.');
+ }
bracketedPasteMode: boolean;
mouseHelper: IMouseHelper;
renderer: IRenderer;
diff --git a/src/Types.ts b/src/Types.ts
index 721ab063dd..3845e41b65 100644
--- a/src/Types.ts
+++ b/src/Types.ts
@@ -269,6 +269,7 @@ export interface IPublicTerminal extends IDisposable {
clear(): void;
write(data: string): void;
writeUtf8(data: Uint8Array): void;
+ getRangeAsHTML(range: ISelectionPosition): string;
refresh(start: number, end: number): void;
reset(): void;
}
diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts
index 0dae4def84..69874cf672 100644
--- a/src/public/Terminal.ts
+++ b/src/public/Terminal.ts
@@ -125,6 +125,9 @@ export class Terminal implements ITerminalApi {
public writeUtf8(data: Uint8Array): void {
this._core.writeUtf8(data);
}
+ public getRangeAsHTML(range: ISelectionPosition): string {
+ return this._core.getRangeAsHTML(range)
+ }
public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'rendererType' | 'termName'): string;
public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean;
public getOption(key: 'colors'): string[];
diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts
index 042ca5379d..7e8506ff7a 100644
--- a/typings/xterm.d.ts
+++ b/typings/xterm.d.ts
@@ -767,6 +767,12 @@ declare module 'xterm' {
*/
setOption(key: string, value: any): void;
+ /**
+ * Returns HTML representing the specified content range.
+ * @param range Range of cells to be converted.
+ */
+ getRangeAsHTML(range: ISelectionPosition): string;
+
/**
* Tells the renderer to refresh terminal content between two rows
* (inclusive) at the next opportunity.