support Copy as HTML in the terminal (#144784)

This commit is contained in:
Megan Rogge 2022-03-14 12:45:30 -04:00 committed by GitHub
parent 5a751e2bed
commit 04f6cdc4ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 5 deletions

View file

@ -649,7 +649,7 @@ export interface ITerminalInstance {
/** /**
* Copies the terminal selection to the clipboard. * Copies the terminal selection to the clipboard.
*/ */
copySelection(): Promise<void>; copySelection(asHtml?: boolean): Promise<void>;
/** /**
* Current selection in the terminal. * Current selection in the terminal.

View file

@ -2113,6 +2113,20 @@ export function registerTerminalActions() {
await accessor.get(ITerminalService).activeInstance?.copySelection(); await accessor.get(ITerminalService).activeInstance?.copySelection();
} }
}); });
registerAction2(class extends Action2 {
constructor() {
super({
id: TerminalCommandId.CopySelectionAsHtml,
title: { value: localize('workbench.action.terminal.copySelectionAsHtml', "Copy Selection as HTML"), original: 'Copy Selection as HTML' },
f1: true,
category,
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.textSelected)
});
}
async run(accessor: ServicesAccessor) {
await accessor.get(ITerminalService).activeInstance?.copySelection(true);
}
});
} }
if (BrowserFeatures.clipboard.readText) { if (BrowserFeatures.clipboard.readText) {

View file

@ -1126,10 +1126,21 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return this.xterm ? this.xterm.raw.hasSelection() : false; return this.xterm ? this.xterm.raw.hasSelection() : false;
} }
async copySelection(): Promise<void> { async copySelection(asHtml?: boolean): Promise<void> {
const xterm = await this._xtermReadyPromise; const xterm = await this._xtermReadyPromise;
if (this.hasSelection()) { if (this.hasSelection()) {
await this._clipboardService.writeText(xterm.raw.getSelection()); if (asHtml) {
const selectionAsHtml = await xterm.getSelectionAsHtml();
function listener(e: any) {
e.clipboardData.setData('text/html', selectionAsHtml);
e.preventDefault();
}
document.addEventListener('copy', listener);
document.execCommand('copy');
document.removeEventListener('copy', listener);
} else {
await this._clipboardService.writeText(xterm.raw.getSelection());
}
} else { } else {
this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy')); this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy'));
} }

View file

@ -137,6 +137,17 @@ export function setupTerminalMenus(): void {
order: 1 order: 1
} }
}, },
{
id: MenuId.TerminalInstanceContext,
item: {
command: {
id: TerminalCommandId.CopySelectionAsHtml,
title: localize('workbench.action.terminal.copySelectionAsHtml', "Copy as HTML")
},
group: ContextMenuGroup.Edit,
order: 2
}
},
{ {
id: MenuId.TerminalInstanceContext, id: MenuId.TerminalInstanceContext,
item: { item: {
@ -145,7 +156,7 @@ export function setupTerminalMenus(): void {
title: localize('workbench.action.terminal.paste.short', "Paste") title: localize('workbench.action.terminal.paste.short', "Paste")
}, },
group: ContextMenuGroup.Edit, group: ContextMenuGroup.Edit,
order: 2 order: 3
} }
}, },
{ {
@ -237,6 +248,17 @@ export function setupTerminalMenus(): void {
order: 1 order: 1
} }
}, },
{
id: MenuId.TerminalEditorInstanceContext,
item: {
command: {
id: TerminalCommandId.CopySelectionAsHtml,
title: localize('workbench.action.terminal.copySelectionAsHtml', "Copy as HTML")
},
group: ContextMenuGroup.Edit,
order: 2
}
},
{ {
id: MenuId.TerminalEditorInstanceContext, id: MenuId.TerminalEditorInstanceContext,
item: { item: {
@ -245,7 +267,7 @@ export function setupTerminalMenus(): void {
title: localize('workbench.action.terminal.paste.short', "Paste") title: localize('workbench.action.terminal.paste.short', "Paste")
}, },
group: ContextMenuGroup.Edit, group: ContextMenuGroup.Edit,
order: 2 order: 3
} }
}, },
{ {

View file

@ -7,6 +7,7 @@ import type { IBuffer, ITheme, RendererType, Terminal as RawXtermTerminal } from
import type { ISearchOptions, SearchAddon as SearchAddonType } from 'xterm-addon-search'; import type { ISearchOptions, SearchAddon as SearchAddonType } from 'xterm-addon-search';
import type { Unicode11Addon as Unicode11AddonType } from 'xterm-addon-unicode11'; import type { Unicode11Addon as Unicode11AddonType } from 'xterm-addon-unicode11';
import type { WebglAddon as WebglAddonType } from 'xterm-addon-webgl'; import type { WebglAddon as WebglAddonType } from 'xterm-addon-webgl';
import { SerializeAddon as SerializeAddonType } from 'xterm-addon-serialize';
import { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; import { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
@ -42,6 +43,7 @@ const NUMBER_OF_FRAMES_TO_MEASURE = 20;
let SearchAddon: typeof SearchAddonType; let SearchAddon: typeof SearchAddonType;
let Unicode11Addon: typeof Unicode11AddonType; let Unicode11Addon: typeof Unicode11AddonType;
let WebglAddon: typeof WebglAddonType; let WebglAddon: typeof WebglAddonType;
let SerializeAddon: typeof SerializeAddonType;
/** /**
* Wraps the xterm object with additional functionality. Interaction with the backing process is out * Wraps the xterm object with additional functionality. Interaction with the backing process is out
@ -64,6 +66,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal {
private _searchAddon?: SearchAddonType; private _searchAddon?: SearchAddonType;
private _unicode11Addon?: Unicode11AddonType; private _unicode11Addon?: Unicode11AddonType;
private _webglAddon?: WebglAddonType; private _webglAddon?: WebglAddonType;
private _serializeAddon?: SerializeAddonType;
private readonly _onDidRequestRunCommand = new Emitter<string>(); private readonly _onDidRequestRunCommand = new Emitter<string>();
readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event; readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event;
@ -167,6 +170,15 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal {
this.raw.loadAddon(this._decorationAddon); this.raw.loadAddon(this._decorationAddon);
} }
async getSelectionAsHtml(): Promise<string> {
if (!this._serializeAddon) {
const Addon = await this._getSerializeAddonConstructor();
this._serializeAddon = new Addon();
this.raw.loadAddon(this._serializeAddon);
}
return this._serializeAddon.serializeAsHTML({ onlySelection: true });
}
attachToElement(container: HTMLElement): HTMLElement { attachToElement(container: HTMLElement): HTMLElement {
// Update the theme when attaching as the terminal location could have changed // Update the theme when attaching as the terminal location could have changed
this._updateTheme(); this._updateTheme();
@ -404,6 +416,13 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal {
return WebglAddon; return WebglAddon;
} }
protected async _getSerializeAddonConstructor(): Promise<typeof SerializeAddonType> {
if (!SerializeAddon) {
SerializeAddon = (await import('xterm-addon-serialize')).SerializeAddon;
}
return SerializeAddon;
}
private _disposeOfWebglRenderer(): void { private _disposeOfWebglRenderer(): void {
try { try {
this._webglAddon?.dispose(); this._webglAddon?.dispose();

View file

@ -475,6 +475,7 @@ export const enum TerminalCommandId {
RunRecentCommand = 'workbench.action.terminal.runRecentCommand', RunRecentCommand = 'workbench.action.terminal.runRecentCommand',
GoToRecentDirectory = 'workbench.action.terminal.goToRecentDirectory', GoToRecentDirectory = 'workbench.action.terminal.goToRecentDirectory',
CopySelection = 'workbench.action.terminal.copySelection', CopySelection = 'workbench.action.terminal.copySelection',
CopySelectionAsHtml = 'workbench.action.terminal.copySelectionAsHtml',
SelectAll = 'workbench.action.terminal.selectAll', SelectAll = 'workbench.action.terminal.selectAll',
DeleteWordLeft = 'workbench.action.terminal.deleteWordLeft', DeleteWordLeft = 'workbench.action.terminal.deleteWordLeft',
DeleteWordRight = 'workbench.action.terminal.deleteWordRight', DeleteWordRight = 'workbench.action.terminal.deleteWordRight',
@ -566,6 +567,7 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [
TerminalCommandId.ClearSelection, TerminalCommandId.ClearSelection,
TerminalCommandId.Clear, TerminalCommandId.Clear,
TerminalCommandId.CopySelection, TerminalCommandId.CopySelection,
TerminalCommandId.CopySelectionAsHtml,
TerminalCommandId.DeleteToLineStart, TerminalCommandId.DeleteToLineStart,
TerminalCommandId.DeleteWordLeft, TerminalCommandId.DeleteWordLeft,
TerminalCommandId.DeleteWordRight, TerminalCommandId.DeleteWordRight,