浏览代码

copy as html if possible (fixes #503)

Eugene Pankov 6 年之前
父节点
当前提交
37a7d32bc8

+ 65 - 8
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 = `<div style="font-family: '${this.configService.store.terminal.font}', monospace; white-space: pre">`
+        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 += '</div>'
+        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 = '<div>'
+        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 += '</span>'
+                }
+                html += `<span style="${style}">`
+                lastStyle = style
+            }
+            html += line.getString(i) || ' '
+        }
+        html += '</span></div>'
+        return html
+    }
 }
 
 /** @hidden */

+ 4 - 1
terminus-terminal/tsconfig.typings.json

@@ -8,7 +8,10 @@
     "declarationDir": "./typings",
     "paths": {
       "terminus-*": ["../../terminus-*"],
-      "*": ["../../app/node_modules/*"]
+      "*": [
+        "../../app/node_modules/*",
+        "../node_modules/xterm/src/*"
+      ]
     }
   }
 }

+ 5 - 2
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/*')
+              ],
             }
           },
         },