@lvce-editor/main-area-worker 9.28.0 → 9.28.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mainAreaWorkerMain.js +173 -12
- package/package.json +1 -1
|
@@ -2295,7 +2295,21 @@ const closeActiveEditor = async state => {
|
|
|
2295
2295
|
return closeTabAndSave(state, focusedGroup.id, activeTabId);
|
|
2296
2296
|
};
|
|
2297
2297
|
|
|
2298
|
-
const
|
|
2298
|
+
const disposeEditors = async editorUids => {
|
|
2299
|
+
// Dispose all child viewlets in the renderer worker
|
|
2300
|
+
for (const editorUid of editorUids) {
|
|
2301
|
+
try {
|
|
2302
|
+
await invoke('Viewlet.dispose', editorUid);
|
|
2303
|
+
} catch {
|
|
2304
|
+
// Silently ignore errors during disposal
|
|
2305
|
+
// Viewlet may already be disposed or renderer worker may not be available
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2309
|
+
|
|
2310
|
+
const closeAll$1 = async state => {
|
|
2311
|
+
const editorUids = state.layout.groups.flatMap(group => group.tabs.map(tab => tab.editorUid)).filter(editorUid => editorUid !== -1);
|
|
2312
|
+
await disposeEditors(editorUids);
|
|
2299
2313
|
const entries = state.layout.groups.flatMap((group, groupIndex) => {
|
|
2300
2314
|
return group.tabs.map((tab, tabIndex) => ({
|
|
2301
2315
|
group,
|
|
@@ -2671,6 +2685,7 @@ const getEditorInputEditorType = editorInput => {
|
|
|
2671
2685
|
case 'process-explorer':
|
|
2672
2686
|
case 'running-extensions':
|
|
2673
2687
|
case 'video':
|
|
2688
|
+
case 'webview':
|
|
2674
2689
|
return 'custom';
|
|
2675
2690
|
case 'editor':
|
|
2676
2691
|
return 'text';
|
|
@@ -2684,6 +2699,7 @@ const getEditorInputUri = editorInput => {
|
|
|
2684
2699
|
case 'editor':
|
|
2685
2700
|
case 'image':
|
|
2686
2701
|
case 'video':
|
|
2702
|
+
case 'webview':
|
|
2687
2703
|
return editorInput.uri;
|
|
2688
2704
|
case 'extension-detail-view':
|
|
2689
2705
|
return `extension-detail://${editorInput.extensionId}`;
|
|
@@ -2788,6 +2804,9 @@ const getNormalizedEditorInput = tab => {
|
|
|
2788
2804
|
editorInput: tabEditorInput,
|
|
2789
2805
|
uri: tabUri
|
|
2790
2806
|
} = tab ?? {};
|
|
2807
|
+
if (tabEditorInput?.type === 'editor' && tabEditorInput.forceText || tabEditorInput?.type === 'webview') {
|
|
2808
|
+
return tabEditorInput;
|
|
2809
|
+
}
|
|
2791
2810
|
let uri;
|
|
2792
2811
|
if (typeof tabUri === 'string') {
|
|
2793
2812
|
uri = tabUri;
|
|
@@ -2916,11 +2935,11 @@ const getSelectedTabBounds = state => {
|
|
|
2916
2935
|
};
|
|
2917
2936
|
};
|
|
2918
2937
|
|
|
2919
|
-
const getViewletModuleId$1 = async uri => {
|
|
2938
|
+
const getViewletModuleId$1 = async (uri, opener) => {
|
|
2920
2939
|
// Query RendererWorker for viewlet module ID (optional, may fail in tests)
|
|
2921
2940
|
let viewletModuleId;
|
|
2922
2941
|
try {
|
|
2923
|
-
viewletModuleId = await invoke('Layout.getModuleId', uri);
|
|
2942
|
+
viewletModuleId = opener === undefined ? await invoke('Layout.getModuleId', uri) : await invoke('Layout.getModuleId', uri, opener);
|
|
2924
2943
|
} catch {
|
|
2925
2944
|
// Viewlet creation is optional - silently ignore if RendererWorker isn't available
|
|
2926
2945
|
}
|
|
@@ -2939,15 +2958,18 @@ const getViewletModuleIdForEditorInput = async editorInput => {
|
|
|
2939
2958
|
case 'diff-editor':
|
|
2940
2959
|
return DiffEditor;
|
|
2941
2960
|
case 'editor':
|
|
2961
|
+
return editorInput.forceText ? EditorText : getViewletModuleId$1(editorInput.uri);
|
|
2962
|
+
case 'extension-detail-view':
|
|
2963
|
+
return ExtensionDetail;
|
|
2942
2964
|
case 'image':
|
|
2943
2965
|
case 'video':
|
|
2944
2966
|
return getViewletModuleId$1(editorInput.uri);
|
|
2945
|
-
case 'extension-detail-view':
|
|
2946
|
-
return ExtensionDetail;
|
|
2947
2967
|
case 'process-explorer':
|
|
2948
2968
|
return ProcessExplorer;
|
|
2949
2969
|
case 'running-extensions':
|
|
2950
2970
|
return RunningExtensions;
|
|
2971
|
+
case 'webview':
|
|
2972
|
+
return getViewletModuleId$1(editorInput.uri, editorInput.providerId);
|
|
2951
2973
|
}
|
|
2952
2974
|
};
|
|
2953
2975
|
|
|
@@ -3496,6 +3518,7 @@ const getEditorInputTitle = editorInput => {
|
|
|
3496
3518
|
case 'editor':
|
|
3497
3519
|
case 'image':
|
|
3498
3520
|
case 'video':
|
|
3521
|
+
case 'webview':
|
|
3499
3522
|
return getLabel(editorInput.uri);
|
|
3500
3523
|
case 'extension-detail-view':
|
|
3501
3524
|
return editorInput.extensionId;
|
|
@@ -4060,8 +4083,11 @@ const getGroupSegments = (groups, parentDirection) => {
|
|
|
4060
4083
|
if (group.direction !== undefined && group.direction !== parentDirection) {
|
|
4061
4084
|
const startIndex = index;
|
|
4062
4085
|
const nestedGroups = [group];
|
|
4086
|
+
const {
|
|
4087
|
+
segmentId
|
|
4088
|
+
} = group;
|
|
4063
4089
|
index++;
|
|
4064
|
-
while (index < groups.length && groups[index].direction === group.direction) {
|
|
4090
|
+
while (index < groups.length && groups[index].direction === group.direction && groups[index].segmentId === segmentId) {
|
|
4065
4091
|
nestedGroups.push(groups[index]);
|
|
4066
4092
|
index++;
|
|
4067
4093
|
}
|
|
@@ -4153,6 +4179,7 @@ const splitWithinMatchingNestedDirection = (state, groups, sourceGroup, groupId,
|
|
|
4153
4179
|
const newGroup = {
|
|
4154
4180
|
...baseNewGroup,
|
|
4155
4181
|
direction: splitLayoutDirection,
|
|
4182
|
+
segmentId: sourceGroup.segmentId,
|
|
4156
4183
|
size: Number((sourceGroup.size / 2).toFixed(6))
|
|
4157
4184
|
};
|
|
4158
4185
|
const insertIndex = isTrailingSplit(direction) ? sourceIndex + 1 : sourceIndex;
|
|
@@ -4162,15 +4189,18 @@ const splitWithinMatchingNestedDirection = (state, groups, sourceGroup, groupId,
|
|
|
4162
4189
|
const splitStandaloneSourceIntoNestedDirection = (state, groups, sourceGroup, groupId, newGroupId, direction, splitLayoutDirection, baseNewGroup) => {
|
|
4163
4190
|
const sourceIndex = groups.findIndex(group => group.id === groupId);
|
|
4164
4191
|
const halfSize = Number((sourceGroup.size / 2).toFixed(6));
|
|
4192
|
+
const segmentId = sourceGroup.segmentId ?? sourceGroup.id;
|
|
4165
4193
|
const updatedSourceGroup = {
|
|
4166
4194
|
...sourceGroup,
|
|
4167
4195
|
direction: splitLayoutDirection,
|
|
4168
4196
|
focused: false,
|
|
4197
|
+
segmentId,
|
|
4169
4198
|
size: halfSize
|
|
4170
4199
|
};
|
|
4171
4200
|
const newGroup = {
|
|
4172
4201
|
...baseNewGroup,
|
|
4173
4202
|
direction: splitLayoutDirection,
|
|
4203
|
+
segmentId,
|
|
4174
4204
|
size: Number((sourceGroup.size - halfSize).toFixed(6))
|
|
4175
4205
|
};
|
|
4176
4206
|
const replacementGroups = isTrailingSplit(direction) ? [updatedSourceGroup, newGroup] : [newGroup, updatedSourceGroup];
|
|
@@ -4306,7 +4336,10 @@ const handleClickCloseTab = async (state, rawGroupIndex, rawIndex) => {
|
|
|
4306
4336
|
return closeTabAndSave(state, groupId, tabId);
|
|
4307
4337
|
};
|
|
4308
4338
|
|
|
4309
|
-
const handleClickTab = async (state, groupIndexRaw, indexRaw) => {
|
|
4339
|
+
const handleClickTab = async (state, groupIndexRaw, indexRaw, button = 0) => {
|
|
4340
|
+
if (button !== 0) {
|
|
4341
|
+
return state;
|
|
4342
|
+
}
|
|
4310
4343
|
if (!groupIndexRaw || !indexRaw) {
|
|
4311
4344
|
return state;
|
|
4312
4345
|
}
|
|
@@ -4836,7 +4869,7 @@ const isValidTab = tab => {
|
|
|
4836
4869
|
};
|
|
4837
4870
|
|
|
4838
4871
|
const isValidEditorGroup = group => {
|
|
4839
|
-
return group && typeof group.id === 'number' && Array.isArray(group.tabs) && group.tabs.every(isValidTab) && (group.activeTabId === undefined || typeof group.activeTabId === 'number') && (group.direction === undefined || isLayoutDirection(group.direction)) && typeof group.focused === 'boolean' && typeof group.size === 'number' && group.size > 0 && typeof group.isEmpty === 'boolean';
|
|
4872
|
+
return group && typeof group.id === 'number' && Array.isArray(group.tabs) && group.tabs.every(isValidTab) && (group.activeTabId === undefined || typeof group.activeTabId === 'number') && (group.direction === undefined || isLayoutDirection(group.direction)) && typeof group.focused === 'boolean' && (group.segmentId === undefined || typeof group.segmentId === 'number') && typeof group.size === 'number' && group.size > 0 && typeof group.isEmpty === 'boolean';
|
|
4840
4873
|
};
|
|
4841
4874
|
|
|
4842
4875
|
const isValidMainAreaLayout = layout => {
|
|
@@ -5072,6 +5105,24 @@ const handleWorkspaceChange = async (state, _workspacePath, savedState) => {
|
|
|
5072
5105
|
return loadContent(state, savedState);
|
|
5073
5106
|
};
|
|
5074
5107
|
|
|
5108
|
+
const handleWorkspaceRefresh = async (state, deletedUris = []) => {
|
|
5109
|
+
const deleted = new Set(deletedUris);
|
|
5110
|
+
let newState = state;
|
|
5111
|
+
for (const group of state.layout.groups) {
|
|
5112
|
+
for (const tab of group.tabs) {
|
|
5113
|
+
if (tab.editorInput?.type === 'editor' && deleted.has(tab.editorInput.uri)) {
|
|
5114
|
+
newState = await closeTabWithViewlet(newState, group.id, tab.id);
|
|
5115
|
+
}
|
|
5116
|
+
}
|
|
5117
|
+
}
|
|
5118
|
+
return newState;
|
|
5119
|
+
};
|
|
5120
|
+
const handleWorkspaceRefreshWithContext = async (context, deletedUris = []) => {
|
|
5121
|
+
const state = context.getState();
|
|
5122
|
+
const newState = await handleWorkspaceRefresh(state, deletedUris);
|
|
5123
|
+
await context.updateState(() => newState);
|
|
5124
|
+
};
|
|
5125
|
+
|
|
5075
5126
|
const hasActiveTextEditor = state => {
|
|
5076
5127
|
const activeTab = getActiveTab(state);
|
|
5077
5128
|
return activeTab?.tab.editorInput?.type === 'editor';
|
|
@@ -5203,7 +5254,7 @@ const copyPath = () => {
|
|
|
5203
5254
|
const copyRelativePath = () => {
|
|
5204
5255
|
return i18nString(CopyRelativePath);
|
|
5205
5256
|
};
|
|
5206
|
-
const reopenEditorWith = () => {
|
|
5257
|
+
const reopenEditorWith$1 = () => {
|
|
5207
5258
|
return i18nString(ReopenEditorWith);
|
|
5208
5259
|
};
|
|
5209
5260
|
const share = () => {
|
|
@@ -5370,10 +5421,10 @@ const getMenuEntries$1 = state => {
|
|
|
5370
5421
|
id: 'copyRelativePath',
|
|
5371
5422
|
label: copyRelativePath()
|
|
5372
5423
|
}, menuEntrySeparator, {
|
|
5373
|
-
command: '',
|
|
5424
|
+
command: 'Main.reopenEditorWith',
|
|
5374
5425
|
flags: None,
|
|
5375
5426
|
id: 'reopenEditorWith',
|
|
5376
|
-
label: reopenEditorWith()
|
|
5427
|
+
label: reopenEditorWith$1()
|
|
5377
5428
|
}, menuEntrySeparator, {
|
|
5378
5429
|
command: '',
|
|
5379
5430
|
flags: None,
|
|
@@ -6429,7 +6480,7 @@ const renderEventListeners = () => {
|
|
|
6429
6480
|
params: ['handleClickCloseTab', 'event.target.dataset.groupIndex', 'event.target.dataset.index']
|
|
6430
6481
|
}, {
|
|
6431
6482
|
name: HandleClickTab,
|
|
6432
|
-
params: ['handleClickTab', 'event.target.dataset.groupIndex', 'event.target.dataset.index']
|
|
6483
|
+
params: ['handleClickTab', 'event.target.dataset.groupIndex', 'event.target.dataset.index', Button$1]
|
|
6433
6484
|
}, {
|
|
6434
6485
|
name: HandleContextMenu,
|
|
6435
6486
|
params: ['handleContextMenu', 'event.target.dataset.groupId', ClientX, ClientY],
|
|
@@ -6458,6 +6509,101 @@ const renderEventListeners = () => {
|
|
|
6458
6509
|
}];
|
|
6459
6510
|
};
|
|
6460
6511
|
|
|
6512
|
+
const getViewProviderEntries = async uri => {
|
|
6513
|
+
const textEditorEntry = {
|
|
6514
|
+
id: 'editor',
|
|
6515
|
+
label: 'Text Editor',
|
|
6516
|
+
type: 'editor'
|
|
6517
|
+
};
|
|
6518
|
+
try {
|
|
6519
|
+
const providers = await invoke('WebView.getWebViews');
|
|
6520
|
+
const matchingProviders = providers.filter(provider => provider?.id && provider.selector?.some(selector => uri.endsWith(selector)));
|
|
6521
|
+
return [textEditorEntry, ...matchingProviders.map(provider => {
|
|
6522
|
+
const titleElement = provider.elements?.find(element => element.type === 'title');
|
|
6523
|
+
return {
|
|
6524
|
+
id: provider.id,
|
|
6525
|
+
label: provider.name || provider.title || titleElement?.value || provider.id,
|
|
6526
|
+
type: 'webview'
|
|
6527
|
+
};
|
|
6528
|
+
})];
|
|
6529
|
+
} catch {
|
|
6530
|
+
return [textEditorEntry];
|
|
6531
|
+
}
|
|
6532
|
+
};
|
|
6533
|
+
const reopenEditorWith = async context => {
|
|
6534
|
+
const initialState = context.getState();
|
|
6535
|
+
const {
|
|
6536
|
+
activeGroupId,
|
|
6537
|
+
groups
|
|
6538
|
+
} = initialState.layout;
|
|
6539
|
+
const activeGroup = groups.find(group => group.id === activeGroupId);
|
|
6540
|
+
const initialTab = activeGroup?.tabs.find(tab => tab.id === activeGroup.activeTabId);
|
|
6541
|
+
if (!initialTab?.uri) {
|
|
6542
|
+
return;
|
|
6543
|
+
}
|
|
6544
|
+
const entries = await getViewProviderEntries(initialTab.uri);
|
|
6545
|
+
const items = entries.map(entry => ({
|
|
6546
|
+
description: '',
|
|
6547
|
+
label: entry.label,
|
|
6548
|
+
value: entry
|
|
6549
|
+
}));
|
|
6550
|
+
const selected = await invoke('ExtensionHostQuickPick.showQuickPick', {
|
|
6551
|
+
items,
|
|
6552
|
+
placeholder: 'Select Editor'
|
|
6553
|
+
});
|
|
6554
|
+
if (!selected) {
|
|
6555
|
+
return;
|
|
6556
|
+
}
|
|
6557
|
+
const editorInput = selected.type === 'editor' ? {
|
|
6558
|
+
forceText: true,
|
|
6559
|
+
type: 'editor',
|
|
6560
|
+
uri: initialTab.uri
|
|
6561
|
+
} : {
|
|
6562
|
+
providerId: selected.id,
|
|
6563
|
+
type: 'webview',
|
|
6564
|
+
uri: initialTab.uri
|
|
6565
|
+
};
|
|
6566
|
+
const viewletModuleId = await getViewletModuleIdForEditorInput(editorInput);
|
|
6567
|
+
if (!viewletModuleId) {
|
|
6568
|
+
return;
|
|
6569
|
+
}
|
|
6570
|
+
const latestState = context.getState();
|
|
6571
|
+
const latestTab = findTabById(latestState, initialTab.id);
|
|
6572
|
+
if (!latestTab) {
|
|
6573
|
+
return;
|
|
6574
|
+
}
|
|
6575
|
+
const oldEditorUid = latestTab.tab.editorUid;
|
|
6576
|
+
const resetState = updateTab(latestState, initialTab.id, {
|
|
6577
|
+
editorInput,
|
|
6578
|
+
editorType: getEditorInputEditorType(editorInput),
|
|
6579
|
+
editorUid: -1,
|
|
6580
|
+
errorMessage: undefined,
|
|
6581
|
+
loadingState: undefined
|
|
6582
|
+
});
|
|
6583
|
+
const bounds = {
|
|
6584
|
+
height: resetState.height - resetState.tabHeight,
|
|
6585
|
+
width: resetState.width,
|
|
6586
|
+
x: resetState.x,
|
|
6587
|
+
y: resetState.y + resetState.tabHeight
|
|
6588
|
+
};
|
|
6589
|
+
const stateWithViewlet = createViewletForTab(resetState, initialTab.id);
|
|
6590
|
+
await context.updateState(() => stateWithViewlet);
|
|
6591
|
+
const tabWithViewlet = findTabById(stateWithViewlet, initialTab.id);
|
|
6592
|
+
if (!tabWithViewlet || tabWithViewlet.tab.editorUid === -1) {
|
|
6593
|
+
return;
|
|
6594
|
+
}
|
|
6595
|
+
const renderedTitle = await createViewlet(viewletModuleId, tabWithViewlet.tab.editorUid, initialTab.id, bounds, initialTab.uri);
|
|
6596
|
+
const readyState = handleViewletReady(context.getState(), tabWithViewlet.tab.editorUid, renderedTitle);
|
|
6597
|
+
await context.updateState(() => readyState);
|
|
6598
|
+
if (oldEditorUid !== -1) {
|
|
6599
|
+
try {
|
|
6600
|
+
await invoke('Viewlet.dispose', oldEditorUid);
|
|
6601
|
+
} catch {
|
|
6602
|
+
// Reopening should still proceed if the previous viewlet was already disposed.
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
};
|
|
6606
|
+
|
|
6461
6607
|
const getLatestStoredState = (uid, fallbackState, referenceTabId, referenceTabUri, allowMissingReference = false) => {
|
|
6462
6608
|
const stateFromStore = get(uid);
|
|
6463
6609
|
if (!stateFromStore) {
|
|
@@ -6627,6 +6773,7 @@ const setEditorLayout = (state, direction, slots) => {
|
|
|
6627
6773
|
...group,
|
|
6628
6774
|
direction: slot.direction,
|
|
6629
6775
|
focused,
|
|
6776
|
+
segmentId: slot.segmentId,
|
|
6630
6777
|
size: slot.size
|
|
6631
6778
|
};
|
|
6632
6779
|
});
|
|
@@ -6690,39 +6837,51 @@ const setEditorLayoutThreeRows = state => {
|
|
|
6690
6837
|
}]);
|
|
6691
6838
|
};
|
|
6692
6839
|
const setEditorLayoutGrid = state => {
|
|
6840
|
+
const leftSegmentId = create$2();
|
|
6841
|
+
const rightSegmentId = create$2();
|
|
6693
6842
|
return setEditorLayout(state, Horizontal, [{
|
|
6694
6843
|
direction: Vertical,
|
|
6844
|
+
segmentId: leftSegmentId,
|
|
6695
6845
|
size: 25
|
|
6696
6846
|
}, {
|
|
6697
6847
|
direction: Vertical,
|
|
6848
|
+
segmentId: leftSegmentId,
|
|
6698
6849
|
size: 25
|
|
6699
6850
|
}, {
|
|
6700
6851
|
direction: Vertical,
|
|
6852
|
+
segmentId: rightSegmentId,
|
|
6701
6853
|
size: 25
|
|
6702
6854
|
}, {
|
|
6703
6855
|
direction: Vertical,
|
|
6856
|
+
segmentId: rightSegmentId,
|
|
6704
6857
|
size: 25
|
|
6705
6858
|
}]);
|
|
6706
6859
|
};
|
|
6707
6860
|
const setEditorLayoutTwoRowsRight = state => {
|
|
6861
|
+
const segmentId = create$2();
|
|
6708
6862
|
return setEditorLayout(state, Horizontal, [{
|
|
6709
6863
|
size: 50
|
|
6710
6864
|
}, {
|
|
6711
6865
|
direction: Vertical,
|
|
6866
|
+
segmentId,
|
|
6712
6867
|
size: 25
|
|
6713
6868
|
}, {
|
|
6714
6869
|
direction: Vertical,
|
|
6870
|
+
segmentId,
|
|
6715
6871
|
size: 25
|
|
6716
6872
|
}]);
|
|
6717
6873
|
};
|
|
6718
6874
|
const setEditorLayoutTwoColumnsBottom = state => {
|
|
6875
|
+
const segmentId = create$2();
|
|
6719
6876
|
return setEditorLayout(state, Vertical, [{
|
|
6720
6877
|
size: 50
|
|
6721
6878
|
}, {
|
|
6722
6879
|
direction: Horizontal,
|
|
6880
|
+
segmentId,
|
|
6723
6881
|
size: 25
|
|
6724
6882
|
}, {
|
|
6725
6883
|
direction: Horizontal,
|
|
6884
|
+
segmentId,
|
|
6726
6885
|
size: 25
|
|
6727
6886
|
}]);
|
|
6728
6887
|
};
|
|
@@ -6928,6 +7087,7 @@ const commandMap = {
|
|
|
6928
7087
|
'MainArea.handleTabContextMenu': wrapSerialCommand(handleTabContextMenu),
|
|
6929
7088
|
'MainArea.handleUriChange': wrapSerialCommand(handleUriChange),
|
|
6930
7089
|
'MainArea.handleWorkspaceChange': wrapSerialCommand(handleWorkspaceChange),
|
|
7090
|
+
'MainArea.handleWorkspaceRefresh': wrapAsyncCommand(handleWorkspaceRefreshWithContext),
|
|
6931
7091
|
'MainArea.hasActiveTextEditor': wrapGetter(hasActiveTextEditor),
|
|
6932
7092
|
'MainArea.initialize': initialize,
|
|
6933
7093
|
'MainArea.loadContent': wrapSerialCommand(loadContent),
|
|
@@ -6940,6 +7100,7 @@ const commandMap = {
|
|
|
6940
7100
|
'MainArea.refresh': wrapSerialCommand(refresh),
|
|
6941
7101
|
'MainArea.render2': render2,
|
|
6942
7102
|
'MainArea.renderEventListeners': renderEventListeners,
|
|
7103
|
+
'MainArea.reopenEditorWith': wrapSerialAsyncCommand(reopenEditorWith),
|
|
6943
7104
|
'MainArea.resize': wrapGetter(handleResize),
|
|
6944
7105
|
'MainArea.restoreClosedTab': wrapSerialCommand(restoreClosedTab),
|
|
6945
7106
|
'MainArea.save': wrapSerialCommand(save),
|