mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
improvements for html serialize
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* (EXPERIMENTAL) This Addon is still under development
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
|
||||
import { Terminal, ITerminalAddon, IBuffer, IBufferCell, IBufferRange } from 'xterm';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
|
||||
function constrain(value: number, low: number, high: number): number {
|
||||
@@ -19,18 +19,25 @@ abstract class BaseSerializeHandler {
|
||||
) {
|
||||
}
|
||||
|
||||
public serialize(startRow: number, endRow: number): string {
|
||||
public serialize(range: IBufferRange): string {
|
||||
// we need two of them to flip between old and new cell
|
||||
const cell1 = this._buffer.getNullCell();
|
||||
const cell2 = this._buffer.getNullCell();
|
||||
let oldCell = cell1;
|
||||
|
||||
const startRow = range.start.x;
|
||||
const endRow = range.end.x;
|
||||
const startColumn = range.start.y;
|
||||
const endColumn = range.end.y;
|
||||
|
||||
this._beforeSerialize(endRow - startRow, startRow, endRow);
|
||||
|
||||
for (let row = startRow; row < endRow; row++) {
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
const line = this._buffer.getLine(row);
|
||||
if (line) {
|
||||
for (let col = 0; col < line.length; col++) {
|
||||
const startLineColumn = row !== range.start.x ? 0 : startColumn;
|
||||
const endLineColumn = row !== range.end.x ? line.length : endColumn;
|
||||
for (let col = startLineColumn; col < endLineColumn; col++) {
|
||||
const c = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
|
||||
if (!c) {
|
||||
console.warn(`Can't get cell at row=${row}, col=${col}`);
|
||||
@@ -40,7 +47,7 @@ abstract class BaseSerializeHandler {
|
||||
oldCell = c;
|
||||
}
|
||||
}
|
||||
this._rowEnd(row, row === endRow - 1);
|
||||
this._rowEnd(row, row === endRow);
|
||||
}
|
||||
|
||||
this._afterSerialize();
|
||||
@@ -400,17 +407,37 @@ export class SerializeAddon implements ITerminalAddon {
|
||||
}
|
||||
|
||||
private _serializeBuffer(terminal: Terminal, buffer: IBuffer, scrollback?: number): string {
|
||||
const maxRows = buffer.length;
|
||||
const maxRows = buffer.length - 1;
|
||||
const handler = new StringSerializeHandler(buffer, terminal);
|
||||
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);
|
||||
return handler.serialize(maxRows - correctRows, maxRows);
|
||||
return handler.serialize({
|
||||
start: { x: maxRows - correctRows, y: 0 },
|
||||
end: { x: maxRows, y: 0 }
|
||||
});
|
||||
}
|
||||
|
||||
private _htmlserializeBuffer(terminal: Terminal, buffer: IBuffer, scrollback?: number): string {
|
||||
const maxRows = buffer.length;
|
||||
private _htmlserializeBuffer(terminal: Terminal, buffer: IBuffer, options: Partial<IHtmlSerializeOptions>): string {
|
||||
const handler = new HTMLSerializeHandler(buffer, terminal);
|
||||
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);
|
||||
return handler.serialize(maxRows - correctRows, maxRows);
|
||||
const onlySelection = options.onlySelection ?? true;
|
||||
if (!onlySelection) {
|
||||
const maxRows = buffer.length;
|
||||
const scrollback = options.scrollback;
|
||||
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);
|
||||
return handler.serialize({
|
||||
start: { x: maxRows - correctRows, y: 0 },
|
||||
end: { x: maxRows, y: 0 }
|
||||
});
|
||||
}
|
||||
|
||||
const selection = this._terminal?.getSelectionPosition();
|
||||
if (selection !== undefined) {
|
||||
return handler.serialize({
|
||||
start: { x: selection.startRow, y: selection.startColumn },
|
||||
end: { x: selection.endRow, y: selection.endColumn }
|
||||
});
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private _serializeModes(terminal: Terminal): string {
|
||||
@@ -467,12 +494,12 @@ export class SerializeAddon implements ITerminalAddon {
|
||||
return content;
|
||||
}
|
||||
|
||||
public htmlserialize(options?: IHtmlSerializeOptions): string {
|
||||
public htmlserialize(options?: Partial<IHtmlSerializeOptions>): string {
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot use addon until it has been loaded');
|
||||
}
|
||||
|
||||
return this._htmlserializeBuffer(this._terminal, this._terminal.buffer.normal, options?.scrollback);
|
||||
return this._htmlserializeBuffer(this._terminal, this._terminal.buffer.normal, options || {});
|
||||
}
|
||||
|
||||
public dispose(): void { }
|
||||
@@ -486,7 +513,8 @@ interface ISerializeOptions {
|
||||
}
|
||||
|
||||
interface IHtmlSerializeOptions {
|
||||
scrollback?: number;
|
||||
scrollback: number;
|
||||
onlySelection: boolean;
|
||||
}
|
||||
|
||||
export class HTMLSerializeHandler extends BaseSerializeHandler {
|
||||
@@ -532,8 +560,16 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
|
||||
protected _beforeSerialize(rows: number, start: number, end: number): void {
|
||||
this._htmlContent += '<html><head><meta name=\'generator\' content=\'xtermjs\'/>'
|
||||
+ '<meta http-equiv=\'Content-Type\' content=\'text/html; charset=UTF-8\'/></head><body><!--StartFragment--><pre>';
|
||||
// TODO: fetch options and remove hardcoded values
|
||||
this._htmlContent += '<div style=\'color: #ffffff; background-color: #000000; font-family: Fira Code, courier-new, courier, monospace; font-size: 15px;\'>';
|
||||
|
||||
const foreground = this._terminal.options.theme?.foreground ?? '#ffffff';
|
||||
const background = this._terminal.options.theme?.background ?? '#000000';
|
||||
|
||||
const globalStyleDefinitions = [];
|
||||
globalStyleDefinitions.push('color: ' + foreground + ';');
|
||||
globalStyleDefinitions.push('background-color: ' + background + ';');
|
||||
globalStyleDefinitions.push('font-family: ' + this._terminal.options.fontFamily + ';');
|
||||
globalStyleDefinitions.push('font-size: ' + this._terminal.options.fontSize + 'px;');
|
||||
this._htmlContent += '<div style=\'' + globalStyleDefinitions.join(' ') + '\'>';
|
||||
}
|
||||
|
||||
protected _afterSerialize(): void {
|
||||
|
||||
+36
-45
@@ -59,27 +59,27 @@ interface IDemoAddon<T extends AddonType> {
|
||||
name: T;
|
||||
canChange: boolean;
|
||||
ctor:
|
||||
T extends 'attach' ? typeof AttachAddon :
|
||||
T extends 'fit' ? typeof FitAddon :
|
||||
T extends 'search' ? typeof SearchAddon :
|
||||
T extends 'serialize' ? typeof SerializeAddon :
|
||||
T extends 'web-links' ? typeof WebLinksAddon :
|
||||
T extends 'unicode11' ? typeof Unicode11Addon :
|
||||
T extends 'ligatures' ? typeof LigaturesAddon :
|
||||
typeof WebglAddon;
|
||||
instance?:
|
||||
T extends 'attach' ? AttachAddon :
|
||||
T extends 'fit' ? FitAddon :
|
||||
T extends 'search' ? SearchAddon :
|
||||
T extends 'serialize' ? SerializeAddon :
|
||||
T extends 'web-links' ? WebLinksAddon :
|
||||
T extends 'webgl' ? WebglAddon :
|
||||
T extends 'unicode11' ? typeof Unicode11Addon :
|
||||
T extends 'ligatures' ? typeof LigaturesAddon :
|
||||
never;
|
||||
T extends 'attach' ? typeof AttachAddon :
|
||||
T extends 'fit' ? typeof FitAddon :
|
||||
T extends 'search' ? typeof SearchAddon :
|
||||
T extends 'serialize' ? typeof SerializeAddon :
|
||||
T extends 'web-links' ? typeof WebLinksAddon :
|
||||
T extends 'unicode11' ? typeof Unicode11Addon :
|
||||
T extends 'ligatures' ? typeof LigaturesAddon :
|
||||
typeof WebglAddon;
|
||||
instance?:
|
||||
T extends 'attach' ? AttachAddon :
|
||||
T extends 'fit' ? FitAddon :
|
||||
T extends 'search' ? SearchAddon :
|
||||
T extends 'serialize' ? SerializeAddon :
|
||||
T extends 'web-links' ? WebLinksAddon :
|
||||
T extends 'webgl' ? WebglAddon :
|
||||
T extends 'unicode11' ? typeof Unicode11Addon :
|
||||
T extends 'ligatures' ? typeof LigaturesAddon :
|
||||
never;
|
||||
}
|
||||
|
||||
const addons: { [T in AddonType]: IDemoAddon<T> } = {
|
||||
const addons: { [T in AddonType]: IDemoAddon<T>} = {
|
||||
attach: { name: 'attach', ctor: AttachAddon, canChange: false },
|
||||
fit: { name: 'fit', ctor: FitAddon, canChange: false },
|
||||
search: { name: 'search', ctor: SearchAddon, canChange: true },
|
||||
@@ -147,7 +147,7 @@ if (document.location.pathname === '/test') {
|
||||
createTerminal();
|
||||
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
|
||||
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
|
||||
document.getElementById('htmlserialize').addEventListener('click', htmlserializeButtonHandler);
|
||||
document.getElementById('htmlserialize').addEventListener('click', htmlSerializeButtonHandler);
|
||||
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
|
||||
document.getElementById('load-test').addEventListener('click', loadTest);
|
||||
}
|
||||
@@ -187,7 +187,7 @@ function createTerminal(): void {
|
||||
const rows = size.rows;
|
||||
const url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
|
||||
|
||||
fetch(url, { method: 'POST' });
|
||||
fetch(url, {method: 'POST'});
|
||||
});
|
||||
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
||||
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
|
||||
@@ -217,7 +217,7 @@ function createTerminal(): void {
|
||||
// Set terminal size again to set the specific dimensions on the demo
|
||||
updateTerminalSize();
|
||||
|
||||
fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, { method: 'POST' }).then((res) => {
|
||||
fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then((res) => {
|
||||
res.text().then((processId) => {
|
||||
pid = processId;
|
||||
socketURL += processId;
|
||||
@@ -262,7 +262,7 @@ function runFakeTerminal(): void {
|
||||
if (ev.keyCode === 13) {
|
||||
term.prompt();
|
||||
} else if (ev.keyCode === 8) {
|
||||
// Do not delete the prompt
|
||||
// Do not delete the prompt
|
||||
if (term._core.buffer.x > 2) {
|
||||
term.write('\b \b');
|
||||
}
|
||||
@@ -354,7 +354,7 @@ function initOptions(term: TerminalType): void {
|
||||
} else if (o === 'scrollSensitivity') {
|
||||
term.options.scrollSensitivity = parseFloat(input.value);
|
||||
updateTerminalSize();
|
||||
} else if (o === 'scrollback') {
|
||||
} else if(o === 'scrollback') {
|
||||
term.options.scrollback = parseInt(input.value);
|
||||
setTimeout(() => updateTerminalSize(), 5);
|
||||
} else {
|
||||
@@ -381,7 +381,7 @@ function initAddons(term: TerminalType): void {
|
||||
if (!addon.canChange) {
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
if (name === 'unicode11' && checkbox.checked) {
|
||||
if(name === 'unicode11' && checkbox.checked) {
|
||||
term.unicode.activeVersion = '11';
|
||||
}
|
||||
addDomListener(checkbox, 'change', () => {
|
||||
@@ -448,30 +448,21 @@ function serializeButtonHandler(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function htmlserializeButtonHandler(): void {
|
||||
function htmlSerializeButtonHandler(): void {
|
||||
const output = addons.serialize.instance.htmlserialize();
|
||||
document.getElementById('htmlserialize-output').innerText = output;
|
||||
|
||||
var type = "text/html";
|
||||
var blob = new Blob([output], { type });
|
||||
// @ts-ignore
|
||||
var data = [new ClipboardItem({ [type]: blob })];
|
||||
|
||||
const permissionName = "clipboard-write" as PermissionName;
|
||||
navigator.permissions.query({name: permissionName }).then(result => {
|
||||
if (result.state == "granted" || result.state == "prompt") {
|
||||
navigator.clipboard.write(data).then(
|
||||
() => {
|
||||
document.getElementById("htmlserialize-output-result").innerText
|
||||
// Deprecated, but the most supported for now.
|
||||
function listener(e) {
|
||||
e.clipboardData.setData("text/html", output);
|
||||
e.clipboardData.setData("text/plain", output);
|
||||
e.preventDefault();
|
||||
}
|
||||
document.addEventListener("copy", listener);
|
||||
document.execCommand("copy");
|
||||
document.removeEventListener("copy", listener);
|
||||
document.getElementById("htmlserialize-output-result").innerText
|
||||
= "Copied to clipboard";
|
||||
},
|
||||
() => {
|
||||
document.getElementById("htmlserialize-output-result").innerText
|
||||
= "Can't copy to clipboard.";
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user