diff --git a/src/vs/platform/clipboard/browser/clipboardService.ts b/src/vs/platform/clipboard/browser/clipboardService.ts index f9a61f6ce3c..65e38be21ec 100644 --- a/src/vs/platform/clipboard/browser/clipboardService.ts +++ b/src/vs/platform/clipboard/browser/clipboardService.ts @@ -189,6 +189,17 @@ export class BrowserClipboardService extends Disposable implements IClipboardSer return this.resources; } + // --- Start Positron --- + async writeImage(data: string): Promise { + const blob = new Blob([data], { type: 'image/png' }); + navigator.clipboard.write([ + new ClipboardItem({ + [blob.type]: blob + } + )]); + } + // --- End Positron --- + private async computeResourcesStateHash(): Promise { if (this.resources.length === 0) { return undefined; // no resources, no hash needed diff --git a/src/vs/platform/clipboard/common/clipboardService.ts b/src/vs/platform/clipboard/common/clipboardService.ts index c4aea9f7132..4fdd6c3f3eb 100644 --- a/src/vs/platform/clipboard/common/clipboardService.ts +++ b/src/vs/platform/clipboard/common/clipboardService.ts @@ -37,6 +37,10 @@ export interface IClipboardService { */ writeResources(resources: URI[]): Promise; + // --- Start Positron --- + writeImage(data: string): Promise; + // --- End Positron --- + /** * Reads resources from the system clipboard. */ diff --git a/src/vs/platform/clipboard/test/common/testClipboardService.ts b/src/vs/platform/clipboard/test/common/testClipboardService.ts index 0ab758a9990..c20a75bb9aa 100644 --- a/src/vs/platform/clipboard/test/common/testClipboardService.ts +++ b/src/vs/platform/clipboard/test/common/testClipboardService.ts @@ -36,6 +36,12 @@ export class TestClipboardService implements IClipboardService { this.resources = resources; } + // --- Start Positron --- + async writeImage(data: string): Promise { + // no-op + } + // --- End Positron --- + async readResources(): Promise { return this.resources ?? []; } diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index 94ae20b32f8..c61f0cca591 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -152,6 +152,10 @@ export interface ICommonNativeHostService { readClipboardBuffer(format: string): Promise; hasClipboard(format: string, type?: 'selection' | 'clipboard'): Promise; + // --- Start Positron --- + writeClipboardImage(dataUri: string): Promise; + // --- End Positron --- + // macOS Touchbar newWindowTab(): Promise; showPreviousWindowTab(): Promise; diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index a1d28301611..c8dfeeaaf30 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -44,6 +44,11 @@ import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electr import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; import { CancellationError } from 'vs/base/common/errors'; +// --- Start Positron --- +// eslint-disable-next-line no-duplicate-imports +import { nativeImage } from 'electron'; +// --- End Positron --- + export interface INativeHostMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } export const INativeHostMainService = createDecorator('nativeHostMainService'); @@ -647,6 +652,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain return clipboard.has(format, type); } + // --- Start Positron --- + async writeClipboardImage(windowId: number | undefined, dataUri: string): Promise { + return clipboard.writeImage(nativeImage.createFromDataURL(dataUri)); + } + // --- End Positron --- + //#endregion diff --git a/src/vs/workbench/contrib/positronPlots/browser/components/actionBars.tsx b/src/vs/workbench/contrib/positronPlots/browser/components/actionBars.tsx index f4ba601b78c..ceb46ef9735 100644 --- a/src/vs/workbench/contrib/positronPlots/browser/components/actionBars.tsx +++ b/src/vs/workbench/contrib/positronPlots/browser/components/actionBars.tsx @@ -72,6 +72,12 @@ export const ActionBars = (props: PropsWithChildren) => { && (selectedPlot instanceof PlotClientInstance || selectedPlot instanceof StaticPlotClient); + const enableCopyPlot = hasPlots && + (positronPlotsContext.positronPlotInstances[positronPlotsContext.selectedInstanceIndex] + instanceof StaticPlotClient + || positronPlotsContext.positronPlotInstances[positronPlotsContext.selectedInstanceIndex] + instanceof PlotClientInstance); + useEffect(() => { // Empty for now. }); @@ -104,6 +110,17 @@ export const ActionBars = (props: PropsWithChildren) => { positronPlotsContext.positronPlotsService.savePlot(); }; + const copyPlotHandler = () => { + positronPlotsContext.positronPlotsService.copyPlotToClipboard() + .then(() => { + positronPlotsContext.notificationService.info(localize('positronPlotsServiceCopyToClipboard', 'Plot copied to clipboard')); + }) + .catch((error) => { + positronPlotsContext.notificationService.error(localize('positronPlotsServiceCopyToClipboardError', 'Failed to copy plot to clipboard: {0}', error.message)); + }); + + }; + // Render. return ( @@ -118,6 +135,8 @@ export const ActionBars = (props: PropsWithChildren) => { {(enableSizingPolicy || enableSavingPlots || enableZoomPlot) && } {enableSavingPlots && } + {enableCopyPlot && } {enableZoomPlot && } {enableSizingPolicy && { + const plot = this._plots.find(plot => plot.id === this.selectedPlotId); + if (plot instanceof StaticPlotClient) { + try { + await this._clipboardService.writeImage(plot.uri); + } catch (error) { + throw new Error(error.message); + } + } else if (plot instanceof PlotClientInstance) { + if (plot.lastRender?.uri) { + try { + await this._clipboardService.writeImage(plot.lastRender.uri); + } catch (error) { + throw new Error(error.message); + } + } + } + } + /** * Generates a storage key for a plot. * diff --git a/src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts b/src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts index f4808d9f3a1..2dd3db0ffcb 100644 --- a/src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts +++ b/src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts @@ -48,6 +48,12 @@ export class NativeClipboardService implements IClipboardService { } } + // --- Start Positron --- + async writeImage(data: string): Promise { + return this.nativeHostService.writeClipboardImage(data); + } + // --- End Positron --- + async readResources(): Promise { return this.bufferToResources(await this.nativeHostService.readClipboardBuffer(NativeClipboardService.FILE_FORMAT)); } diff --git a/src/vs/workbench/services/positronPlots/common/positronPlots.ts b/src/vs/workbench/services/positronPlots/common/positronPlots.ts index 451c4dce7c7..3e070d31432 100644 --- a/src/vs/workbench/services/positronPlots/common/positronPlots.ts +++ b/src/vs/workbench/services/positronPlots/common/positronPlots.ts @@ -150,6 +150,13 @@ export interface IPositronPlotsService { */ selectHistoryPolicy(policy: HistoryPolicy): void; + /** + * Copies the selected plot to the clipboard. + * + * @throws An error if the plot cannot be copied. + */ + copyPlotToClipboard(): Promise; + /** * Saves the plot. */ diff --git a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts index d323ca935ae..2b9c9ff840d 100644 --- a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts @@ -153,6 +153,10 @@ export class TestNativeHostService implements INativeHostService { async hasClipboard(format: string, type?: 'selection' | 'clipboard' | undefined): Promise { return false; } async windowsGetStringRegKey(hive: 'HKEY_CURRENT_USER' | 'HKEY_LOCAL_MACHINE' | 'HKEY_CLASSES_ROOT' | 'HKEY_USERS' | 'HKEY_CURRENT_CONFIG', path: string, name: string): Promise { return undefined; } async profileRenderer(): Promise { throw new Error(); } + + // --- Start Positron --- + async writeClipboardImage(dataUri: string): Promise { } + // --- End Positron --- } export class TestExtensionTipsService extends AbstractNativeExtensionTipsService {