From a97e1873a4240ac00a3331fa1751ac7959554cec Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 7 Feb 2025 11:15:42 +0100 Subject: [PATCH 01/24] changes from https://github.com/microsoft/vscode/pull/177434 with latest `main` --- .../common/extensionsApiProposals.ts | 3 ++ .../api/browser/mainThreadEditors.ts | 48 +++++++++++++++++++ .../workbench/api/common/extHost.api.impl.ts | 1 + .../workbench/api/common/extHost.protocol.ts | 2 + .../workbench/api/common/extHostTextEditor.ts | 6 +++ src/vs/workbench/api/common/extHostTypes.ts | 9 ++++ .../test/browser/extHostTextEditor.test.ts | 4 +- .../browser/parts/editor/editorStatus.ts | 2 +- .../vscode.proposed.editorEncoding.d.ts | 35 ++++++++++++++ 9 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.editorEncoding.d.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 93d6088e74232..2b971217670a1 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -168,6 +168,9 @@ const _allApiProposals = { editSessionIdentityProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts', }, + editorEncoding: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorEncoding.d.ts', + }, editorHoverVerbosityLevel: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorHoverVerbosityLevel.d.ts', }, diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index a61ba3c780107..649078ffcf74a 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -36,6 +36,8 @@ import { LineRangeMapping } from '../../../editor/common/diff/rangeMapping.js'; import { equals } from '../../../base/common/arrays.js'; import { Event } from '../../../base/common/event.js'; import { DiffAlgorithmName } from '../../../editor/common/services/editorWorker.js'; +import { toEditorWithEncodingSupport } from '../../browser/parts/editor/editorStatus.js'; +import { EncodingMode } from '../../services/textfile/common/textfiles.js'; export interface IMainThreadEditorLocator { getEditor(id: string): MainThreadTextEditor | undefined; @@ -405,6 +407,52 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { quickDiffModelRef.dispose(); } } + + $tryGetEncoding(id: string): Promise { + const editor = this._editorLocator.getEditor(id); + + if (!editor) { + return Promise.reject(new Error('No such TextEditor')); + } + + const editorPanes = this._editorService.visibleEditorPanes; + for (const editorPane of editorPanes) { + if (editor.matches(editorPane)) { + const encodingSupport = toEditorWithEncodingSupport(editorPane.input); + if (!encodingSupport) { + return Promise.reject(new Error('No file active at this time')); + } + + const encoding = encodingSupport.getEncoding(); + + return Promise.resolve(encoding); + } + } + + return Promise.reject(new Error('No text editor active at this time')); + } + + $trySetEncoding(id: string, encoding: string, mode: number): Promise { + const editor = this._editorLocator.getEditor(id); + + if (!editor) { + return Promise.reject(new Error('No such TextEditor')); + } + + const editorPanes = this._editorService.visibleEditorPanes; + for (const editorPane of editorPanes) { + if (editor.matches(editorPane)) { + const encodingSupport = toEditorWithEncodingSupport(editorPane.input); + if (!encodingSupport) { + return Promise.reject(new Error('No file active at this time')); + } + + return encodingSupport.setEncoding(encoding, mode === 0 ? EncodingMode.Encode : EncodingMode.Decode); + } + } + + return Promise.reject(new Error('No text editor active at this time')); + } } // --- commands diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 64cbb2e677877..2dbabf7336945 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1793,6 +1793,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TextSearchContext2: TextSearchContext2, TextSearchMatch2: TextSearchMatch2, TextSearchCompleteMessageTypeNew: TextSearchCompleteMessageType, + EncodingMode: extHostTypes.EncodingMode, }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2004fabcdbb17..5186f580cb283 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -289,6 +289,8 @@ export interface MainThreadTextEditorsShape extends IDisposable { $tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): Promise; $tryInsertSnippet(id: string, modelVersionId: number, template: string, selections: readonly IRange[], opts: IUndoStopOptions): Promise; $getDiffInformation(id: string): Promise; + $tryGetEncoding(id: string): Promise; + $trySetEncoding(id: string, encoding: string, mode: number): Promise; } export interface MainThreadTreeViewsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index 86a322348a8d2..c1bafe68731f0 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -574,6 +574,12 @@ export class ExtHostTextEditor { hide() { _proxy.$tryHideEditor(id); }, + getEncoding(): Promise { + return _proxy.$tryGetEncoding(id); + }, + setEncoding(encoding: string, mode: vscode.EncodingMode): Promise { + return _proxy.$trySetEncoding(id, encoding, mode); + }, [Symbol.for('debug.description')]() { return `TextEditor(${this.document.uri.toString()})`; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index e3fb40a10ca09..982194253cdb0 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4954,3 +4954,12 @@ export enum InlineEditTriggerKind { } //#endregion + +//#region Encoding + +export enum EncodingMode { + Encode = 0, + Decode = 1 +} + +//#endregion diff --git a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts index b76b9048aaa79..39a0b50d1be66 100644 --- a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts @@ -89,7 +89,9 @@ suite('ExtHostTextEditorOptions', () => { $trySetSelections: undefined!, $tryApplyEdits: undefined!, $tryInsertSnippet: undefined!, - $getDiffInformation: undefined! + $getDiffInformation: undefined!, + $tryGetEncoding: undefined!, + $trySetEncoding: undefined! }; opts = new ExtHostTextEditorOptions(mockProxy, '1', { tabSize: 4, diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index a1b9bd5215c2c..d06e98234558e 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -79,7 +79,7 @@ class SideBySideEditorLanguageSupport implements ILanguageSupport { } } -function toEditorWithEncodingSupport(input: EditorInput): IEncodingSupport | null { +export function toEditorWithEncodingSupport(input: EditorInput): IEncodingSupport | null { // Untitled Text Editor if (input instanceof UntitledTextEditorInput) { diff --git a/src/vscode-dts/vscode.proposed.editorEncoding.d.ts b/src/vscode-dts/vscode.proposed.editorEncoding.d.ts new file mode 100644 index 0000000000000..cb7835ef02fb3 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.editorEncoding.d.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + // https://github.com/microsoft/vscode/issues/824 + + export enum EncodingMode { + + /** + * Instructs the encoding support to encode the object with the provided encoding + */ + Encode = 0, + + /** + * Instructs the encoding support to decode the object with the provided encoding + */ + Decode = 1 + } + + export interface TextEditor { + /** + * Get the text editor encoding. + */ + getEncoding(): Thenable; + + /** + * Set the text editor encoding. + * @param encoding + * @param mode + */ + setEncoding(encoding: string, mode: EncodingMode): Thenable; + } +} From eefb7c0abaa22fd5f434f8b772cd21b94f0226ce Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 08:47:13 +0100 Subject: [PATCH 02/24] towards encoding in document --- .../common/extensionsApiProposals.ts | 6 ++-- .../browser/mainThreadDocumentsAndEditors.ts | 3 +- .../workbench/api/common/extHost.api.impl.ts | 1 - .../workbench/api/common/extHost.protocol.ts | 1 + .../api/common/extHostDocumentData.ts | 14 ++++++++ .../api/common/extHostDocumentsAndEditors.ts | 1 + .../api/common/extHostInteractive.ts | 1 + .../api/common/extHostNotebookDocument.ts | 3 +- .../workbench/api/common/extHostTextEditor.ts | 6 ---- src/vs/workbench/api/common/extHostTypes.ts | 9 ----- .../test/browser/extHostApiCommands.test.ts | 1 + .../api/test/browser/extHostBulkEdits.test.ts | 1 + .../extHostDocumentContentProvider.test.ts | 1 + .../test/browser/extHostDocumentData.test.ts | 16 ++++----- .../extHostDocumentSaveParticipant.test.ts | 1 + .../extHostDocumentsAndEditors.test.ts | 3 +- .../browser/extHostLanguageFeatures.test.ts | 1 + .../api/test/browser/extHostNotebook.test.ts | 3 +- .../test/browser/extHostTextEditor.test.ts | 2 +- .../textfile/browser/textFileService.ts | 16 +++++++-- .../services/textfile/common/textfiles.ts | 2 ++ .../vscode.proposed.editorEncoding.d.ts | 35 ------------------- .../vscode.proposed.textDocumentEncoding.d.ts | 33 +++++++++++++++++ 23 files changed, 90 insertions(+), 70 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.editorEncoding.d.ts create mode 100644 src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index e35aaaf469cb1..f6c9174d7328d 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -165,9 +165,6 @@ const _allApiProposals = { editSessionIdentityProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts', }, - editorEncoding: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorEncoding.d.ts', - }, editorHoverVerbosityLevel: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorHoverVerbosityLevel.d.ts', }, @@ -364,6 +361,9 @@ const _allApiProposals = { testRelatedCode: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testRelatedCode.d.ts', }, + textDocumentEncoding: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts', + }, textEditorDiffInformation: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textEditorDiffInformation.d.ts', }, diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 66ae21fb2f8ee..5aab6f573ed0b 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -386,7 +386,8 @@ export class MainThreadDocumentsAndEditors { lines: model.getLinesContent(), EOL: model.getEOL(), languageId: model.getLanguageId(), - isDirty: this._textFileService.isDirty(model.uri) + isDirty: this._textFileService.isDirty(model.uri), + encoding: this._textFileService.getEncoding(model.uri), }; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f941edec2c005..af61950f93446 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1788,7 +1788,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TextSearchContext2: TextSearchContext2, TextSearchMatch2: TextSearchMatch2, TextSearchCompleteMessageTypeNew: TextSearchCompleteMessageType, - EncodingMode: extHostTypes.EncodingMode, }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 36d6b3f24a63a..76514d2807b35 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1793,6 +1793,7 @@ export interface IModelAddedData { EOL: string; languageId: string; isDirty: boolean; + encoding: string; } export interface ExtHostDocumentsShape { $acceptModelLanguageChanged(strURL: UriComponents, newLanguageId: string): void; diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index c69c38d80cedb..ce9c975fb88ba 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -37,6 +37,7 @@ export class ExtHostDocumentData extends MirrorTextModel { uri: URI, lines: string[], eol: string, versionId: number, private _languageId: string, private _isDirty: boolean, + private _encoding: string ) { super(uri, lines, eol, versionId); } @@ -66,6 +67,7 @@ export class ExtHostDocumentData extends MirrorTextModel { get version() { return that._versionId; }, get isClosed() { return that._isDisposed; }, get isDirty() { return that._isDirty; }, + get encoding() { return that._encoding; }, save() { return that._save(); }, getText(range?) { return range ? that._getTextInRange(range) : that.getText(); }, get eol() { return that._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; }, @@ -76,6 +78,8 @@ export class ExtHostDocumentData extends MirrorTextModel { validateRange(ran) { return that._validateRange(ran); }, validatePosition(pos) { return that._validatePosition(pos); }, getWordRangeAtPosition(pos, regexp?) { return that._getWordRangeAtPosition(pos, regexp); }, + encode(encoding) { return that._encode(encoding); }, + decode(encoding) { return that._decode(encoding); }, [Symbol.for('debug.description')]() { return `TextDocument(${that._uri.toString()})`; } @@ -241,6 +245,16 @@ export class ExtHostDocumentData extends MirrorTextModel { } return undefined; } + + // --- encodings + + private _decode(encoding: string): Thenable { + throw new Error('Method not implemented.'); + } + + private _encode(encoding: string): Thenable { + throw new Error('Method not implemented.'); + } } export class ExtHostDocumentLine implements vscode.TextLine { diff --git a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts index 5fe4b192750b1..81dd46d341bae 100644 --- a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts @@ -97,6 +97,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha data.versionId, data.languageId, data.isDirty, + data.encoding )); this._documents.set(resource, ref); addedDocuments.push(ref.value); diff --git a/src/vs/workbench/api/common/extHostInteractive.ts b/src/vs/workbench/api/common/extHostInteractive.ts index 0debcfa5b6cc4..0c76d8e682ce3 100644 --- a/src/vs/workbench/api/common/extHostInteractive.ts +++ b/src/vs/workbench/api/common/extHostInteractive.ts @@ -52,6 +52,7 @@ export class ExtHostInteractive implements ExtHostInteractiveShape { uri: uri, isDirty: false, versionId: 1, + encoding: 'utf8' // todo }] }); } diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index 205a899854cb6..1ed72abbbcbe3 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -40,7 +40,8 @@ export class ExtHostCell { languageId: cell.language, uri: cell.uri, isDirty: false, - versionId: 1 + versionId: 1, + encoding: 'utf8' // todo }; } diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index c1bafe68731f0..86a322348a8d2 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -574,12 +574,6 @@ export class ExtHostTextEditor { hide() { _proxy.$tryHideEditor(id); }, - getEncoding(): Promise { - return _proxy.$tryGetEncoding(id); - }, - setEncoding(encoding: string, mode: vscode.EncodingMode): Promise { - return _proxy.$trySetEncoding(id, encoding, mode); - }, [Symbol.for('debug.description')]() { return `TextEditor(${this.document.uri.toString()})`; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 3c260cf7cb616..826548dfdef3c 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4958,12 +4958,3 @@ export enum InlineEditTriggerKind { } //#endregion - -//#region Encoding - -export enum EncodingMode { - Encode = 0, - Decode = 1 -} - -//#endregion diff --git a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts index 2597ac759e504..2e10b368a6437 100644 --- a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts @@ -167,6 +167,7 @@ suite('ExtHostLanguageFeatureCommands', function () { uri: model.uri, lines: model.getValue().split(model.getEOL()), EOL: model.getEOL(), + encoding: 'utf8' }] }); const extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); diff --git a/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts b/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts index 582a5e7c95480..08fff8d05229f 100644 --- a/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts +++ b/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts @@ -40,6 +40,7 @@ suite('ExtHostBulkEdits.applyWorkspaceEdit', () => { versionId: 1337, lines: ['foo'], EOL: '\n', + encoding: 'utf8' }] }); bulkEdits = new ExtHostBulkEdits(rpcProtocol, documentsAndEditors); diff --git a/src/vs/workbench/api/test/browser/extHostDocumentContentProvider.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentContentProvider.test.ts index 5a128d5f781f1..4f83fc7d99973 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentContentProvider.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentContentProvider.test.ts @@ -54,6 +54,7 @@ suite('ExtHostDocumentContentProvider', () => { versionId: 1, lines: ['foo'], EOL: '\n', + encoding: 'utf8' }] }); documentContentProvider = new ExtHostDocumentContentProvider(ehContext, documentsAndEditors, new NullLogService()); diff --git a/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts index 926b8fdc6d29a..5755dc3e1e817 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts @@ -37,7 +37,7 @@ suite('ExtHostDocumentData', () => { 'and this is line number two', //27 'it is followed by #3', //20 'and finished with the fourth.', //29 - ], '\n', 1, 'text', false); + ], '\n', 1, 'text', false, 'utf8'); }); ensureNoDisposablesAreLeakedInTestSuite(); @@ -59,7 +59,7 @@ suite('ExtHostDocumentData', () => { saved = uri; return Promise.resolve(true); } - }, URI.parse('foo:bar'), [], '\n', 1, 'text', true); + }, URI.parse('foo:bar'), [], '\n', 1, 'text', true, 'utf8'); return data.document.save().then(() => { assert.strictEqual(saved.toString(), 'foo:bar'); @@ -256,7 +256,7 @@ suite('ExtHostDocumentData', () => { test('getWordRangeAtPosition', () => { data = new ExtHostDocumentData(undefined!, URI.file(''), [ 'aaaa bbbb+cccc abc' - ], '\n', 1, 'text', false); + ], '\n', 1, 'text', false, 'utf8'); let range = data.document.getWordRangeAtPosition(new Position(0, 2))!; assert.strictEqual(range.start.line, 0); @@ -290,7 +290,7 @@ suite('ExtHostDocumentData', () => { 'function() {', ' "far boo"', '}' - ], '\n', 1, 'text', false); + ], '\n', 1, 'text', false, 'utf8'); let range = data.document.getWordRangeAtPosition(new Position(0, 0), /\/\*.+\*\//); assert.strictEqual(range, undefined); @@ -318,7 +318,7 @@ suite('ExtHostDocumentData', () => { data = new ExtHostDocumentData(undefined!, URI.file(''), [ perfData._$_$_expensive - ], '\n', 1, 'text', false); + ], '\n', 1, 'text', false, 'utf8'); // this test only ensures that we eventually give and timeout (when searching "funny" words and long lines) // for the sake of speedy tests we lower the timeBudget here @@ -345,7 +345,7 @@ suite('ExtHostDocumentData', () => { data = new ExtHostDocumentData(undefined!, URI.file(''), [ line - ], '\n', 1, 'text', false); + ], '\n', 1, 'text', false, 'utf8'); const range = data.document.getWordRangeAtPosition(new Position(0, 27), regex)!; assert.strictEqual(range.start.line, 0); @@ -358,7 +358,7 @@ suite('ExtHostDocumentData', () => { data = new ExtHostDocumentData(undefined!, URI.file(''), [ `

Sheldon, soprannominato "Shelly dalla madre e dalla sorella, è nato a Galveston, in Texas, il 26 febbraio 1980 in un supermercato. È stato un bambino prodigio, come testimoniato dal suo quoziente d'intelligenza (187, di molto superiore alla norma) e dalla sua rapida carriera scolastica: si è diplomato all'eta di 11 anni approdando alla stessa età alla formazione universitaria e all'età di 16 anni ha ottenuto il suo primo dottorato di ricerca. All'inizio della serie e per gran parte di essa vive con il coinquilino Leonard nell'appartamento 4A al 2311 North Los Robles Avenue di Pasadena, per poi trasferirsi nell'appartamento di Penny con Amy nella decima stagione. Come più volte afferma lui stesso possiede una memoria eidetica e un orecchio assoluto. È stato educato da una madre estremamente religiosa e, in più occasioni, questo aspetto contrasta con il rigore scientifico di Sheldon; tuttavia la donna sembra essere l'unica persona in grado di comandarlo a bacchetta.

` - ], '\n', 1, 'text', false); + ], '\n', 1, 'text', false, 'utf8'); const pos = new Position(0, 55); const range = data.document.getWordRangeAtPosition(pos)!; @@ -426,7 +426,7 @@ suite('ExtHostDocumentData updates line mapping', () => { } function testLineMappingDirectionAfterEvents(lines: string[], eol: string, direction: AssertDocumentLineMappingDirection, e: IModelChangedEvent): void { - const myDocument = new ExtHostDocumentData(undefined!, URI.file(''), lines.slice(0), eol, 1, 'text', false); + const myDocument = new ExtHostDocumentData(undefined!, URI.file(''), lines.slice(0), eol, 1, 'text', false, 'utf8'); assertDocumentLineMapping(myDocument, direction); myDocument.onEvents(e); diff --git a/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts index 965bb16223ed4..f2ccb19be7087 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts @@ -39,6 +39,7 @@ suite('ExtHostDocumentSaveParticipant', () => { versionId: 1, lines: ['foo'], EOL: '\n', + encoding: 'utf8' }] }); documents = new ExtHostDocuments(SingleProxyRPCProtocol(null), documentsAndEditors); diff --git a/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts index a0b9f1f7bef11..ca8101f20762a 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts @@ -32,7 +32,8 @@ suite('ExtHostDocumentsAndEditors', () => { lines: [ 'first', 'second' - ] + ], + encoding: 'utf8' }] }); diff --git a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts index e26fa6617e057..7aa83e5df457f 100644 --- a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts @@ -111,6 +111,7 @@ suite('ExtHostLanguageFeatures', function () { uri: model.uri, lines: model.getValue().split(model.getEOL()), EOL: model.getEOL(), + encoding: 'utf8' }] }); const extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); diff --git a/src/vs/workbench/api/test/browser/extHostNotebook.test.ts b/src/vs/workbench/api/test/browser/extHostNotebook.test.ts index 54b14a349b4ad..0d7138473e671 100644 --- a/src/vs/workbench/api/test/browser/extHostNotebook.test.ts +++ b/src/vs/workbench/api/test/browser/extHostNotebook.test.ts @@ -220,7 +220,8 @@ suite('NotebookCell#Document', function () { lines: doc.getText().split('\n'), languageId: doc.languageId, uri: doc.uri, - versionId: doc.version + versionId: doc.version, + encoding: 'utf8' }); } diff --git a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts index 39a0b50d1be66..c5670e1801fb8 100644 --- a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts @@ -19,7 +19,7 @@ suite('ExtHostTextEditor', () => { let editor: ExtHostTextEditor; const doc = new ExtHostDocumentData(undefined!, URI.file(''), [ 'aaaa bbbb+cccc abc' - ], '\n', 1, 'text', false); + ], '\n', 1, 'text', false, 'utf8'); setup(() => { editor = new ExtHostTextEditor('fake', null!, new NullLogService(), new Lazy(() => doc.document), [], { cursorStyle: TextEditorCursorStyle.Line, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4, originalIndentSize: 'tabSize' }, [], 1); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 9ba24786bc5ce..d1f97afe1ffd6 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -270,6 +270,11 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.fileService.writeFile(resource, readable, options); } + getEncoding(resource: URI): string { + const model = resource.scheme === Schemas.untitled ? this.untitled.get(resource) : this.files.get(resource); + return model?.getEncoding() ?? this.encoding.getUnvalidatedEncodingForResource(resource); + } + async getEncodedReadable(resource: URI, value: ITextSnapshot): Promise; async getEncodedReadable(resource: URI, value: string): Promise; async getEncodedReadable(resource: URI, value?: ITextSnapshot): Promise; @@ -773,7 +778,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { } async getPreferredWriteEncoding(resource: URI, preferredEncoding?: string): Promise { - const resourceEncoding = await this.getEncodingForResource(resource, preferredEncoding); + const resourceEncoding = await this.getValidatedEncodingForResource(resource, preferredEncoding); return { encoding: resourceEncoding, @@ -803,7 +808,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { preferredEncoding = UTF8; // if we did not detect UTF 8 BOM before, this can only be UTF 8 then } - const encoding = await this.getEncodingForResource(resource, preferredEncoding); + const encoding = await this.getValidatedEncodingForResource(resource, preferredEncoding); return { encoding, @@ -811,7 +816,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { }; } - private async getEncodingForResource(resource: URI, preferredEncoding?: string): Promise { + getUnvalidatedEncodingForResource(resource: URI, preferredEncoding?: string): string { let fileEncoding: string; const override = this.getEncodingOverride(resource); @@ -823,6 +828,11 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { fileEncoding = this.textResourceConfigurationService.getValue(resource, 'files.encoding'); // and last we check for settings } + return fileEncoding ?? UTF8; + } + + private async getValidatedEncodingForResource(resource: URI, preferredEncoding?: string): Promise { + let fileEncoding = this.getUnvalidatedEncodingForResource(resource, preferredEncoding); if (fileEncoding !== UTF8) { if (!fileEncoding || !(await encodingExists(fileEncoding))) { fileEncoding = UTF8; // the default is UTF-8 diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 7681f36904735..6be484f8d4b4c 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -116,6 +116,8 @@ export interface ITextFileService extends IDisposable { * Will throw an error if `acceptTextOnly: true` for resources that seem to be binary. */ getDecodedStream(resource: URI, value: VSBufferReadableStream, options?: IReadTextFileEncodingOptions): Promise>; + + getEncoding(resource: URI): string; } export interface IReadTextFileEncodingOptions { diff --git a/src/vscode-dts/vscode.proposed.editorEncoding.d.ts b/src/vscode-dts/vscode.proposed.editorEncoding.d.ts deleted file mode 100644 index cb7835ef02fb3..0000000000000 --- a/src/vscode-dts/vscode.proposed.editorEncoding.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - // https://github.com/microsoft/vscode/issues/824 - - export enum EncodingMode { - - /** - * Instructs the encoding support to encode the object with the provided encoding - */ - Encode = 0, - - /** - * Instructs the encoding support to decode the object with the provided encoding - */ - Decode = 1 - } - - export interface TextEditor { - /** - * Get the text editor encoding. - */ - getEncoding(): Thenable; - - /** - * Set the text editor encoding. - * @param encoding - * @param mode - */ - setEncoding(encoding: string, mode: EncodingMode): Thenable; - } -} diff --git a/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts b/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts new file mode 100644 index 0000000000000..2347b27b318df --- /dev/null +++ b/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/824 + + export interface TextDocument { + + /** + * The text document's encoding. + */ + readonly encoding: string; + + /** + * Encodes the text document's content using the specified encoding. + * + * @param encoding The encoding to be used for encoding the document. + * @returns A promise that resolves when the encoding is complete. + */ + encode(encoding: string): Thenable; + + /** + * Decodes the text document's content using the specified encoding. + * + * @param encoding The encoding to be used for decoding the document. + * @returns A promise that resolves when the decoding is complete. + */ + decode(encoding: string): Thenable; + } +} From 155a06dbb039a9f91d8bce85f2a9fb1f93e88346 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 08:55:11 +0100 Subject: [PATCH 03/24] . --- .../services/textfile/browser/textFileService.ts | 6 +++--- src/vs/workbench/services/textfile/common/textfiles.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index d1f97afe1ffd6..1238c8594a89c 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -828,14 +828,14 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { fileEncoding = this.textResourceConfigurationService.getValue(resource, 'files.encoding'); // and last we check for settings } - return fileEncoding ?? UTF8; + return fileEncoding || UTF8; } private async getValidatedEncodingForResource(resource: URI, preferredEncoding?: string): Promise { let fileEncoding = this.getUnvalidatedEncodingForResource(resource, preferredEncoding); if (fileEncoding !== UTF8) { - if (!fileEncoding || !(await encodingExists(fileEncoding))) { - fileEncoding = UTF8; // the default is UTF-8 + if (!(await encodingExists(fileEncoding))) { + fileEncoding = UTF8; } } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 6be484f8d4b4c..c52f9295af8ca 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -98,6 +98,12 @@ export interface ITextFileService extends IDisposable { */ create(operations: { resource: URI; value?: string | ITextSnapshot; options?: { overwrite?: boolean } }[], undoInfo?: IFileOperationUndoRedoInfo): Promise; + /** + * Get the encoding for the provided `resource`. Will try to determine the encoding + * from any existing model for that `resource` and fallback to the configured defaults. + */ + getEncoding(resource: URI): string; + /** * Returns the readable that uses the appropriate encoding. This method should * be used whenever a `string` or `ITextSnapshot` is being persisted to the @@ -116,8 +122,6 @@ export interface ITextFileService extends IDisposable { * Will throw an error if `acceptTextOnly: true` for resources that seem to be binary. */ getDecodedStream(resource: URI, value: VSBufferReadableStream, options?: IReadTextFileEncodingOptions): Promise>; - - getEncoding(resource: URI): string; } export interface IReadTextFileEncodingOptions { From 2374bc973f4ecf96c5333668080ebce60469bb11 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 09:11:59 +0100 Subject: [PATCH 04/24] more work --- .../api/browser/mainThreadDocuments.ts | 34 ++++++++++++++++++- .../workbench/api/common/extHost.protocol.ts | 3 ++ .../api/common/extHostDocumentData.ts | 30 +++++++++++++--- .../workbench/api/common/extHostDocuments.ts | 14 ++++++++ 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 6ded63e34be22..e37e1a1746bfe 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -12,7 +12,7 @@ import { IModelService } from '../../../editor/common/services/model.js'; import { ITextModelService } from '../../../editor/common/services/resolverService.js'; import { IFileService, FileOperation } from '../../../platform/files/common/files.js'; import { ExtHostContext, ExtHostDocumentsShape, MainThreadDocumentsShape } from '../common/extHost.protocol.js'; -import { ITextFileService } from '../../services/textfile/common/textfiles.js'; +import { EncodingMode, ITextFileService } from '../../services/textfile/common/textfiles.js'; import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js'; import { toLocalResource, extUri, IExtUri } from '../../../base/common/resources.js'; import { IWorkingCopyFileService } from '../../services/workingCopy/common/workingCopyFileService.js'; @@ -146,6 +146,23 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen } })); + this._store.add(_textFileService.files.onDidChangeEncoding(m => { + if (this._shouldHandleFileEvent(m.resource)) { + const encoding = m.getEncoding(); + if (encoding) { + this._proxy.$acceptEncodingChanged(m.resource, encoding); + } + } + })); + this._store.add(_textFileService.untitled.onDidChangeEncoding(m => { + if (this._shouldHandleFileEvent(m.resource)) { + const encoding = m.getEncoding(); + if (encoding) { + this._proxy.$acceptEncodingChanged(m.resource, encoding); + } + } + })); + this._store.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => { const isMove = e.operation === FileOperation.MOVE; if (isMove || e.operation === FileOperation.DELETE) { @@ -283,4 +300,19 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen this._proxy.$acceptDirtyStateChanged(resource, true); // mark as dirty return resource; } + + async $tryDecode(uri: UriComponents, encoding: string): Promise { + await this.setEncoding(uri, encoding, EncodingMode.Decode); + } + + async $tryEncode(uri: UriComponents, encoding: string): Promise { + await this.setEncoding(uri, encoding, EncodingMode.Encode); + } + + private async setEncoding(uri: UriComponents, encoding: string, mode: EncodingMode): Promise { + const inputUri = URI.revive(uri); + const target = inputUri.scheme === Schemas.untitled ? this._textFileService.untitled.get(inputUri) : this._textFileService.files.get(inputUri); + + return target?.setEncoding(encoding, mode); + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 76514d2807b35..a4e0cd1b425d9 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -226,6 +226,8 @@ export interface MainThreadDocumentsShape extends IDisposable { $tryCreateDocument(options?: { language?: string; content?: string }): Promise; $tryOpenDocument(uri: UriComponents): Promise; $trySaveDocument(uri: UriComponents): Promise; + $tryDecode(uri: UriComponents, encoding: string): Promise; + $tryEncode(uri: UriComponents, encoding: string): Promise; } export interface ITextEditorConfigurationUpdate { @@ -1799,6 +1801,7 @@ export interface ExtHostDocumentsShape { $acceptModelLanguageChanged(strURL: UriComponents, newLanguageId: string): void; $acceptModelSaved(strURL: UriComponents): void; $acceptDirtyStateChanged(strURL: UriComponents, isDirty: boolean): void; + $acceptEncodingChanged(strURL: UriComponents, encoding: string): void; $acceptModelChanged(strURL: UriComponents, e: IModelChangedEvent, isDirty: boolean): void; } diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index ce9c975fb88ba..e444a6698a660 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -67,7 +67,10 @@ export class ExtHostDocumentData extends MirrorTextModel { get version() { return that._versionId; }, get isClosed() { return that._isDisposed; }, get isDirty() { return that._isDirty; }, - get encoding() { return that._encoding; }, + get encoding() { + // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); + return that._encoding; + }, save() { return that._save(); }, getText(range?) { return range ? that._getTextInRange(range) : that.getText(); }, get eol() { return that._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; }, @@ -78,8 +81,14 @@ export class ExtHostDocumentData extends MirrorTextModel { validateRange(ran) { return that._validateRange(ran); }, validatePosition(pos) { return that._validatePosition(pos); }, getWordRangeAtPosition(pos, regexp?) { return that._getWordRangeAtPosition(pos, regexp); }, - encode(encoding) { return that._encode(encoding); }, - decode(encoding) { return that._decode(encoding); }, + encode(encoding) { + // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); + return that._encode(encoding); + }, + decode(encoding) { + // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); + return that._decode(encoding); + }, [Symbol.for('debug.description')]() { return `TextDocument(${that._uri.toString()})`; } @@ -98,6 +107,11 @@ export class ExtHostDocumentData extends MirrorTextModel { this._isDirty = isDirty; } + _acceptEncoding(encoding: string): void { + ok(!this._isDisposed); + this._encoding = encoding; + } + private _save(): Promise { if (this._isDisposed) { return Promise.reject(new Error('Document has been closed')); @@ -249,11 +263,17 @@ export class ExtHostDocumentData extends MirrorTextModel { // --- encodings private _decode(encoding: string): Thenable { - throw new Error('Method not implemented.'); + if (this._isDisposed) { + return Promise.reject(new Error('Document has been closed')); + } + return this._proxy.$tryDecode(this._uri, encoding); } private _encode(encoding: string): Thenable { - throw new Error('Method not implemented.'); + if (this._isDisposed) { + return Promise.reject(new Error('Document has been closed')); + } + return this._proxy.$tryEncode(this._uri, encoding); } } diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index c26baab4a6726..c0d0ca5ad9c12 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -140,6 +140,20 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { }); } + public $acceptEncodingChanged(uriComponents: UriComponents, encoding: string): void { + const uri = URI.revive(uriComponents); + const data = this._documentsAndEditors.getDocument(uri); + if (!data) { + throw new Error('unknown document'); + } + data._acceptEncoding(encoding); + this._onDidChangeDocument.fire({ + document: data.document, + contentChanges: [], + reason: undefined + }); + } + public $acceptModelChanged(uriComponents: UriComponents, events: IModelChangedEvent, isDirty: boolean): void { const uri = URI.revive(uriComponents); const data = this._documentsAndEditors.getDocument(uri); From 8eab2c0e42b1234f267e05a047a9173e6edc3936 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 09:14:58 +0100 Subject: [PATCH 05/24] unwind --- .../api/browser/mainThreadEditors.ts | 48 ------------------- .../workbench/api/common/extHost.protocol.ts | 2 - .../test/browser/extHostTextEditor.test.ts | 4 +- .../browser/parts/editor/editorStatus.ts | 2 +- 4 files changed, 2 insertions(+), 54 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 649078ffcf74a..a61ba3c780107 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -36,8 +36,6 @@ import { LineRangeMapping } from '../../../editor/common/diff/rangeMapping.js'; import { equals } from '../../../base/common/arrays.js'; import { Event } from '../../../base/common/event.js'; import { DiffAlgorithmName } from '../../../editor/common/services/editorWorker.js'; -import { toEditorWithEncodingSupport } from '../../browser/parts/editor/editorStatus.js'; -import { EncodingMode } from '../../services/textfile/common/textfiles.js'; export interface IMainThreadEditorLocator { getEditor(id: string): MainThreadTextEditor | undefined; @@ -407,52 +405,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { quickDiffModelRef.dispose(); } } - - $tryGetEncoding(id: string): Promise { - const editor = this._editorLocator.getEditor(id); - - if (!editor) { - return Promise.reject(new Error('No such TextEditor')); - } - - const editorPanes = this._editorService.visibleEditorPanes; - for (const editorPane of editorPanes) { - if (editor.matches(editorPane)) { - const encodingSupport = toEditorWithEncodingSupport(editorPane.input); - if (!encodingSupport) { - return Promise.reject(new Error('No file active at this time')); - } - - const encoding = encodingSupport.getEncoding(); - - return Promise.resolve(encoding); - } - } - - return Promise.reject(new Error('No text editor active at this time')); - } - - $trySetEncoding(id: string, encoding: string, mode: number): Promise { - const editor = this._editorLocator.getEditor(id); - - if (!editor) { - return Promise.reject(new Error('No such TextEditor')); - } - - const editorPanes = this._editorService.visibleEditorPanes; - for (const editorPane of editorPanes) { - if (editor.matches(editorPane)) { - const encodingSupport = toEditorWithEncodingSupport(editorPane.input); - if (!encodingSupport) { - return Promise.reject(new Error('No file active at this time')); - } - - return encodingSupport.setEncoding(encoding, mode === 0 ? EncodingMode.Encode : EncodingMode.Decode); - } - } - - return Promise.reject(new Error('No text editor active at this time')); - } } // --- commands diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a4e0cd1b425d9..af66409831207 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -291,8 +291,6 @@ export interface MainThreadTextEditorsShape extends IDisposable { $tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): Promise; $tryInsertSnippet(id: string, modelVersionId: number, template: string, selections: readonly IRange[], opts: IUndoStopOptions): Promise; $getDiffInformation(id: string): Promise; - $tryGetEncoding(id: string): Promise; - $trySetEncoding(id: string, encoding: string, mode: number): Promise; } export interface MainThreadTreeViewsShape extends IDisposable { diff --git a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts index c5670e1801fb8..58148a5164e58 100644 --- a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts @@ -89,9 +89,7 @@ suite('ExtHostTextEditorOptions', () => { $trySetSelections: undefined!, $tryApplyEdits: undefined!, $tryInsertSnippet: undefined!, - $getDiffInformation: undefined!, - $tryGetEncoding: undefined!, - $trySetEncoding: undefined! + $getDiffInformation: undefined! }; opts = new ExtHostTextEditorOptions(mockProxy, '1', { tabSize: 4, diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index d06e98234558e..a1b9bd5215c2c 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -79,7 +79,7 @@ class SideBySideEditorLanguageSupport implements ILanguageSupport { } } -export function toEditorWithEncodingSupport(input: EditorInput): IEncodingSupport | null { +function toEditorWithEncodingSupport(input: EditorInput): IEncodingSupport | null { // Untitled Text Editor if (input instanceof UntitledTextEditorInput) { From 932ae6a5e52244ff5a88b043c5c6d7998e375e8f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 09:17:52 +0100 Subject: [PATCH 06/24] easy --- .../workbench/api/browser/mainThreadDocuments.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index e37e1a1746bfe..aabda17913cda 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -12,7 +12,7 @@ import { IModelService } from '../../../editor/common/services/model.js'; import { ITextModelService } from '../../../editor/common/services/resolverService.js'; import { IFileService, FileOperation } from '../../../platform/files/common/files.js'; import { ExtHostContext, ExtHostDocumentsShape, MainThreadDocumentsShape } from '../common/extHost.protocol.js'; -import { EncodingMode, ITextFileService } from '../../services/textfile/common/textfiles.js'; +import { EncodingMode, ITextFileEditorModel, ITextFileService } from '../../services/textfile/common/textfiles.js'; import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js'; import { toLocalResource, extUri, IExtUri } from '../../../base/common/resources.js'; import { IWorkingCopyFileService } from '../../services/workingCopy/common/workingCopyFileService.js'; @@ -22,6 +22,7 @@ import { IPathService } from '../../services/path/common/pathService.js'; import { ResourceMap } from '../../../base/common/map.js'; import { IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { ErrorNoTelemetry } from '../../../base/common/errors.js'; +import { IUntitledTextEditorModel } from '../../services/untitled/common/untitledTextEditorModel.js'; export class BoundModelReferenceCollection { @@ -145,16 +146,7 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen this._proxy.$acceptDirtyStateChanged(m.resource, m.isDirty()); } })); - - this._store.add(_textFileService.files.onDidChangeEncoding(m => { - if (this._shouldHandleFileEvent(m.resource)) { - const encoding = m.getEncoding(); - if (encoding) { - this._proxy.$acceptEncodingChanged(m.resource, encoding); - } - } - })); - this._store.add(_textFileService.untitled.onDidChangeEncoding(m => { + this._store.add(Event.any(_textFileService.files.onDidChangeEncoding, _textFileService.untitled.onDidChangeEncoding)(m => { if (this._shouldHandleFileEvent(m.resource)) { const encoding = m.getEncoding(); if (encoding) { From 76a794c986de60974b4b2cfdeb9e2f0ee72083ff Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 09:22:03 +0100 Subject: [PATCH 07/24] cleanup --- src/vs/workbench/api/browser/mainThreadDocuments.ts | 2 +- .../api/browser/mainThreadDocumentsAndEditors.ts | 2 +- src/vs/workbench/api/common/extHostDocumentData.ts | 10 +++++----- src/vs/workbench/api/common/extHostInteractive.ts | 2 +- src/vs/workbench/api/common/extHostNotebookDocument.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index aabda17913cda..13a91067fded8 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -13,6 +13,7 @@ import { ITextModelService } from '../../../editor/common/services/resolverServi import { IFileService, FileOperation } from '../../../platform/files/common/files.js'; import { ExtHostContext, ExtHostDocumentsShape, MainThreadDocumentsShape } from '../common/extHost.protocol.js'; import { EncodingMode, ITextFileEditorModel, ITextFileService } from '../../services/textfile/common/textfiles.js'; +import { IUntitledTextEditorModel } from '../../services/untitled/common/untitledTextEditorModel.js'; import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js'; import { toLocalResource, extUri, IExtUri } from '../../../base/common/resources.js'; import { IWorkingCopyFileService } from '../../services/workingCopy/common/workingCopyFileService.js'; @@ -22,7 +23,6 @@ import { IPathService } from '../../services/path/common/pathService.js'; import { ResourceMap } from '../../../base/common/map.js'; import { IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { ErrorNoTelemetry } from '../../../base/common/errors.js'; -import { IUntitledTextEditorModel } from '../../services/untitled/common/untitledTextEditorModel.js'; export class BoundModelReferenceCollection { diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 5aab6f573ed0b..69f487ee521c5 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -387,7 +387,7 @@ export class MainThreadDocumentsAndEditors { EOL: model.getEOL(), languageId: model.getLanguageId(), isDirty: this._textFileService.isDirty(model.uri), - encoding: this._textFileService.getEncoding(model.uri), + encoding: this._textFileService.getEncoding(model.uri) }; } diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index e444a6698a660..b52786a49bc4d 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -107,11 +107,6 @@ export class ExtHostDocumentData extends MirrorTextModel { this._isDirty = isDirty; } - _acceptEncoding(encoding: string): void { - ok(!this._isDisposed); - this._encoding = encoding; - } - private _save(): Promise { if (this._isDisposed) { return Promise.reject(new Error('Document has been closed')); @@ -262,6 +257,11 @@ export class ExtHostDocumentData extends MirrorTextModel { // --- encodings + _acceptEncoding(encoding: string): void { + ok(!this._isDisposed); + this._encoding = encoding; + } + private _decode(encoding: string): Thenable { if (this._isDisposed) { return Promise.reject(new Error('Document has been closed')); diff --git a/src/vs/workbench/api/common/extHostInteractive.ts b/src/vs/workbench/api/common/extHostInteractive.ts index 0c76d8e682ce3..9030149e35075 100644 --- a/src/vs/workbench/api/common/extHostInteractive.ts +++ b/src/vs/workbench/api/common/extHostInteractive.ts @@ -52,7 +52,7 @@ export class ExtHostInteractive implements ExtHostInteractiveShape { uri: uri, isDirty: false, versionId: 1, - encoding: 'utf8' // todo + encoding: 'utf8' }] }); } diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index 1ed72abbbcbe3..333dad5de8db0 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -41,7 +41,7 @@ export class ExtHostCell { uri: cell.uri, isDirty: false, versionId: 1, - encoding: 'utf8' // todo + encoding: 'utf8' }; } From 2f10286ccf4e2db68968d7a6f9cf5f9d37a4b616 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 16:20:11 +0100 Subject: [PATCH 08/24] more --- .../api/browser/mainThreadDocuments.ts | 45 ++++++++++++------- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 4 +- .../api/common/extHostDocumentData.ts | 31 +++---------- .../browser/parts/editor/editorStatus.ts | 5 ++- .../files/browser/editors/fileEditorInput.ts | 4 +- .../textfile/common/textFileEditorModel.ts | 11 +++-- .../services/textfile/common/textfiles.ts | 2 +- .../common/untitledTextEditorInput.ts | 2 +- .../common/untitledTextEditorModel.ts | 6 ++- .../parts/editor/editorGroupModel.test.ts | 2 +- .../editor/filteredEditorGroupModel.test.ts | 2 +- .../test/browser/workbenchTestServices.ts | 2 +- .../vscode.proposed.textDocumentEncoding.d.ts | 22 ++------- 14 files changed, 61 insertions(+), 79 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 13a91067fded8..f1e3007ef4183 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -214,11 +214,37 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen // --- from extension host process - async $trySaveDocument(uri: UriComponents): Promise { - const target = await this._textFileService.save(URI.revive(uri)); + async $trySaveDocument(uri: UriComponents, options?: { encoding?: string }): Promise { + const inputUri = URI.revive(uri); + + if (options?.encoding) { + return this.doTrySaveDocumentWithEncoding(inputUri, options.encoding); + } + + return this.doTrySaveDocument(inputUri); + } + + private async doTrySaveDocument(uri: URI): Promise { + const target = await this._textFileService.save(uri); return Boolean(target); } + private async doTrySaveDocumentWithEncoding(uri: URI, encoding: string): Promise { + const target = uri.scheme === Schemas.untitled ? this._textFileService.untitled.get(uri) : this._textFileService.files.get(uri); + + if (target) { + let success = await target.setEncoding(encoding, EncodingMode.Encode); + + if (uri.scheme === Schemas.untitled) { + success = await this.doTrySaveDocument(uri); + } + + return success; + } + + return false; + } + async $tryOpenDocument(uriData: UriComponents): Promise { const inputUri = URI.revive(uriData); if (!inputUri.scheme || !(inputUri.fsPath || inputUri.authority)) { @@ -292,19 +318,4 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen this._proxy.$acceptDirtyStateChanged(resource, true); // mark as dirty return resource; } - - async $tryDecode(uri: UriComponents, encoding: string): Promise { - await this.setEncoding(uri, encoding, EncodingMode.Decode); - } - - async $tryEncode(uri: UriComponents, encoding: string): Promise { - await this.setEncoding(uri, encoding, EncodingMode.Encode); - } - - private async setEncoding(uri: UriComponents, encoding: string, mode: EncodingMode): Promise { - const inputUri = URI.revive(uri); - const target = inputUri.scheme === Schemas.untitled ? this._textFileService.untitled.get(inputUri) : this._textFileService.files.get(inputUri); - - return target?.setEncoding(encoding, mode); - } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index af61950f93446..c44e1592bd61e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1021,7 +1021,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I set textDocuments(value) { throw new errors.ReadonlyError('textDocuments'); }, - openTextDocument(uriOrFileNameOrOptions?: vscode.Uri | string | { language?: string; content?: string }) { + openTextDocument(uriOrFileNameOrOptions?: vscode.Uri | string | { language?: string; content?: string }, textDocumentOptions?: { encoding?: string }) { let uriPromise: Thenable; const options = uriOrFileNameOrOptions as { language?: string; content?: string }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index af66409831207..5ee96819a50a4 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -225,9 +225,7 @@ export interface MainThreadDocumentContentProvidersShape extends IDisposable { export interface MainThreadDocumentsShape extends IDisposable { $tryCreateDocument(options?: { language?: string; content?: string }): Promise; $tryOpenDocument(uri: UriComponents): Promise; - $trySaveDocument(uri: UriComponents): Promise; - $tryDecode(uri: UriComponents, encoding: string): Promise; - $tryEncode(uri: UriComponents, encoding: string): Promise; + $trySaveDocument(uri: UriComponents, options?: { encoding?: string }): Promise; } export interface ITextEditorConfigurationUpdate { diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index b52786a49bc4d..4123ee49b2d7a 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -71,7 +71,10 @@ export class ExtHostDocumentData extends MirrorTextModel { // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); return that._encoding; }, - save() { return that._save(); }, + save(options?: { encoding?: string }) { + // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); + return that._save(options); + }, getText(range?) { return range ? that._getTextInRange(range) : that.getText(); }, get eol() { return that._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; }, get lineCount() { return that._lines.length; }, @@ -81,14 +84,6 @@ export class ExtHostDocumentData extends MirrorTextModel { validateRange(ran) { return that._validateRange(ran); }, validatePosition(pos) { return that._validatePosition(pos); }, getWordRangeAtPosition(pos, regexp?) { return that._getWordRangeAtPosition(pos, regexp); }, - encode(encoding) { - // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); - return that._encode(encoding); - }, - decode(encoding) { - // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); - return that._decode(encoding); - }, [Symbol.for('debug.description')]() { return `TextDocument(${that._uri.toString()})`; } @@ -107,11 +102,11 @@ export class ExtHostDocumentData extends MirrorTextModel { this._isDirty = isDirty; } - private _save(): Promise { + private _save(options?: { encoding?: string }): Promise { if (this._isDisposed) { return Promise.reject(new Error('Document has been closed')); } - return this._proxy.$trySaveDocument(this._uri); + return this._proxy.$trySaveDocument(this._uri, options); } private _getTextInRange(_range: vscode.Range): string { @@ -261,20 +256,6 @@ export class ExtHostDocumentData extends MirrorTextModel { ok(!this._isDisposed); this._encoding = encoding; } - - private _decode(encoding: string): Thenable { - if (this._isDisposed) { - return Promise.reject(new Error('Document has been closed')); - } - return this._proxy.$tryDecode(this._uri, encoding); - } - - private _encode(encoding: string): Thenable { - if (this._isDisposed) { - return Promise.reject(new Error('Document has been closed')); - } - return this._proxy.$tryEncode(this._uri, encoding); - } } export class ExtHostDocumentLine implements vscode.TextLine { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index a1b9bd5215c2c..31303bf7cec54 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -65,8 +65,9 @@ class SideBySideEditorEncodingSupport implements IEncodingSupport { return this.primary.getEncoding(); // always report from modified (right hand) side } - async setEncoding(encoding: string, mode: EncodingMode): Promise { - await Promises.settled([this.primary, this.secondary].map(editor => editor.setEncoding(encoding, mode))); + async setEncoding(encoding: string, mode: EncodingMode): Promise { + const results = await Promises.settled([this.primary, this.secondary].map(editor => editor.setEncoding(encoding, mode))); + return results.every(result => !!result); } } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts index d7157206c9316..cb812694c96c5 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts @@ -253,10 +253,10 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements return this.preferredEncoding; } - async setEncoding(encoding: string, mode: EncodingMode): Promise { + async setEncoding(encoding: string, mode: EncodingMode): Promise { this.setPreferredEncoding(encoding); - return this.model?.setEncoding(encoding, mode); + return this.model?.setEncoding(encoding, mode) ?? false; } setPreferredEncoding(encoding: string): void { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 31b4827edfb0f..3c997d860717d 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -1116,7 +1116,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private hasEncodingSetExplicitly: boolean = false; - setEncoding(encoding: string, mode: EncodingMode): Promise { + setEncoding(encoding: string, mode: EncodingMode): Promise { // Remember that an explicit encoding was set this.hasEncodingSetExplicitly = true; @@ -1124,7 +1124,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.setEncodingInternal(encoding, mode); } - private async setEncodingInternal(encoding: string, mode: EncodingMode): Promise { + private async setEncodingInternal(encoding: string, mode: EncodingMode): Promise { // Encode: Save with encoding if (mode === EncodingMode.Encode) { @@ -1137,14 +1137,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } if (!this.inConflictMode) { - await this.save({ source: TextFileEditorModel.TEXTFILE_SAVE_ENCODING_SOURCE }); + return await this.save({ source: TextFileEditorModel.TEXTFILE_SAVE_ENCODING_SOURCE }); } } // Decode: Resolve with encoding else { if (!this.isNewEncoding(encoding)) { - return; // return early if the encoding is already the same + return true; // return early if the encoding is already the same } if (this.isDirty() && !this.inConflictMode) { @@ -1154,7 +1154,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.updatePreferredEncoding(encoding); await this.forceResolveFromFile(); + return true; } + + return false; } updatePreferredEncoding(encoding: string | undefined): void { diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index c52f9295af8ca..d6a7895d231bf 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -498,7 +498,7 @@ export interface IEncodingSupport { /** * Sets the encoding for the object for saving. */ - setEncoding(encoding: string, mode: EncodingMode): Promise; + setEncoding(encoding: string, mode: EncodingMode): Promise; } export interface ILanguageSupport { diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts index 9dcd00fc52026..e51564fad8a7f 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts @@ -130,7 +130,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return this.model.getEncoding(); } - setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): Promise { + setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): Promise { return this.model.setEncoding(encoding); } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index 5a4c357d56690..ed544f9499564 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -59,7 +59,7 @@ export interface IUntitledTextEditorModel extends ITextEditorModel, ILanguageSup /** * Sets the encoding to use for this untitled model. */ - setEncoding(encoding: string): Promise; + setEncoding(encoding: string): Promise; /** * Resolves the untitled model. @@ -223,7 +223,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt return this.preferredEncoding || this.configuredEncoding; } - async setEncoding(encoding: string): Promise { + async setEncoding(encoding: string): Promise { const oldEncoding = this.getEncoding(); this.preferredEncoding = encoding; @@ -231,6 +231,8 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt if (oldEncoding !== this.preferredEncoding) { this._onDidChangeEncoding.fire(); } + + return true; } //#endregion diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts index 30c8430e65577..36240060b12ef 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts @@ -247,7 +247,7 @@ suite('EditorGroupModel', () => { setPreferredName(name: string): void { } setPreferredDescription(description: string): void { } setPreferredResource(resource: URI): void { } - async setEncoding(encoding: string) { } + async setEncoding(encoding: string) { return true; } getEncoding() { return undefined; } setPreferredEncoding(encoding: string) { } setForceOpenAsBinary(): void { } diff --git a/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts index c4fef60fd157d..1d02bfc6f1804 100644 --- a/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts @@ -115,7 +115,7 @@ suite('FilteredEditorGroupModel', () => { setPreferredName(name: string): void { } setPreferredDescription(description: string): void { } setPreferredResource(resource: URI): void { } - async setEncoding(encoding: string) { } + async setEncoding(encoding: string) { return true; } getEncoding() { return undefined; } setPreferredEncoding(encoding: string) { } setForceOpenAsBinary(): void { } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 6161e10f542fc..ac38f89e5f582 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1770,7 +1770,7 @@ export class TestFileEditorInput extends EditorInput implements IFileEditorInput return isEqual(this.resource, other.resource) && (this.editorId === other.options?.override || other.options?.override === undefined); } setPreferredResource(resource: URI): void { } - async setEncoding(encoding: string) { } + async setEncoding(encoding: string) { return true; } getEncoding() { return undefined; } setPreferredName(name: string): void { } setPreferredDescription(description: string): void { } diff --git a/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts b/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts index 2347b27b318df..c2563d61f5f8b 100644 --- a/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts +++ b/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts @@ -9,25 +9,11 @@ declare module 'vscode' { export interface TextDocument { - /** - * The text document's encoding. - */ readonly encoding: string; - /** - * Encodes the text document's content using the specified encoding. - * - * @param encoding The encoding to be used for encoding the document. - * @returns A promise that resolves when the encoding is complete. - */ - encode(encoding: string): Thenable; - - /** - * Decodes the text document's content using the specified encoding. - * - * @param encoding The encoding to be used for decoding the document. - * @returns A promise that resolves when the decoding is complete. - */ - decode(encoding: string): Thenable; + save(options?: { encoding?: string }): Thenable; } + + export function openTextDocument(uri: Uri, options?: { encoding?: string }): Thenable; + export function openTextDocument(path: string, options?: { encoding?: string }): Thenable; } From aed67f2cda406671bc64a4c97ff75c8739c639a3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 16:27:13 +0100 Subject: [PATCH 09/24] simple --- .../api/browser/mainThreadDocuments.ts | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index f1e3007ef4183..eaeb3b7ab731c 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -218,31 +218,21 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen const inputUri = URI.revive(uri); if (options?.encoding) { - return this.doTrySaveDocumentWithEncoding(inputUri, options.encoding); - } - - return this.doTrySaveDocument(inputUri); - } - - private async doTrySaveDocument(uri: URI): Promise { - const target = await this._textFileService.save(uri); - return Boolean(target); - } - - private async doTrySaveDocumentWithEncoding(uri: URI, encoding: string): Promise { - const target = uri.scheme === Schemas.untitled ? this._textFileService.untitled.get(uri) : this._textFileService.files.get(uri); - - if (target) { - let success = await target.setEncoding(encoding, EncodingMode.Encode); + const target = uri.scheme === Schemas.untitled ? this._textFileService.untitled.get(inputUri) : this._textFileService.files.get(inputUri); + if (target) { + const result = await target.setEncoding(options.encoding, EncodingMode.Encode); + if (!result) { + return false; + } - if (uri.scheme === Schemas.untitled) { - success = await this.doTrySaveDocument(uri); + if (uri.scheme !== Schemas.untitled) { + return result; // non untitled files get saved right away + } } - - return success; } - return false; + const target = await this._textFileService.save(inputUri); + return Boolean(target); } async $tryOpenDocument(uriData: UriComponents): Promise { From bd7d19e21a346219c046d334fe53160ef1e33a65 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 16:28:03 +0100 Subject: [PATCH 10/24] . --- .../workbench/api/browser/mainThreadDocuments.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index eaeb3b7ab731c..fe81692a0045c 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -219,15 +219,13 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen if (options?.encoding) { const target = uri.scheme === Schemas.untitled ? this._textFileService.untitled.get(inputUri) : this._textFileService.files.get(inputUri); - if (target) { - const result = await target.setEncoding(options.encoding, EncodingMode.Encode); - if (!result) { - return false; - } + const result = await target?.setEncoding(options.encoding, EncodingMode.Encode); + if (!result) { + return false; + } - if (uri.scheme !== Schemas.untitled) { - return result; // non untitled files get saved right away - } + if (uri.scheme !== Schemas.untitled) { + return result; // non untitled files get saved right away } } From 1e43f7c9f93ec57d0a523563a1b98e2c50e9b929 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 16:32:08 +0100 Subject: [PATCH 11/24] cleanup --- src/vs/workbench/api/common/extHostDocumentData.ts | 12 +++++------- .../services/textfile/common/textFileEditorModel.ts | 9 +++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index 4123ee49b2d7a..2c5173b727ed0 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -102,6 +102,11 @@ export class ExtHostDocumentData extends MirrorTextModel { this._isDirty = isDirty; } + _acceptEncoding(encoding: string): void { + ok(!this._isDisposed); + this._encoding = encoding; + } + private _save(options?: { encoding?: string }): Promise { if (this._isDisposed) { return Promise.reject(new Error('Document has been closed')); @@ -249,13 +254,6 @@ export class ExtHostDocumentData extends MirrorTextModel { } return undefined; } - - // --- encodings - - _acceptEncoding(encoding: string): void { - ok(!this._isDisposed); - this._encoding = encoding; - } } export class ExtHostDocumentLine implements vscode.TextLine { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 3c997d860717d..95870ea789996 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -1136,9 +1136,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.setDirty(true); } - if (!this.inConflictMode) { - return await this.save({ source: TextFileEditorModel.TEXTFILE_SAVE_ENCODING_SOURCE }); + if (this.inConflictMode) { + return false; } + + return await this.save({ source: TextFileEditorModel.TEXTFILE_SAVE_ENCODING_SOURCE }); } // Decode: Resolve with encoding @@ -1154,10 +1156,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.updatePreferredEncoding(encoding); await this.forceResolveFromFile(); + return true; } - - return false; } updatePreferredEncoding(encoding: string | undefined): void { From 64f3fb43c704239ef1654dd3a3c84722c87fd904 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 16:48:08 +0100 Subject: [PATCH 12/24] implement --- .../workbench/api/browser/mainThreadDocuments.ts | 14 +++++++++----- src/vs/workbench/api/common/extHost.api.impl.ts | 3 ++- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostDocuments.ts | 6 +++--- .../vscode.proposed.textDocumentEncoding.d.ts | 6 ++++-- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index fe81692a0045c..56638a4a4ab71 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -233,7 +233,7 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen return Boolean(target); } - async $tryOpenDocument(uriData: UriComponents): Promise { + async $tryOpenDocument(uriData: UriComponents, options?: { encoding?: string }): Promise { const inputUri = URI.revive(uriData); if (!inputUri.scheme || !(inputUri.fsPath || inputUri.authority)) { throw new ErrorNoTelemetry(`Invalid uri. Scheme and authority or path must be set.`); @@ -244,11 +244,11 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen let promise: Promise; switch (canonicalUri.scheme) { case Schemas.untitled: - promise = this._handleUntitledScheme(canonicalUri); + promise = this._handleUntitledScheme(canonicalUri, options); break; case Schemas.file: default: - promise = this._handleAsResourceInput(canonicalUri); + promise = this._handleAsResourceInput(canonicalUri, options); break; } @@ -273,13 +273,17 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen return this._doCreateUntitled(undefined, options ? options.language : undefined, options ? options.content : undefined); } - private async _handleAsResourceInput(uri: URI): Promise { + private async _handleAsResourceInput(uri: URI, options: { encoding?: string } | undefined): Promise { + if (options?.encoding) { + await this._textFileService.files.resolve(uri, { encoding: options?.encoding }); + } + const ref = await this._textModelResolverService.createModelReference(uri); this._modelReferenceCollection.add(uri, ref, ref.object.textEditorModel.getValueLength()); return ref.object.textEditorModel.uri; } - private async _handleUntitledScheme(uri: URI): Promise { + private async _handleUntitledScheme(uri: URI, options: { encoding?: string } | undefined): Promise { const asLocalUri = toLocalResource(uri, this._environmentService.remoteAuthority, this._pathService.defaultUriScheme); const exists = await this._fileService.exists(asLocalUri); if (exists) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index c44e1592bd61e..f22c6f52242e0 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1022,6 +1022,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I throw new errors.ReadonlyError('textDocuments'); }, openTextDocument(uriOrFileNameOrOptions?: vscode.Uri | string | { language?: string; content?: string }, textDocumentOptions?: { encoding?: string }) { + // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); let uriPromise: Thenable; const options = uriOrFileNameOrOptions as { language?: string; content?: string }; @@ -1040,7 +1041,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I if (uri.scheme === Schemas.vscodeRemote && !uri.authority) { extHostApiDeprecation.report('workspace.openTextDocument', extension, `A URI of 'vscode-remote' scheme requires an authority.`); } - return extHostDocuments.ensureDocumentData(uri).then(documentData => { + return extHostDocuments.ensureDocumentData(uri, textDocumentOptions).then(documentData => { return documentData.document; }); }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5ee96819a50a4..ced32f640f29e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -224,7 +224,7 @@ export interface MainThreadDocumentContentProvidersShape extends IDisposable { export interface MainThreadDocumentsShape extends IDisposable { $tryCreateDocument(options?: { language?: string; content?: string }): Promise; - $tryOpenDocument(uri: UriComponents): Promise; + $tryOpenDocument(uri: UriComponents, options?: { encoding?: string }): Promise; $trySaveDocument(uri: UriComponents, options?: { encoding?: string }): Promise; } diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index c0d0ca5ad9c12..4f8b91e6c6bd2 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -76,16 +76,16 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { return data.document; } - public ensureDocumentData(uri: URI): Promise { + public ensureDocumentData(uri: URI, options?: { encoding?: string }): Promise { - const cached = this._documentsAndEditors.getDocument(uri); + const cached = this._documentsAndEditors.getDocument(uri); // TODO cache per encoding? if (cached) { return Promise.resolve(cached); } let promise = this._documentLoader.get(uri.toString()); if (!promise) { - promise = this._proxy.$tryOpenDocument(uri).then(uriData => { + promise = this._proxy.$tryOpenDocument(uri, options).then(uriData => { this._documentLoader.delete(uri.toString()); const canonicalUri = URI.revive(uriData); return assertIsDefined(this._documentsAndEditors.getDocument(canonicalUri)); diff --git a/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts b/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts index c2563d61f5f8b..17ec56d3a35a3 100644 --- a/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts +++ b/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts @@ -14,6 +14,8 @@ declare module 'vscode' { save(options?: { encoding?: string }): Thenable; } - export function openTextDocument(uri: Uri, options?: { encoding?: string }): Thenable; - export function openTextDocument(path: string, options?: { encoding?: string }): Thenable; + export namespace workspace { + export function openTextDocument(uri: Uri, options?: { encoding?: string }): Thenable; + export function openTextDocument(path: string, options?: { encoding?: string }): Thenable; + } } From 11eb2cf2d8744e3c16337cade321a102064cf64a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 13 Feb 2025 17:19:37 +0100 Subject: [PATCH 13/24] fix tests --- .../api/test/browser/mainThreadDocumentsAndEditors.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts index e077dfc7ac7e9..26dd5eaf8f2eb 100644 --- a/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts @@ -83,8 +83,13 @@ suite('MainThreadDocumentsAndEditors', () => { override files = { onDidSave: Event.None, onDidRevert: Event.None, - onDidChangeDirty: Event.None + onDidChangeDirty: Event.None, + onDidChangeEncoding: Event.None }; + override untitled = { + onDidChangeEncoding: Event.None + }; + override getEncoding() { return 'utf8'; } }; const workbenchEditorService = disposables.add(new TestEditorService()); const editorGroupService = new TestEditorGroupsService(); From a5d88db4d91f8dd78a5314b9570265ad43a6ec48 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 09:34:09 +0100 Subject: [PATCH 14/24] support encoding for new untitled as well --- src/vs/workbench/api/browser/mainThreadDocuments.ts | 9 +++++---- src/vs/workbench/api/common/extHost.api.impl.ts | 6 +++--- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostDocuments.ts | 2 +- src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts | 1 + 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 56638a4a4ab71..edfadcd8c3ee6 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -269,8 +269,8 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen } } - $tryCreateDocument(options?: { language?: string; content?: string }): Promise { - return this._doCreateUntitled(undefined, options ? options.language : undefined, options ? options.content : undefined); + $tryCreateDocument(options?: { language?: string; content?: string; encoding?: string }): Promise { + return this._doCreateUntitled(undefined, options ? options.language : undefined, options ? options.content : undefined, options?.encoding); } private async _handleAsResourceInput(uri: URI, options: { encoding?: string } | undefined): Promise { @@ -293,11 +293,12 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen return await this._doCreateUntitled(Boolean(uri.path) ? uri : undefined); } - private async _doCreateUntitled(associatedResource?: URI, languageId?: string, initialValue?: string): Promise { + private async _doCreateUntitled(associatedResource?: URI, languageId?: string, initialValue?: string, encoding?: string): Promise { const model = this._textFileService.untitled.create({ associatedResource, languageId, - initialValue + initialValue, + encoding }); const resource = model.resource; const ref = await this._textModelResolverService.createModelReference(resource); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f0870059dfe58..2e1208189b85c 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1024,11 +1024,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I set textDocuments(value) { throw new errors.ReadonlyError('textDocuments'); }, - openTextDocument(uriOrFileNameOrOptions?: vscode.Uri | string | { language?: string; content?: string }, textDocumentOptions?: { encoding?: string }) { + openTextDocument(uriOrFileNameOrOptions?: vscode.Uri | string | { language?: string; content?: string; encoding?: string }, options?: { encoding?: string }) { // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); let uriPromise: Thenable; - const options = uriOrFileNameOrOptions as { language?: string; content?: string }; + options = options ?? uriOrFileNameOrOptions as { language?: string; content?: string; encoding?: string }; if (typeof uriOrFileNameOrOptions === 'string') { uriPromise = Promise.resolve(URI.file(uriOrFileNameOrOptions)); } else if (URI.isUri(uriOrFileNameOrOptions)) { @@ -1044,7 +1044,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I if (uri.scheme === Schemas.vscodeRemote && !uri.authority) { extHostApiDeprecation.report('workspace.openTextDocument', extension, `A URI of 'vscode-remote' scheme requires an authority.`); } - return extHostDocuments.ensureDocumentData(uri, textDocumentOptions).then(documentData => { + return extHostDocuments.ensureDocumentData(uri, options).then(documentData => { return documentData.document; }); }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 638c3e808ea75..fd4d937bef2e2 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -237,7 +237,7 @@ export interface MainThreadDocumentContentProvidersShape extends IDisposable { } export interface MainThreadDocumentsShape extends IDisposable { - $tryCreateDocument(options?: { language?: string; content?: string }): Promise; + $tryCreateDocument(options?: { language?: string; content?: string; encoding?: string }): Promise; $tryOpenDocument(uri: UriComponents, options?: { encoding?: string }): Promise; $trySaveDocument(uri: UriComponents, options?: { encoding?: string }): Promise; } diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index 4f8b91e6c6bd2..1c844619afd90 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -99,7 +99,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { return promise; } - public createDocumentData(options?: { language?: string; content?: string }): Promise { + public createDocumentData(options?: { language?: string; content?: string; encoding?: string }): Promise { return this._proxy.$tryCreateDocument(options).then(data => URI.revive(data)); } diff --git a/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts b/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts index 17ec56d3a35a3..cbd67a9fe5ec4 100644 --- a/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts +++ b/src/vscode-dts/vscode.proposed.textDocumentEncoding.d.ts @@ -17,5 +17,6 @@ declare module 'vscode' { export namespace workspace { export function openTextDocument(uri: Uri, options?: { encoding?: string }): Thenable; export function openTextDocument(path: string, options?: { encoding?: string }): Thenable; + export function openTextDocument(options?: { language?: string; content?: string; encoding?: string }): Thenable; } } From 064f94ca2285836593453d55e625fd536d47d4bd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 09:36:38 +0100 Subject: [PATCH 15/24] what? --- .../vscode-api-tests/src/singlefolder-tests/workspace.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 3fac3601f2b1a..d190d1f9f5bc9 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -619,7 +619,6 @@ suite('vscode API - workspace', () => { test('findFiles2, exclude', () => { return vscode.workspace.findFiles2(['**/image.png'], { exclude: ['**/sub/**'] }).then((res) => { - res.forEach(r => console.log(r.toString())); assert.strictEqual(res.length, 1); }); }); From 5e9c85177b3d4b03388c98a4a9abb78cfeef60fa Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 09:48:42 +0100 Subject: [PATCH 16/24] some tests --- .../files/browser/editors/fileEditorInput.ts | 6 +++++- .../files/test/browser/fileEditorInput.test.ts | 3 ++- .../test/browser/textFileEditorModel.test.ts | 13 +++++++++---- .../test/browser/untitledTextEditor.test.ts | 9 ++++++--- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts index cb812694c96c5..fddc0e5ed7bdc 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts @@ -256,7 +256,11 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements async setEncoding(encoding: string, mode: EncodingMode): Promise { this.setPreferredEncoding(encoding); - return this.model?.setEncoding(encoding, mode) ?? false; + if (this.model) { + return this.model.setEncoding(encoding, mode); + } + + return true; } setPreferredEncoding(encoding: string): void { diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index e79fd5edec802..c7e80ed549dbf 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -235,7 +235,8 @@ suite('Files - FileEditorInput', () => { test('getEncoding/setEncoding', async function () { const input = createFileInput(toResource.call(this, '/foo/bar/updatefile.js')); - await input.setEncoding('utf16', EncodingMode.Encode); + const res = await input.setEncoding('utf16', EncodingMode.Encode); + assert.strictEqual(res, true); assert.strictEqual(input.getEncoding(), 'utf16'); const resolved = disposables.add(await input.resolve() as TextFileEditorModel); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index 2a7acd660f7c6..87419f1a190a2 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -292,12 +292,15 @@ suite('Files - TextFileEditorModel', () => { let encodingEvent = false; disposables.add(model.onDidChangeEncoding(() => encodingEvent = true)); - await model.setEncoding('utf8', EncodingMode.Encode); // no-op + let res = await model.setEncoding('utf8', EncodingMode.Encode); // no-op + assert.strictEqual(res, false); assert.strictEqual(getLastModifiedTime(model), -1); assert.ok(!encodingEvent); - await model.setEncoding('utf16', EncodingMode.Encode); + await model.resolve(); + res = await model.setEncoding('utf16', EncodingMode.Encode); // expect model to be saved + assert.strictEqual(res, true); assert.ok(encodingEvent); @@ -308,7 +311,8 @@ suite('Files - TextFileEditorModel', () => { let model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); accessor.workingCopyService.testUnregisterWorkingCopy(model); // causes issues with subsequent resolves otherwise - await model.setEncoding('utf16', EncodingMode.Decode); + const res = await model.setEncoding('utf16', EncodingMode.Decode); + assert.strictEqual(res, true); // expect the model to be saved // we have to get the model again from working copy service // because `setEncoding` will resolve it again through the @@ -327,7 +331,8 @@ suite('Files - TextFileEditorModel', () => { model.updateTextEditorModel(createTextBufferFactory('bar')); assert.strictEqual(model.isDirty(), true); - await model.setEncoding('utf16', EncodingMode.Decode); + const res = await model.setEncoding('utf16', EncodingMode.Decode); + assert.strictEqual(res, true); assert.strictEqual(model.isDirty(), false); }); diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index 31562d151c63b..f4447f84750fb 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -388,7 +388,8 @@ suite('Untitled text editors', () => { // encoding const model = disposables.add(await input.resolve()); - await model.setEncoding('utf16'); + const res = await model.setEncoding('utf16'); + assert.strictEqual(res, true); assert.strictEqual(counter, 1); }); @@ -585,10 +586,12 @@ suite('Untitled text editors', () => { const model = disposables.add(await input.resolve()); disposables.add(model.onDidChangeEncoding(() => counter++)); - await model.setEncoding('utf16'); + let res = await model.setEncoding('utf16'); + assert.strictEqual(res, true); assert.strictEqual(counter, 1, 'Dirty model should trigger event'); - await model.setEncoding('utf16'); + res = await model.setEncoding('utf16'); + assert.strictEqual(res, true); assert.strictEqual(counter, 1, 'Another change to same encoding does not fire event'); }); From fe29d6a7e96580f8d08138d95d6ca49e79b54146 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 09:59:42 +0100 Subject: [PATCH 17/24] cleanup --- .../textfile/browser/textFileService.ts | 6 ++---- .../textfile/common/textFileEditorModel.ts | 7 ++++--- .../test/browser/textFileService.test.ts | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 1238c8594a89c..9b3b2aa04ecb8 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -833,10 +833,8 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { private async getValidatedEncodingForResource(resource: URI, preferredEncoding?: string): Promise { let fileEncoding = this.getUnvalidatedEncodingForResource(resource, preferredEncoding); - if (fileEncoding !== UTF8) { - if (!(await encodingExists(fileEncoding))) { - fileEncoding = UTF8; - } + if (fileEncoding !== UTF8 && !(await encodingExists(fileEncoding))) { + fileEncoding = UTF8; } return fileEncoding; diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 95870ea789996..daa2fe1f96044 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -1140,7 +1140,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return false; } - return await this.save({ source: TextFileEditorModel.TEXTFILE_SAVE_ENCODING_SOURCE }); + return this.save({ source: TextFileEditorModel.TEXTFILE_SAVE_ENCODING_SOURCE }); } // Decode: Resolve with encoding @@ -1149,15 +1149,16 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return true; // return early if the encoding is already the same } + let saved = undefined; if (this.isDirty() && !this.inConflictMode) { - await this.save(); + saved = await this.save(); } this.updatePreferredEncoding(encoding); await this.forceResolveFromFile(); - return true; + return saved === false ? false : true; // signal back if model was saved } } diff --git a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts index 18290d14a8571..b27c93663a794 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts @@ -10,6 +10,7 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com import { TextFileEditorModel } from '../../common/textFileEditorModel.js'; import { FileOperation } from '../../../../../platform/files/common/files.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { EncodingMode } from '../../common/textfiles.js'; suite('Files - TextFileService', () => { @@ -199,5 +200,22 @@ suite('Files - TextFileService', () => { assert.strictEqual(suggested, 'plumbus'); }); + test('getEncoding() - files and untitled', async function () { + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined)); + (accessor.textFileService.files).add(model.resource, model); + + await model.resolve(); + + assert.strictEqual(accessor.textFileService.getEncoding(model.resource), 'utf8'); + await model.setEncoding('utf16', EncodingMode.Encode); + assert.strictEqual(accessor.textFileService.getEncoding(model.resource), 'utf16'); + + const untitled = disposables.add(await accessor.textFileService.untitled.resolve()); + + assert.strictEqual(accessor.textFileService.getEncoding(untitled.resource), 'utf8'); + await untitled.setEncoding('utf16'); + assert.strictEqual(accessor.textFileService.getEncoding(untitled.resource), 'utf16'); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); From 312fbe2f4d9f04755ac1bacf7b0128356e436e22 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 10:13:05 +0100 Subject: [PATCH 18/24] check proposed --- src/vs/workbench/api/common/extHost.api.impl.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 2e1208189b85c..1f7a8a086d1a8 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1025,10 +1025,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I throw new errors.ReadonlyError('textDocuments'); }, openTextDocument(uriOrFileNameOrOptions?: vscode.Uri | string | { language?: string; content?: string; encoding?: string }, options?: { encoding?: string }) { - // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); let uriPromise: Thenable; options = options ?? uriOrFileNameOrOptions as { language?: string; content?: string; encoding?: string }; + if (typeof options.encoding === 'string') { + checkProposedApiEnabled(extension, 'textDocumentEncoding'); + } + if (typeof uriOrFileNameOrOptions === 'string') { uriPromise = Promise.resolve(URI.file(uriOrFileNameOrOptions)); } else if (URI.isUri(uriOrFileNameOrOptions)) { From c63debce8c2dbcc786916d8c62f3680d495f47b8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 10:14:05 +0100 Subject: [PATCH 19/24] add propo --- extensions/vscode-api-tests/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 0d7f87a9be49e..66a80b8867f86 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -44,6 +44,7 @@ "terminalDataWriteEvent", "terminalDimensions", "testObserver", + "textDocumentEncoding", "textSearchProvider", "timeline", "tokenInformation", From 3cf278ec95e2a4ec8a7b504dcbf210d02efaea02 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 10:17:22 +0100 Subject: [PATCH 20/24] . --- src/vs/workbench/api/browser/mainThreadDocuments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index edfadcd8c3ee6..47988b7185062 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -290,7 +290,7 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen // don't create a new file ontop of an existing file return Promise.reject(new Error('file already exists')); } - return await this._doCreateUntitled(Boolean(uri.path) ? uri : undefined); + return await this._doCreateUntitled(Boolean(uri.path) ? uri : undefined, undefined, undefined, options?.encoding); } private async _doCreateUntitled(associatedResource?: URI, languageId?: string, initialValue?: string, encoding?: string): Promise { From 80fed79b8f681deb70a8dac0560d74e8cd56ca3e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 10:29:30 +0100 Subject: [PATCH 21/24] more fixes --- .../api/browser/mainThreadDocuments.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 47988b7185062..1b644a4c71015 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -218,14 +218,11 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen const inputUri = URI.revive(uri); if (options?.encoding) { - const target = uri.scheme === Schemas.untitled ? this._textFileService.untitled.get(inputUri) : this._textFileService.files.get(inputUri); - const result = await target?.setEncoding(options.encoding, EncodingMode.Encode); - if (!result) { - return false; - } + const model = uri.scheme === Schemas.untitled ? this._textFileService.untitled.get(inputUri) : this._textFileService.files.get(inputUri); + const result = await model?.setEncoding(options.encoding, EncodingMode.Encode); if (uri.scheme !== Schemas.untitled) { - return result; // non untitled files get saved right away + return Boolean(result); // non untitled files get saved right away } } @@ -270,12 +267,13 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen } $tryCreateDocument(options?: { language?: string; content?: string; encoding?: string }): Promise { - return this._doCreateUntitled(undefined, options ? options.language : undefined, options ? options.content : undefined, options?.encoding); + return this._doCreateUntitled(undefined, options); } - private async _handleAsResourceInput(uri: URI, options: { encoding?: string } | undefined): Promise { + private async _handleAsResourceInput(uri: URI, options?: { encoding?: string }): Promise { if (options?.encoding) { - await this._textFileService.files.resolve(uri, { encoding: options?.encoding }); + const model = await this._textFileService.files.resolve(uri); + await model.setEncoding(options.encoding, EncodingMode.Decode); } const ref = await this._textModelResolverService.createModelReference(uri); @@ -283,22 +281,22 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen return ref.object.textEditorModel.uri; } - private async _handleUntitledScheme(uri: URI, options: { encoding?: string } | undefined): Promise { + private async _handleUntitledScheme(uri: URI, options?: { encoding?: string }): Promise { const asLocalUri = toLocalResource(uri, this._environmentService.remoteAuthority, this._pathService.defaultUriScheme); const exists = await this._fileService.exists(asLocalUri); if (exists) { // don't create a new file ontop of an existing file return Promise.reject(new Error('file already exists')); } - return await this._doCreateUntitled(Boolean(uri.path) ? uri : undefined, undefined, undefined, options?.encoding); + return await this._doCreateUntitled(Boolean(uri.path) ? uri : undefined, options); } - private async _doCreateUntitled(associatedResource?: URI, languageId?: string, initialValue?: string, encoding?: string): Promise { + private async _doCreateUntitled(associatedResource?: URI, options?: { language?: string; content?: string; encoding?: string }): Promise { const model = this._textFileService.untitled.create({ associatedResource, - languageId, - initialValue, - encoding + languageId: options?.language, + initialValue: options?.content, + encoding: options?.encoding }); const resource = model.resource; const ref = await this._textModelResolverService.createModelReference(resource); From fca29c48bfb37131665572abfc631a510ae5ce7e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 10:54:22 +0100 Subject: [PATCH 22/24] add tests --- .../src/singlefolder-tests/workspace.test.ts | 60 +++++++++++++++++++ .../workbench/api/common/extHostDocuments.ts | 4 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index d190d1f9f5bc9..f9990f6783391 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -1304,4 +1304,64 @@ suite('vscode API - workspace', () => { disposeAll(disposables); return deleteFile(file); } + + test('text document encodings', async () => { + const uri1 = await createRandomFile(); + const uri2 = await createRandomFile(); + + // --- events + const disposables: vscode.Disposable[] = []; + const onDidChangeTextDocument = new Set(); + disposables.push(vscode.workspace.onDidChangeTextDocument(e => { + onDidChangeTextDocument.add(e.document); + })); + + // --- create with encoding + + const doc1 = await vscode.workspace.openTextDocument(uri1); + assert.strictEqual(doc1.encoding, 'utf8'); + + const doc2 = await vscode.workspace.openTextDocument(uri2, { encoding: 'utf16le' }); + assert.strictEqual(doc2.encoding, 'utf16le'); + + const doc3 = await vscode.workspace.openTextDocument({ encoding: 'utf16be' }); + assert.strictEqual(doc3.encoding, 'utf16be'); + + const doc4 = await vscode.workspace.openTextDocument(vscode.Uri.parse('untitled://foo/bar'), { encoding: 'cp1252' }); + assert.strictEqual(doc4.encoding, 'cp1252'); + + // --- encode (save with encoding) + onDidChangeTextDocument.clear(); + let res = await doc1.save({ encoding: 'utf16le' }); + assert.strictEqual(res, true); + assert.ok(Array.from(onDidChangeTextDocument).find(e => e.uri.toString() === doc1.uri.toString()), 'did Change: ' + doc1.uri.toString()); + res = await doc1.save({ encoding: 'utf16le' }); + assert.strictEqual(res, true); + assert.strictEqual(doc1.encoding, 'utf16le'); + + // --- decode (reopen with encoding) + + const textDocumentChangeEvent = function (uri: vscode.Uri) { + return new Promise(resolve => { + vscode.workspace.onDidChangeTextDocument(e => { + if (e.document.uri.toString() === uri.toString()) { + resolve(e); + } + }); + }); + }; + + let event = textDocumentChangeEvent(uri2); + const doc2Reopened = await vscode.workspace.openTextDocument(uri2, { encoding: 'cp1252' }); + assert.strictEqual(doc2, doc2Reopened); + assert.strictEqual(doc2.encoding, 'cp1252'); + await event; + + event = textDocumentChangeEvent(uri2); + await vscode.workspace.openTextDocument(uri2, { encoding: 'utf8' }); + assert.strictEqual(doc2.encoding, 'utf8'); + await event; + + disposeAll(disposables); + }); }); diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index 1c844619afd90..40e5e3876e446 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -78,8 +78,8 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { public ensureDocumentData(uri: URI, options?: { encoding?: string }): Promise { - const cached = this._documentsAndEditors.getDocument(uri); // TODO cache per encoding? - if (cached) { + const cached = this._documentsAndEditors.getDocument(uri); + if (cached && (!options?.encoding || cached.document.encoding === options.encoding)) { return Promise.resolve(cached); } From 0c0373a0ad72937c96299b12a0c0d6450fcc1be9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 11:12:28 +0100 Subject: [PATCH 23/24] cleanup --- src/vs/workbench/api/common/extHostDocumentData.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index 2c5173b727ed0..81e5413c26438 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -67,14 +67,8 @@ export class ExtHostDocumentData extends MirrorTextModel { get version() { return that._versionId; }, get isClosed() { return that._isDisposed; }, get isDirty() { return that._isDirty; }, - get encoding() { - // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); - return that._encoding; - }, - save(options?: { encoding?: string }) { - // TODO: checkProposedApiEnabled(that._extension, 'textDocumentEncoding'); - return that._save(options); - }, + get encoding() { return that._encoding; }, + save(options?: { encoding?: string }) { return that._save(options); }, getText(range?) { return range ? that._getTextInRange(range) : that.getText(); }, get eol() { return that._eol === '\n' ? EndOfLine.LF : EndOfLine.CRLF; }, get lineCount() { return that._lines.length; }, From 156597abb9de3d8f768245506405b9cdce93c246 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 14 Feb 2025 11:36:53 +0100 Subject: [PATCH 24/24] fix tests --- src/vs/workbench/api/common/extHost.api.impl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1f7a8a086d1a8..b1b717b155412 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1027,8 +1027,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I openTextDocument(uriOrFileNameOrOptions?: vscode.Uri | string | { language?: string; content?: string; encoding?: string }, options?: { encoding?: string }) { let uriPromise: Thenable; - options = options ?? uriOrFileNameOrOptions as { language?: string; content?: string; encoding?: string }; - if (typeof options.encoding === 'string') { + options = (options ?? uriOrFileNameOrOptions) as ({ language?: string; content?: string; encoding?: string } | undefined); + if (typeof options?.encoding === 'string') { checkProposedApiEnabled(extension, 'textDocumentEncoding'); }