From 37a7d32bc8f9fce07986cf07014605d949bf6344 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Thu, 6 Jun 2019 20:53:14 +0200 Subject: [PATCH] copy as html if possible (fixes #503) --- .../src/frontends/xtermFrontend.ts | 73 +++++++++++++++++-- terminus-terminal/tsconfig.typings.json | 5 +- terminus-terminal/webpack.config.js | 7 +- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/terminus-terminal/src/frontends/xtermFrontend.ts b/terminus-terminal/src/frontends/xtermFrontend.ts index cdf056ea..5599f9cb 100644 --- a/terminus-terminal/src/frontends/xtermFrontend.ts +++ b/terminus-terminal/src/frontends/xtermFrontend.ts @@ -5,6 +5,12 @@ import { enableLigatures } from 'xterm-addon-ligatures' import { SearchAddon, ISearchOptions } from './xtermSearchAddon' import './xterm.css' import deepEqual = require('deep-equal') +import { Attributes, AttributeData, CellData } from 'xterm/src/core/buffer/BufferLine' + +const COLOR_NAMES = [ + 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', + 'brightBlack', 'brightRed', 'brightGreen', 'brightYellow', 'brightBlue', 'brightMagenta', 'brightCyan', 'brightWhite' +] /** @hidden */ export class XTermFrontend extends Frontend { @@ -128,7 +134,10 @@ export class XTermFrontend extends Frontend { } copySelection (): void { - (navigator as any).clipboard.writeText(this.getSelection()) + require('electron').remote.clipboard.write({ + text: this.getSelection(), + html: this.getSelectionAsHTML() + }) } clearSelection (): void { @@ -189,13 +198,8 @@ export class XTermFrontend extends Frontend { cursor: config.terminal.colorScheme.cursor, } - const colorNames = [ - 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', - 'brightBlack', 'brightRed', 'brightGreen', 'brightYellow', 'brightBlue', 'brightMagenta', 'brightCyan', 'brightWhite' - ] - - for (let i = 0; i < colorNames.length; i++) { - theme[colorNames[i]] = config.terminal.colorScheme.colors[i] + for (let i = 0; i < COLOR_NAMES.length; i++) { + theme[COLOR_NAMES[i]] = config.terminal.colorScheme.colors[i] } if (this.xtermCore._colorManager && !deepEqual(this.configuredTheme, theme)) { @@ -224,6 +228,59 @@ export class XTermFrontend extends Frontend { private setFontSize () { this.xterm.setOption('fontSize', this.configuredFontSize * Math.pow(1.1, this.zoom)) } + + private getSelectionAsHTML (): string { + let html = `
` + const selection = this.xterm.getSelectionPosition() + if (!selection) { + return null + } + if (selection.startRow === selection.endRow) { + html += this.getLineAsHTML(selection.startRow, selection.startColumn, selection.endColumn) + } else { + html += this.getLineAsHTML(selection.startRow, selection.startColumn, this.xterm.cols) + for (let y = selection.startRow + 1; y < selection.endRow; y++) { + html += this.getLineAsHTML(y, 0, this.xterm.cols) + } + html += this.getLineAsHTML(selection.endRow, 0, selection.endColumn) + } + html += '
' + return html + } + + private getHexColor (mode: number, color: number): string { + if (mode === Attributes.CM_RGB) { + let rgb = AttributeData.toColorRGB(color) + return rgb.map(x => x.toString(16).padStart(2, '0')).join('') + } + if (mode === Attributes.CM_P16 || mode === Attributes.CM_P256) { + return this.configService.store.terminal.colorScheme.colors[color] + } + return 'transparent' + } + + private getLineAsHTML (y: number, start: number, end: number): string { + let html = '
' + let lastStyle = null + const line = (this.xterm.buffer.getLine(y) as any)._line + let cell = new CellData() + for (let i = start; i < end; i++) { + line.loadCell(i, cell) + const fg = this.getHexColor(cell.getFgColorMode(), cell.getFgColor()) + const bg = this.getHexColor(cell.getBgColorMode(), cell.getBgColor()) + const style = `color: ${fg}; background: ${bg}; font-weight: ${cell.isBold() ? 'bold' : 'normal'}; font-style: ${cell.isItalic() ? 'italic' : 'normal'}; text-decoration: ${cell.isUnderline() ? 'underline' : 'none'}` + if (style !== lastStyle) { + if (lastStyle !== null) { + html += '' + } + html += `` + lastStyle = style + } + html += line.getString(i) || ' ' + } + html += '
' + return html + } } /** @hidden */ diff --git a/terminus-terminal/tsconfig.typings.json b/terminus-terminal/tsconfig.typings.json index c0d2273c..1ebf239f 100644 --- a/terminus-terminal/tsconfig.typings.json +++ b/terminus-terminal/tsconfig.typings.json @@ -8,7 +8,10 @@ "declarationDir": "./typings", "paths": { "terminus-*": ["../../terminus-*"], - "*": ["../../app/node_modules/*"] + "*": [ + "../../app/node_modules/*", + "../node_modules/xterm/src/*" + ] } } } diff --git a/terminus-terminal/webpack.config.js b/terminus-terminal/webpack.config.js index 0e4127a7..0e61959c 100644 --- a/terminus-terminal/webpack.config.js +++ b/terminus-terminal/webpack.config.js @@ -18,7 +18,7 @@ module.exports = { minimize: false, }, resolve: { - modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)), + modules: ['.', 'src', 'node_modules', '../app/node_modules', 'node_modules/xterm/src'].map(x => path.join(__dirname, x)), extensions: ['.ts', '.js'], }, module: { @@ -32,7 +32,10 @@ module.exports = { typeRoots: [path.resolve(__dirname, 'node_modules/@types')], paths: { "terminus-*": [path.resolve(__dirname, '../terminus-*')], - "*": [path.resolve(__dirname, '../app/node_modules/*')], + "*": [ + path.resolve(__dirname, '../app/node_modules/*'), + path.resolve(__dirname, './node_modules/xterm/src/*') + ], } }, },