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.
*/
copySelection(): Promise<void>;
copySelection(asHtml?: boolean): Promise<void>;
/**
* Current selection in the terminal.

View file

@ -2113,6 +2113,20 @@ export function registerTerminalActions() {
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) {

View file

@ -1126,10 +1126,21 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return this.xterm ? this.xterm.raw.hasSelection() : false;
}
async copySelection(): Promise<void> {
async copySelection(asHtml?: boolean): Promise<void> {
const xterm = await this._xtermReadyPromise;
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 {
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
}
},
{
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,
item: {
@ -145,7 +156,7 @@ export function setupTerminalMenus(): void {
title: localize('workbench.action.terminal.paste.short', "Paste")
},
group: ContextMenuGroup.Edit,
order: 2
order: 3
}
},
{
@ -237,6 +248,17 @@ export function setupTerminalMenus(): void {
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,
item: {
@ -245,7 +267,7 @@ export function setupTerminalMenus(): void {
title: localize('workbench.action.terminal.paste.short', "Paste")
},
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 { Unicode11Addon as Unicode11AddonType } from 'xterm-addon-unicode11';
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 { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
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 Unicode11Addon: typeof Unicode11AddonType;
let WebglAddon: typeof WebglAddonType;
let SerializeAddon: typeof SerializeAddonType;
/**
* 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 _unicode11Addon?: Unicode11AddonType;
private _webglAddon?: WebglAddonType;
private _serializeAddon?: SerializeAddonType;
private readonly _onDidRequestRunCommand = new Emitter<string>();
readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event;
@ -167,6 +170,15 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal {
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 {
// Update the theme when attaching as the terminal location could have changed
this._updateTheme();
@ -404,6 +416,13 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal {
return WebglAddon;
}
protected async _getSerializeAddonConstructor(): Promise<typeof SerializeAddonType> {
if (!SerializeAddon) {
SerializeAddon = (await import('xterm-addon-serialize')).SerializeAddon;
}
return SerializeAddon;
}
private _disposeOfWebglRenderer(): void {
try {
this._webglAddon?.dispose();

View file

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