Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions src/disablesave.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { Notification } from '@jupyterlab/apputils';

const SAVE_MESSAGE = 'Autosaving is enabled, manual saves are not needed';

/**
* The command IDs for docmanager save operations to disable
*/
const SAVE_COMMANDS = {
save: 'docmanager:save',
saveAs: 'docmanager:save-as',
saveAll: 'docmanager:save-all',
toggleAutosave: 'docmanager:toggle-autosave'
} as const;

// Show the notification every 20 manual save operations
const NOTIFICATION_INTERVAL = 20;

/**
* Plugin to disable save commands
*/
export const disableSavePlugin: JupyterFrontEndPlugin<void> = {
id: 'disable-save:plugin',
description:
'Disables save commands and removes their keyboard shortcuts since documents are autosaved',
autoStart: true,
activate: (app: JupyterFrontEnd): void => {
let saveNotifiedCount = 0;
let saveAsNotifiedCount = 0;
let saveAllNotifiedCount = 0;
let toggleAutosaveNotifiedCount = 0;
/**
* Override save commands and remove keyboard shortcuts after app is fully loaded
*/
app.restored.then(() => {
// Helper function to remove existing command and add new one
const overrideCommand = (commandId: string, options: any) => {
if (app.commands.hasCommand(commandId)) {
// Remove existing command using private API
const commandRegistry = app.commands as any;
if (commandRegistry._commands && commandRegistry._commands.delete) {
commandRegistry._commands.delete(commandId);
}
app.commands.addCommand(commandId, options);
}
};

const notify = () => {
Notification.emit(SAVE_MESSAGE, 'info', {
autoClose: 2000
});
};

// Override main save command (Ctrl/Cmd+S)
overrideCommand(SAVE_COMMANDS.save, {
label: 'Save (Autosaving)',
caption: SAVE_MESSAGE,
isEnabled: () => true,
execute: () => {
if (saveNotifiedCount % NOTIFICATION_INTERVAL === 0) {
notify();
}
saveNotifiedCount++;
return Promise.resolve();
}
});

// Override save-as command (Ctrl/Cmd+Shift+S)
overrideCommand(SAVE_COMMANDS.saveAs, {
label: 'Save As… (Autosaving)',
caption: SAVE_MESSAGE,
isEnabled: () => true,
execute: () => {
if (saveAsNotifiedCount % NOTIFICATION_INTERVAL === 0) {
notify();
}
saveAsNotifiedCount++;
return Promise.resolve();
}
});

// Override save-all command
overrideCommand(SAVE_COMMANDS.saveAll, {
label: 'Save All (Autosaving)',
caption: SAVE_MESSAGE,
isEnabled: () => true,
execute: () => {
if (saveAllNotifiedCount % NOTIFICATION_INTERVAL === 0) {
notify();
}
saveAllNotifiedCount++;
return Promise.resolve();
}
});

// Override toggle autosave command
overrideCommand(SAVE_COMMANDS.toggleAutosave, {
label: 'Autosave Documents (Autosaving)',
caption: SAVE_MESSAGE,
isEnabled: () => true,
isToggled: () => true,
execute: () => {
if (toggleAutosaveNotifiedCount % NOTIFICATION_INTERVAL === 0) {
notify();
}
toggleAutosaveNotifiedCount++;
return Promise.resolve();
}
});

console.log('Full autosave enabled, save commands disabled');
});
}
};
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { AwarenessKernelStatus } from './kernelstatus';

import { codemirrorYjsPlugin } from './codemirror-binding/plugin';
import { notebookFactoryPlugin } from './notebook-factory';
import { disableSavePlugin } from './disablesave';

/**
* Initialization data for the @jupyter/server-documents extension.
Expand Down Expand Up @@ -315,7 +316,8 @@ const plugins: JupyterFrontEndPlugin<unknown>[] = [
kernelStatus,
notebookFactoryPlugin,
codemirrorYjsPlugin,
backupCellExecutorPlugin
backupCellExecutorPlugin,
disableSavePlugin
];

export default plugins;
Loading