UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

268 lines (267 loc) 9.32 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { DataTransfers } from '@sussudio/base/browser/dnd.mjs'; import { coalesce } from '@sussudio/base/common/arrays.mjs'; import { DeferredPromise } from '@sussudio/base/common/async.mjs'; import { VSBuffer } from '@sussudio/base/common/buffer.mjs'; import { ResourceMap } from '@sussudio/base/common/map.mjs'; import { parse } from '@sussudio/base/common/marshalling.mjs'; import { Schemas } from '@sussudio/base/common/network.mjs'; import { isWeb } from '@sussudio/base/common/platform.mjs'; import Severity from '@sussudio/base/common/severity.mjs'; import { withNullAsUndefined } from '@sussudio/base/common/types.mjs'; import { URI } from '@sussudio/base/common/uri.mjs'; import { localize } from 'vscode-nls.mjs'; import { IDialogService } from '../../dialogs/common/dialogs.mjs'; import { HTMLFileSystemProvider } from '../../files/browser/htmlFileSystemProvider.mjs'; import { WebFileSystemAccess } from '../../files/browser/webFileSystemAccess.mjs'; import { ByteSize, IFileService } from '../../files/common/files.mjs'; import { IInstantiationService } from '../../instantiation/common/instantiation.mjs'; import { extractSelection } from '../../opener/common/opener.mjs'; import { Registry } from '../../registry/common/platform.mjs'; //#region Editor / Resources DND export const CodeDataTransfers = { EDITORS: 'CodeEditors', FILES: 'CodeFiles', }; export function extractEditorsDropData(e) { const editors = []; if (e.dataTransfer && e.dataTransfer.types.length > 0) { // Data Transfer: Code Editors const rawEditorsData = e.dataTransfer.getData(CodeDataTransfers.EDITORS); if (rawEditorsData) { try { editors.push(...parse(rawEditorsData)); } catch (error) { // Invalid transfer } } // Data Transfer: Resources else { try { const rawResourcesData = e.dataTransfer.getData(DataTransfers.RESOURCES); editors.push(...createDraggedEditorInputFromRawResourcesData(rawResourcesData)); } catch (error) { // Invalid transfer } } // Check for native file transfer if (e.dataTransfer?.files) { for (let i = 0; i < e.dataTransfer.files.length; i++) { const file = e.dataTransfer.files[i]; if (file && file.path /* Electron only */) { try { editors.push({ resource: URI.file(file.path), isExternal: true, allowWorkspaceOpen: true }); } catch (error) { // Invalid URI } } } } // Check for CodeFiles transfer const rawCodeFiles = e.dataTransfer.getData(CodeDataTransfers.FILES); if (rawCodeFiles) { try { const codeFiles = JSON.parse(rawCodeFiles); for (const codeFile of codeFiles) { editors.push({ resource: URI.file(codeFile), isExternal: true, allowWorkspaceOpen: true }); } } catch (error) { // Invalid transfer } } // Workbench contributions const contributions = Registry.as(Extensions.DragAndDropContribution).getAll(); for (const contribution of contributions) { const data = e.dataTransfer.getData(contribution.dataFormatKey); if (data) { try { editors.push(...contribution.getEditorInputs(data)); } catch (error) { // Invalid transfer } } } } // Prevent duplicates: it is possible that we end up with the same // dragged editor multiple times because multiple data transfers // are being used (https://github.com/microsoft/vscode/issues/128925) const coalescedEditors = []; const seen = new ResourceMap(); for (const editor of editors) { if (!editor.resource) { coalescedEditors.push(editor); } else if (!seen.has(editor.resource)) { coalescedEditors.push(editor); seen.set(editor.resource, true); } } return coalescedEditors; } export async function extractEditorsAndFilesDropData(accessor, e) { const editors = extractEditorsDropData(e); // Web: Check for file transfer if (e.dataTransfer && isWeb && containsDragType(e, DataTransfers.FILES)) { const files = e.dataTransfer.items; if (files) { const instantiationService = accessor.get(IInstantiationService); const filesData = await instantiationService.invokeFunction((accessor) => extractFilesDropData(accessor, e)); for (const fileData of filesData) { editors.push({ resource: fileData.resource, contents: fileData.contents?.toString(), isExternal: true, allowWorkspaceOpen: fileData.isDirectory, }); } } } return editors; } export function createDraggedEditorInputFromRawResourcesData(rawResourcesData) { const editors = []; if (rawResourcesData) { const resourcesRaw = JSON.parse(rawResourcesData); for (const resourceRaw of resourcesRaw) { if (resourceRaw.indexOf(':') > 0) { // mitigate https://github.com/microsoft/vscode/issues/124946 const { selection, uri } = extractSelection(URI.parse(resourceRaw)); editors.push({ resource: uri, options: { selection } }); } } } return editors; } async function extractFilesDropData(accessor, event) { // Try to extract via `FileSystemHandle` if (WebFileSystemAccess.supported(window)) { const items = event.dataTransfer?.items; if (items) { return extractFileTransferData(accessor, items); } } // Try to extract via `FileList` const files = event.dataTransfer?.files; if (!files) { return []; } return extractFileListData(accessor, files); } async function extractFileTransferData(accessor, items) { const fileSystemProvider = accessor.get(IFileService).getProvider(Schemas.file); if (!(fileSystemProvider instanceof HTMLFileSystemProvider)) { return []; // only supported when running in web } const results = []; for (let i = 0; i < items.length; i++) { const file = items[i]; if (file) { const result = new DeferredPromise(); results.push(result); (async () => { try { const handle = await file.getAsFileSystemHandle(); if (!handle) { result.complete(undefined); return; } if (WebFileSystemAccess.isFileSystemFileHandle(handle)) { result.complete({ resource: await fileSystemProvider.registerFileHandle(handle), isDirectory: false, }); } else if (WebFileSystemAccess.isFileSystemDirectoryHandle(handle)) { result.complete({ resource: await fileSystemProvider.registerDirectoryHandle(handle), isDirectory: true, }); } else { result.complete(undefined); } } catch (error) { result.complete(undefined); } })(); } } return coalesce(await Promise.all(results.map((result) => result.p))); } export async function extractFileListData(accessor, files) { const dialogService = accessor.get(IDialogService); const results = []; for (let i = 0; i < files.length; i++) { const file = files.item(i); if (file) { // Skip for very large files because this operation is unbuffered if (file.size > 100 * ByteSize.MB) { dialogService.show( Severity.Warning, localize( 'fileTooLarge', 'File is too large to open as untitled editor. Please upload it first into the file explorer and then try again.', ), ); continue; } const result = new DeferredPromise(); results.push(result); const reader = new FileReader(); reader.onerror = () => result.complete(undefined); reader.onabort = () => result.complete(undefined); reader.onload = async (event) => { const name = file.name; const loadResult = withNullAsUndefined(event.target?.result); if (typeof name !== 'string' || typeof loadResult === 'undefined') { result.complete(undefined); return; } result.complete({ resource: URI.from({ scheme: Schemas.untitled, path: name }), contents: typeof loadResult === 'string' ? VSBuffer.fromString(loadResult) : VSBuffer.wrap(new Uint8Array(loadResult)), }); }; // Start reading reader.readAsArrayBuffer(file); } } return coalesce(await Promise.all(results.map((result) => result.p))); } //#endregion export function containsDragType(event, ...dragTypesToFind) { if (!event.dataTransfer) { return false; } const dragTypes = event.dataTransfer.types; const lowercaseDragTypes = []; for (let i = 0; i < dragTypes.length; i++) { lowercaseDragTypes.push(dragTypes[i].toLowerCase()); // somehow the types are lowercase } for (const dragType of dragTypesToFind) { if (lowercaseDragTypes.indexOf(dragType.toLowerCase()) >= 0) { return true; } } return false; } class DragAndDropContributionRegistry { _contributions = new Map(); register(contribution) { if (this._contributions.has(contribution.dataFormatKey)) { throw new Error(`A drag and drop contributiont with key '${contribution.dataFormatKey}' was already registered.`); } this._contributions.set(contribution.dataFormatKey, contribution); } getAll() { return this._contributions.values(); } } export const Extensions = { DragAndDropContribution: 'workbench.contributions.dragAndDrop', }; Registry.add(Extensions.DragAndDropContribution, new DragAndDropContributionRegistry()); //#endregion