mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
save participants - provide access to the from
resource in "Save As" scenarios (#203516)
This commit is contained in:
parent
b26b05031e
commit
0e7e31b4be
|
@ -58,7 +58,7 @@ else {
|
|||
// Running out of sources
|
||||
if (Object.keys(product).length === 0) {
|
||||
Object.assign(product, {
|
||||
version: '1.82.0-dev',
|
||||
version: '1.87.0-dev',
|
||||
nameShort: 'Code - OSS Dev',
|
||||
nameLong: 'Code - OSS Dev',
|
||||
applicationName: 'code-oss',
|
||||
|
|
|
@ -8,11 +8,10 @@ import { localize } from 'vs/nls';
|
|||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress';
|
||||
import { extHostCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { SaveReason } from 'vs/workbench/common/editor';
|
||||
import { ExtHostContext, ExtHostNotebookDocumentSaveParticipantShape } from '../common/extHost.protocol';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { raceCancellationError } from 'vs/base/common/async';
|
||||
import { IStoredFileWorkingCopySaveParticipant, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IStoredFileWorkingCopySaveParticipant, IStoredFileWorkingCopySaveParticipantContext, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IStoredFileWorkingCopy, IStoredFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy';
|
||||
import { NotebookFileWorkingCopyModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
|
||||
|
||||
|
@ -24,7 +23,7 @@ class ExtHostNotebookDocumentSaveParticipant implements IStoredFileWorkingCopySa
|
|||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebookDocumentSaveParticipant);
|
||||
}
|
||||
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, env: { reason: SaveReason }, _progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: IStoredFileWorkingCopySaveParticipantContext, _progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
|
||||
if (!workingCopy.model || !(workingCopy.model instanceof NotebookFileWorkingCopyModel)) {
|
||||
return undefined;
|
||||
|
@ -38,7 +37,7 @@ class ExtHostNotebookDocumentSaveParticipant implements IStoredFileWorkingCopySa
|
|||
() => reject(new Error(localize('timeout.onWillSave', "Aborted onWillSaveNotebookDocument-event after 1750ms"))),
|
||||
1750
|
||||
);
|
||||
this._proxy.$participateInSave(workingCopy.resource, env.reason, token).then(_ => {
|
||||
this._proxy.$participateInSave(workingCopy.resource, context.reason, token).then(_ => {
|
||||
clearTimeout(_warningTimeout);
|
||||
return undefined;
|
||||
}).then(resolve, reject);
|
||||
|
|
|
@ -9,8 +9,7 @@ import { localize } from 'vs/nls';
|
|||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress';
|
||||
import { extHostCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { ITextFileSaveParticipant, ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { SaveReason } from 'vs/workbench/common/editor';
|
||||
import { ITextFileSaveParticipant, ITextFileService, ITextFileEditorModel, ITextFileSaveParticipantContext } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ExtHostContext, ExtHostDocumentSaveParticipantShape } from '../common/extHost.protocol';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { raceCancellationError } from 'vs/base/common/async';
|
||||
|
@ -23,7 +22,7 @@ class ExtHostSaveParticipant implements ITextFileSaveParticipant {
|
|||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant);
|
||||
}
|
||||
|
||||
async participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }, _progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
async participate(editorModel: ITextFileEditorModel, context: ITextFileSaveParticipantContext, _progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
|
||||
if (!editorModel.textEditorModel || !shouldSynchronizeModel(editorModel.textEditorModel)) {
|
||||
// the model never made it to the extension
|
||||
|
@ -37,7 +36,7 @@ class ExtHostSaveParticipant implements ITextFileSaveParticipant {
|
|||
() => reject(new Error(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms"))),
|
||||
1750
|
||||
);
|
||||
this._proxy.$participateInSave(editorModel.resource, env.reason).then(values => {
|
||||
this._proxy.$participateInSave(editorModel.resource, context.reason).then(values => {
|
||||
if (!values.every(success => success)) {
|
||||
return Promise.reject(new Error('listener failed'));
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as
|
|||
import { SaveReason } from 'vs/workbench/common/editor';
|
||||
import { getModifiedRanges } from 'vs/workbench/contrib/format/browser/formatModified';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { ITextFileEditorModel, ITextFileSaveParticipant, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileEditorModel, ITextFileSaveParticipant, ITextFileSaveParticipantContext, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
|
||||
export class TrimWhitespaceParticipant implements ITextFileSaveParticipant {
|
||||
|
||||
|
@ -41,13 +41,13 @@ export class TrimWhitespaceParticipant implements ITextFileSaveParticipant {
|
|||
// Nothing
|
||||
}
|
||||
|
||||
async participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
|
||||
async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext): Promise<void> {
|
||||
if (!model.textEditorModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageId(), resource: model.resource })) {
|
||||
this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO);
|
||||
this.doTrimTrailingWhitespace(model.textEditorModel, context.reason === SaveReason.AUTO);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ export class FinalNewLineParticipant implements ITextFileSaveParticipant {
|
|||
// Nothing
|
||||
}
|
||||
|
||||
async participate(model: ITextFileEditorModel, _env: { reason: SaveReason }): Promise<void> {
|
||||
async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext): Promise<void> {
|
||||
if (!model.textEditorModel) {
|
||||
return;
|
||||
}
|
||||
|
@ -145,13 +145,13 @@ export class TrimFinalNewLinesParticipant implements ITextFileSaveParticipant {
|
|||
// Nothing
|
||||
}
|
||||
|
||||
async participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise<void> {
|
||||
async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext): Promise<void> {
|
||||
if (!model.textEditorModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageId(), resource: model.resource })) {
|
||||
this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO);
|
||||
this.doTrimFinalNewLines(model.textEditorModel, context.reason === SaveReason.AUTO);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,11 +217,11 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant {
|
|||
// Nothing
|
||||
}
|
||||
|
||||
async participate(model: ITextFileEditorModel, env: { reason: SaveReason }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
if (!model.textEditorModel) {
|
||||
return;
|
||||
}
|
||||
if (env.reason === SaveReason.AUTO) {
|
||||
if (context.reason === SaveReason.AUTO) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -272,7 +272,7 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant {
|
|||
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
|
||||
) { }
|
||||
|
||||
async participate(model: ITextFileEditorModel, env: { reason: SaveReason }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
async participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
if (!model.textEditorModel) {
|
||||
return;
|
||||
}
|
||||
|
@ -286,11 +286,11 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
if (env.reason === SaveReason.AUTO) {
|
||||
if (context.reason === SaveReason.AUTO) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (env.reason !== SaveReason.EXPLICIT && Array.isArray(setting)) {
|
||||
if (context.reason !== SaveReason.EXPLICIT && Array.isArray(setting)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -326,7 +326,7 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant {
|
|||
|
||||
progress.report({ message: localize('codeaction', "Quick Fixes") });
|
||||
|
||||
const filteredSaveList = Array.isArray(setting) ? codeActionsOnSave : codeActionsOnSave.filter(x => setting[x.value] === 'always' || ((setting[x.value] === 'explicit' || setting[x.value] === true) && env.reason === SaveReason.EXPLICIT));
|
||||
const filteredSaveList = Array.isArray(setting) ? codeActionsOnSave : codeActionsOnSave.filter(x => setting[x.value] === 'always' || ((setting[x.value] === 'explicit' || setting[x.value] === true) && context.reason === SaveReason.EXPLICIT));
|
||||
|
||||
await this.applyOnSaveActions(textEditorModel, filteredSaveList, excludedActions, progress, token);
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ import { NotebookFileWorkingCopyModel } from 'vs/workbench/contrib/notebook/comm
|
|||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IStoredFileWorkingCopy, IStoredFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy';
|
||||
import { IStoredFileWorkingCopySaveParticipant, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IStoredFileWorkingCopySaveParticipant, IStoredFileWorkingCopySaveParticipantContext, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
|
||||
class FormatOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant {
|
||||
constructor(
|
||||
|
@ -47,7 +47,7 @@ class FormatOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant {
|
|||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) { }
|
||||
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: { reason: SaveReason }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
if (!workingCopy.model || !(workingCopy.model instanceof NotebookFileWorkingCopyModel)) {
|
||||
return;
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ class TrimWhitespaceParticipant implements IStoredFileWorkingCopySaveParticipant
|
|||
@IBulkEditService private readonly bulkEditService: IBulkEditService,
|
||||
) { }
|
||||
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: { reason: SaveReason }, progress: IProgress<IProgressStep>, _token: CancellationToken): Promise<void> {
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress<IProgressStep>, _token: CancellationToken): Promise<void> {
|
||||
if (this.configurationService.getValue<boolean>('files.trimTrailingWhitespace')) {
|
||||
await this.doTrimTrailingWhitespace(workingCopy, context.reason === SaveReason.AUTO, progress);
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ class TrimFinalNewLinesParticipant implements IStoredFileWorkingCopySaveParticip
|
|||
@IBulkEditService private readonly bulkEditService: IBulkEditService,
|
||||
) { }
|
||||
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: { reason: SaveReason }, progress: IProgress<IProgressStep>, _token: CancellationToken): Promise<void> {
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress<IProgressStep>, _token: CancellationToken): Promise<void> {
|
||||
if (this.configurationService.getValue<boolean>('files.trimFinalNewlines')) {
|
||||
await this.doTrimFinalNewLines(workingCopy, context.reason === SaveReason.AUTO, progress);
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ class FinalNewLineParticipant implements IStoredFileWorkingCopySaveParticipant {
|
|||
@IEditorService private readonly editorService: IEditorService,
|
||||
) { }
|
||||
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: { reason: SaveReason }, progress: IProgress<IProgressStep>, _token: CancellationToken): Promise<void> {
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress<IProgressStep>, _token: CancellationToken): Promise<void> {
|
||||
// waiting on notebook-specific override before this feature can sync with 'files.insertFinalNewline'
|
||||
// if (this.configurationService.getValue('files.insertFinalNewline')) {
|
||||
|
||||
|
@ -317,7 +317,7 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa
|
|||
) {
|
||||
}
|
||||
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: { reason: SaveReason }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
async participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
|
||||
const nbDisposable = new DisposableStore();
|
||||
const isTrusted = this.workspaceTrustManagementService.isWorkspaceTrusted();
|
||||
if (!isTrusted) {
|
||||
|
|
|
@ -560,7 +560,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
|
|||
}
|
||||
|
||||
// save model
|
||||
return targetModel.save(options);
|
||||
return targetModel.save({
|
||||
...options,
|
||||
from: source
|
||||
});
|
||||
}
|
||||
|
||||
private async confirmOverwrite(resource: URI): Promise<boolean> {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event';
|
|||
import { URI } from 'vs/base/common/uri';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { EncodingMode, ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileResolveOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, TextFileResolveReason, ITextFileEditorModelSaveEvent } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { EncodingMode, ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileResolveOptions, IResolvedTextFileEditorModel, TextFileResolveReason, ITextFileEditorModelSaveEvent, ITextFileSaveAsOptions } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IRevertOptions, SaveReason, SaveSourceRegistry } from 'vs/workbench/common/editor';
|
||||
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
|
||||
import { IWorkingCopyBackupService, IResolvedWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
|
||||
|
@ -723,7 +723,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
|
||||
//#region Save
|
||||
|
||||
async save(options: ITextFileSaveOptions = Object.create(null)): Promise<boolean> {
|
||||
async save(options: ITextFileSaveAsOptions = Object.create(null)): Promise<boolean> {
|
||||
if (!this.isResolved()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -751,7 +751,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
return this.hasState(TextFileEditorModelState.SAVED);
|
||||
}
|
||||
|
||||
private async doSave(options: ITextFileSaveOptions): Promise<void> {
|
||||
private async doSave(options: ITextFileSaveAsOptions): Promise<void> {
|
||||
if (typeof options.reason !== 'number') {
|
||||
options.reason = SaveReason.EXPLICIT;
|
||||
}
|
||||
|
@ -852,7 +852,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
if (!saveCancellation.token.isCancellationRequested) {
|
||||
this.ignoreSaveFromSaveParticipants = true;
|
||||
try {
|
||||
await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token);
|
||||
await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT, savedFrom: options.from }, saveCancellation.token);
|
||||
} finally {
|
||||
this.ignoreSaveFromSaveParticipants = false;
|
||||
}
|
||||
|
@ -919,7 +919,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
})(), () => saveCancellation.cancel());
|
||||
}
|
||||
|
||||
private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveOptions): void {
|
||||
private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveAsOptions): void {
|
||||
|
||||
// Updated resolved stat with updated stat
|
||||
this.updateLastResolvedFileStat(stat);
|
||||
|
@ -939,7 +939,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
this._onDidSave.fire({ reason: options.reason, stat, source: options.source });
|
||||
}
|
||||
|
||||
private handleSaveError(error: Error, versionId: number, options: ITextFileSaveOptions): void {
|
||||
private handleSaveError(error: Error, versionId: number, options: ITextFileSaveAsOptions): void {
|
||||
(options.ignoreErrorHandler ? this.logService.trace : this.logService.error).apply(this.logService, [`[text file model] handleSaveError(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString()]);
|
||||
|
||||
// Return early if the save() call was made asking to
|
||||
|
|
|
@ -16,10 +16,9 @@ import { IFileService, FileChangesEvent, FileOperation, FileChangeType, IFileSys
|
|||
import { Promises, ResourceQueue } from 'vs/base/common/async';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { TextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textFileSaveParticipant';
|
||||
import { SaveReason } from 'vs/workbench/common/editor';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IWorkingCopyFileService, WorkingCopyFileEvent } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IStoredFileWorkingCopySaveParticipantContext, IWorkingCopyFileService, WorkingCopyFileEvent } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { ITextSnapshot } from 'vs/editor/common/model';
|
||||
import { extname, joinPath } from 'vs/base/common/resources';
|
||||
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
|
||||
|
@ -536,7 +535,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
|||
return this.saveParticipants.addSaveParticipant(participant);
|
||||
}
|
||||
|
||||
runSaveParticipants(model: ITextFileEditorModel, context: { reason: SaveReason }, token: CancellationToken): Promise<void> {
|
||||
runSaveParticipants(model: ITextFileEditorModel, context: IStoredFileWorkingCopySaveParticipantContext, token: CancellationToken): Promise<void> {
|
||||
return this.saveParticipants.participate(model, context, token);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,7 @@ import { raceCancellation } from 'vs/base/common/async';
|
|||
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { ITextFileSaveParticipant, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { SaveReason } from 'vs/workbench/common/editor';
|
||||
import { ITextFileSaveParticipant, ITextFileEditorModel, ITextFileSaveParticipantContext } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { insert } from 'vs/base/common/arrays';
|
||||
|
||||
|
@ -30,7 +29,7 @@ export class TextFileSaveParticipant extends Disposable {
|
|||
return toDisposable(() => remove());
|
||||
}
|
||||
|
||||
participate(model: ITextFileEditorModel, context: { reason: SaveReason }, token: CancellationToken): Promise<void> {
|
||||
participate(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext, token: CancellationToken): Promise<void> {
|
||||
const cts = new CancellationTokenSource(token);
|
||||
|
||||
return this.progressService.withProgress({
|
||||
|
|
|
@ -313,6 +313,22 @@ export interface ITextFileResolveEvent {
|
|||
readonly reason: TextFileResolveReason;
|
||||
}
|
||||
|
||||
export interface ITextFileSaveParticipantContext {
|
||||
|
||||
/**
|
||||
* The reason why the save was triggered.
|
||||
*/
|
||||
readonly reason: SaveReason;
|
||||
|
||||
/**
|
||||
* Only applies to when a text file was saved as, for
|
||||
* example when starting with untitled and saving. This
|
||||
* provides access to the initial resource the text
|
||||
* file had before.
|
||||
*/
|
||||
readonly savedFrom?: URI;
|
||||
}
|
||||
|
||||
export interface ITextFileSaveParticipant {
|
||||
|
||||
/**
|
||||
|
@ -321,7 +337,7 @@ export interface ITextFileSaveParticipant {
|
|||
*/
|
||||
participate(
|
||||
model: ITextFileEditorModel,
|
||||
context: { reason: SaveReason },
|
||||
context: ITextFileSaveParticipantContext,
|
||||
progress: IProgress<IProgressStep>,
|
||||
token: CancellationToken
|
||||
): Promise<void>;
|
||||
|
@ -369,7 +385,7 @@ export interface ITextFileEditorModelManager {
|
|||
/**
|
||||
* Runs the registered save participants on the provided model.
|
||||
*/
|
||||
runSaveParticipants(model: ITextFileEditorModel, context: { reason: SaveReason }, token: CancellationToken): Promise<void>;
|
||||
runSaveParticipants(model: ITextFileEditorModel, context: ITextFileSaveParticipantContext, token: CancellationToken): Promise<void>;
|
||||
|
||||
/**
|
||||
* Waits for the model to be ready to be disposed. There may be conditions
|
||||
|
@ -406,6 +422,11 @@ export interface ITextFileSaveOptions extends ISaveOptions {
|
|||
|
||||
export interface ITextFileSaveAsOptions extends ITextFileSaveOptions {
|
||||
|
||||
/**
|
||||
* Optional URI of the resource the text file is saved from if known.
|
||||
*/
|
||||
readonly from?: URI;
|
||||
|
||||
/**
|
||||
* Optional URI to use as suggested file path to save as.
|
||||
*/
|
||||
|
@ -498,7 +519,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
|
|||
|
||||
updatePreferredEncoding(encoding: string | undefined): void;
|
||||
|
||||
save(options?: ITextFileSaveOptions): Promise<boolean>;
|
||||
save(options?: ITextFileSaveAsOptions): Promise<boolean>;
|
||||
revert(options?: IRevertOptions): Promise<void>;
|
||||
|
||||
resolve(options?: ITextFileResolveOptions): Promise<void>;
|
||||
|
|
|
@ -19,6 +19,7 @@ import { SaveReason, SaveSourceRegistry } from 'vs/workbench/common/editor';
|
|||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { UTF16be } from 'vs/workbench/services/textfile/common/encoding';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
suite('Files - TextFileEditorModel', () => {
|
||||
|
||||
|
@ -849,5 +850,31 @@ suite('Files - TextFileEditorModel', () => {
|
|||
await savePromise;
|
||||
}
|
||||
|
||||
test('Save Participant carries context', async function () {
|
||||
const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined));
|
||||
|
||||
const from = URI.file('testFrom');
|
||||
let e: Error | undefined = undefined;
|
||||
disposables.add(accessor.textFileService.files.addSaveParticipant({
|
||||
participate: async (wc, context) => {
|
||||
try {
|
||||
assert.strictEqual(context.reason, SaveReason.EXPLICIT);
|
||||
assert.strictEqual(context.savedFrom?.toString(), from.toString());
|
||||
} catch (error) {
|
||||
e = error;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
await model.resolve();
|
||||
model.updateTextEditorModel(createTextBufferFactory('foo'));
|
||||
|
||||
await model.save({ force: true, from });
|
||||
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
});
|
||||
|
|
|
@ -429,7 +429,11 @@ export class FileWorkingCopyManager<S extends IStoredFileWorkingCopyModel, U ext
|
|||
}
|
||||
|
||||
// Save target
|
||||
const success = await targetStoredFileWorkingCopy.save({ ...options, force: true /* force to save, even if not dirty (https://github.com/microsoft/vscode/issues/99619) */ });
|
||||
const success = await targetStoredFileWorkingCopy.save({
|
||||
...options,
|
||||
from: source,
|
||||
force: true /* force to save, even if not dirty (https://github.com/microsoft/vscode/issues/99619) */
|
||||
});
|
||||
if (!success) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -156,6 +156,15 @@ export interface IStoredFileWorkingCopy<M extends IStoredFileWorkingCopyModel> e
|
|||
* Whether the stored file working copy is readonly or not.
|
||||
*/
|
||||
isReadonly(): boolean | IMarkdownString;
|
||||
|
||||
/**
|
||||
* Asks the stored file working copy to save. If the stored file
|
||||
* working copy was dirty, it is expected to be non-dirty after
|
||||
* this operation has finished.
|
||||
*
|
||||
* @returns `true` if the operation was successful and `false` otherwise.
|
||||
*/
|
||||
save(options?: IStoredFileWorkingCopySaveAsOptions): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface IResolvedStoredFileWorkingCopy<M extends IStoredFileWorkingCopyModel> extends IStoredFileWorkingCopy<M> {
|
||||
|
@ -236,6 +245,14 @@ export interface IStoredFileWorkingCopySaveOptions extends ISaveOptions {
|
|||
readonly ignoreErrorHandler?: boolean;
|
||||
}
|
||||
|
||||
export interface IStoredFileWorkingCopySaveAsOptions extends IStoredFileWorkingCopySaveOptions {
|
||||
|
||||
/**
|
||||
* Optional URI of the resource the text file is saved from if known.
|
||||
*/
|
||||
readonly from?: URI;
|
||||
}
|
||||
|
||||
export interface IStoredFileWorkingCopyResolver {
|
||||
|
||||
/**
|
||||
|
@ -815,7 +832,7 @@ export class StoredFileWorkingCopy<M extends IStoredFileWorkingCopyModel> extend
|
|||
|
||||
private ignoreSaveFromSaveParticipants = false;
|
||||
|
||||
async save(options: IStoredFileWorkingCopySaveOptions = Object.create(null)): Promise<boolean> {
|
||||
async save(options: IStoredFileWorkingCopySaveAsOptions = Object.create(null)): Promise<boolean> {
|
||||
if (!this.isResolved()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -843,7 +860,7 @@ export class StoredFileWorkingCopy<M extends IStoredFileWorkingCopyModel> extend
|
|||
return this.hasState(StoredFileWorkingCopyState.SAVED);
|
||||
}
|
||||
|
||||
private async doSave(options: IStoredFileWorkingCopySaveOptions): Promise<void> {
|
||||
private async doSave(options: IStoredFileWorkingCopySaveAsOptions): Promise<void> {
|
||||
if (typeof options.reason !== 'number') {
|
||||
options.reason = SaveReason.EXPLICIT;
|
||||
}
|
||||
|
@ -947,7 +964,7 @@ export class StoredFileWorkingCopy<M extends IStoredFileWorkingCopyModel> extend
|
|||
if (!saveCancellation.token.isCancellationRequested) {
|
||||
this.ignoreSaveFromSaveParticipants = true;
|
||||
try {
|
||||
await this.workingCopyFileService.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token);
|
||||
await this.workingCopyFileService.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT, savedFrom: options.from }, saveCancellation.token);
|
||||
} finally {
|
||||
this.ignoreSaveFromSaveParticipants = false;
|
||||
}
|
||||
|
@ -1039,7 +1056,7 @@ export class StoredFileWorkingCopy<M extends IStoredFileWorkingCopyModel> extend
|
|||
})(), () => saveCancellation.cancel());
|
||||
}
|
||||
|
||||
private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: IStoredFileWorkingCopySaveOptions): void {
|
||||
private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: IStoredFileWorkingCopySaveAsOptions): void {
|
||||
|
||||
// Updated resolved stat with updated stat
|
||||
this.updateLastResolvedFileStat(stat);
|
||||
|
@ -1059,7 +1076,7 @@ export class StoredFileWorkingCopy<M extends IStoredFileWorkingCopyModel> extend
|
|||
this._onDidSave.fire({ reason: options.reason, stat, source: options.source });
|
||||
}
|
||||
|
||||
private handleSaveError(error: Error, versionId: number, options: IStoredFileWorkingCopySaveOptions): void {
|
||||
private handleSaveError(error: Error, versionId: number, options: IStoredFileWorkingCopySaveAsOptions): void {
|
||||
(options.ignoreErrorHandler ? this.logService.trace : this.logService.error).apply(this.logService, [`[stored file working copy] handleSaveError(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString(), this.typeId]);
|
||||
|
||||
// Return early if the save() call was made asking to
|
||||
|
|
|
@ -8,10 +8,9 @@ import { raceCancellation } from 'vs/base/common/async';
|
|||
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { SaveReason } from 'vs/workbench/common/editor';
|
||||
import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { insert } from 'vs/base/common/arrays';
|
||||
import { IStoredFileWorkingCopySaveParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IStoredFileWorkingCopySaveParticipant, IStoredFileWorkingCopySaveParticipantContext } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IStoredFileWorkingCopy, IStoredFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy';
|
||||
|
||||
export class StoredFileWorkingCopySaveParticipant extends Disposable {
|
||||
|
@ -33,7 +32,7 @@ export class StoredFileWorkingCopySaveParticipant extends Disposable {
|
|||
return toDisposable(() => remove());
|
||||
}
|
||||
|
||||
participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: { reason: SaveReason }, token: CancellationToken): Promise<void> {
|
||||
participate(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: IStoredFileWorkingCopySaveParticipantContext, token: CancellationToken): Promise<void> {
|
||||
const cts = new CancellationTokenSource(token);
|
||||
|
||||
return this.progressService.withProgress({
|
||||
|
|
|
@ -84,6 +84,21 @@ export interface IWorkingCopyFileOperationParticipant {
|
|||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IStoredFileWorkingCopySaveParticipantContext {
|
||||
/**
|
||||
* The reason why the save was triggered.
|
||||
*/
|
||||
readonly reason: SaveReason;
|
||||
|
||||
/**
|
||||
* Only applies to when a text file was saved as, for
|
||||
* example when starting with untitled and saving. This
|
||||
* provides access to the initial resource the text
|
||||
* file had before.
|
||||
*/
|
||||
readonly savedFrom?: URI;
|
||||
}
|
||||
|
||||
export interface IStoredFileWorkingCopySaveParticipant {
|
||||
|
||||
/**
|
||||
|
@ -92,7 +107,7 @@ export interface IStoredFileWorkingCopySaveParticipant {
|
|||
*/
|
||||
participate(
|
||||
workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>,
|
||||
context: { reason: SaveReason },
|
||||
context: IStoredFileWorkingCopySaveParticipantContext,
|
||||
progress: IProgress<IProgressStep>,
|
||||
token: CancellationToken
|
||||
): Promise<void>;
|
||||
|
@ -191,7 +206,7 @@ export interface IWorkingCopyFileService {
|
|||
/**
|
||||
* Runs all available save participants for stored file working copies.
|
||||
*/
|
||||
runSaveParticipants(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: { reason: SaveReason }, token: CancellationToken): Promise<void>;
|
||||
runSaveParticipants(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: IStoredFileWorkingCopySaveParticipantContext, token: CancellationToken): Promise<void>;
|
||||
|
||||
//#endregion
|
||||
|
||||
|
@ -492,7 +507,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi
|
|||
return this.saveParticipants.addSaveParticipant(participant);
|
||||
}
|
||||
|
||||
runSaveParticipants(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: { reason: SaveReason }, token: CancellationToken): Promise<void> {
|
||||
runSaveParticipants(workingCopy: IStoredFileWorkingCopy<IStoredFileWorkingCopyModel>, context: IStoredFileWorkingCopySaveParticipantContext, token: CancellationToken): Promise<void> {
|
||||
return this.saveParticipants.participate(workingCopy, context, token);
|
||||
}
|
||||
|
||||
|
|
|
@ -879,11 +879,12 @@ suite('StoredFileWorkingCopy', function () {
|
|||
});
|
||||
|
||||
async function testSaveFromSaveParticipant(workingCopy: StoredFileWorkingCopy<TestStoredFileWorkingCopyModel>, async: boolean): Promise<void> {
|
||||
|
||||
const from = URI.file('testFrom');
|
||||
assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, false);
|
||||
|
||||
const disposable = accessor.workingCopyFileService.addSaveParticipant({
|
||||
participate: async () => {
|
||||
participate: async (wc, context) => {
|
||||
|
||||
if (async) {
|
||||
await timeout(10);
|
||||
}
|
||||
|
@ -894,11 +895,40 @@ suite('StoredFileWorkingCopy', function () {
|
|||
|
||||
assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, true);
|
||||
|
||||
await workingCopy.save({ force: true });
|
||||
await workingCopy.save({ force: true, from });
|
||||
|
||||
disposable.dispose();
|
||||
}
|
||||
|
||||
test('Save Participant carries context', async function () {
|
||||
await workingCopy.resolve();
|
||||
|
||||
const from = URI.file('testFrom');
|
||||
assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, false);
|
||||
|
||||
let e: Error | undefined = undefined;
|
||||
const disposable = accessor.workingCopyFileService.addSaveParticipant({
|
||||
participate: async (wc, context) => {
|
||||
try {
|
||||
assert.strictEqual(context.reason, SaveReason.EXPLICIT);
|
||||
assert.strictEqual(context.savedFrom?.toString(), from.toString());
|
||||
} catch (error) {
|
||||
e = error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, true);
|
||||
|
||||
await workingCopy.save({ force: true, from });
|
||||
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
disposable.dispose();
|
||||
});
|
||||
|
||||
test('revert', async () => {
|
||||
await workingCopy.resolve();
|
||||
workingCopy.model?.updateContents('hello revert');
|
||||
|
|
|
@ -15,7 +15,7 @@ import { isLinux, isMacintosh } from 'vs/base/common/platform';
|
|||
import { InMemoryStorageService, WillSaveStateReason } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy';
|
||||
import { NullExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent, IDeleteOperation, ICopyOperation, IMoveOperation, IFileOperationUndoRedoInfo, ICreateFileOperation, ICreateOperation, IStoredFileWorkingCopySaveParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent, IDeleteOperation, ICopyOperation, IMoveOperation, IFileOperationUndoRedoInfo, ICreateFileOperation, ICreateOperation, IStoredFileWorkingCopySaveParticipant, IStoredFileWorkingCopySaveParticipantContext } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IBaseFileStat, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import { ISaveOptions, IRevertOptions, SaveReason, GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
|
@ -253,7 +253,7 @@ export class TestWorkingCopyFileService implements IWorkingCopyFileService {
|
|||
|
||||
readonly hasSaveParticipants = false;
|
||||
addSaveParticipant(participant: IStoredFileWorkingCopySaveParticipant): IDisposable { return Disposable.None; }
|
||||
async runSaveParticipants(workingCopy: IWorkingCopy, context: { reason: SaveReason }, token: CancellationToken): Promise<void> { }
|
||||
async runSaveParticipants(workingCopy: IWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, token: CancellationToken): Promise<void> { }
|
||||
|
||||
async delete(operations: IDeleteOperation[], token: CancellationToken, undoInfo?: IFileOperationUndoRedoInfo): Promise<void> { }
|
||||
|
||||
|
|
Loading…
Reference in a new issue