diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 06322c3e0b4..e44e219015c 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -537,6 +537,27 @@ export function getCharContainingOffset(str: string, offset: number): [number, n return _getCharContainingOffset(str, offset); } +/** + * Convert a Unicode string to a string in which each 16-bit unit occupies only one byte + * + * From https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa + */ +function toBinary(str: string): string { + const codeUnits = new Uint16Array(str.length); + for (let i = 0; i < codeUnits.length; i++) { + codeUnits[i] = str.charCodeAt(i); + } + return String.fromCharCode(...new Uint8Array(codeUnits.buffer)); +} + +/** + * Version of the global `btoa` function that handles multi-byte characters instead + * of throwing an exception. + */ +export function multibyteAwareBtoa(str: string): string { + return btoa(toBinary(str)); +} + /** * A manual encoding of `str` to UTF8. * Use only in environments which do not offer native conversion methods! diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 04aab60a1ad..ff7778c60be 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -11,6 +11,7 @@ import { Disposable, DisposableStore, IDisposable, IReference } from 'vs/base/co import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; import { isEqual, isEqualOrParent, toLocalResource } from 'vs/base/common/resources'; +import { multibyteAwareBtoa } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; @@ -347,7 +348,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod private static toWorkingCopyResource(viewType: string, resource: URI) { const authority = viewType.replace(/[^a-z0-9\-_]/gi, '-'); - const path = '/' + btoa(resource.with({ query: null, fragment: null }).toString(true)); + const path = '/' + multibyteAwareBtoa(resource.with({ query: null, fragment: null }).toString(true)); return URI.from({ scheme: Schemas.vscodeCustomEditor, authority: authority,