Skip to content

Commit e0d15bd

Browse files
authored
Remove deleted files from open editor tabs (#2386)
* Extract out next active editor tab index logic * Add REMOVE_EDITOR_TAB_FOR_FILE action * Add tests for REMOVE_EDITOR_TAB_FOR_FILE action * Remove corresponding open editor tab when deleting files if it exists * Add REMOVE_EDITOR_TABS_FOR_DIRECTORY action * Add tests for REMOVE_EDITOR_TABS_FOR_DIRECTORY action * Remove corresponding open editor tabs when deleting directories
1 parent 93ec868 commit e0d15bd

File tree

7 files changed

+505
-24
lines changed

7 files changed

+505
-24
lines changed

src/commons/fileSystemView/FileSystemViewDirectoryNode.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { IconNames } from '@blueprintjs/icons';
33
import { FSModule } from 'browserfs/dist/node/core/FS';
44
import path from 'path';
55
import React from 'react';
6+
import { useDispatch } from 'react-redux';
67

78
import { showSimpleConfirmDialog, showSimpleErrorDialog } from '../utils/DialogHelper';
89
import { rmdirRecursively } from '../utils/FileSystemUtils';
10+
import { removeEditorTabsForDirectory } from '../workspace/WorkspaceActions';
911
import { WorkspaceLocation } from '../workspace/WorkspaceTypes';
1012
import FileSystemViewContextMenu from './FileSystemViewContextMenu';
1113
import FileSystemViewFileName from './FileSystemViewFileName';
@@ -40,6 +42,7 @@ const FileSystemViewDirectoryNode: React.FC<FileSystemViewDirectoryNodeProps> =
4042
const [isAddingNewFile, setIsAddingNewFile] = React.useState<boolean>(false);
4143
const [isAddingNewDirectory, setIsAddingNewDirectory] = React.useState<boolean>(false);
4244
const [fileSystemViewListKey, setFileSystemViewListKey] = React.useState<number>(0);
45+
const dispatch = useDispatch();
4346

4447
const toggleIsExpanded = () => {
4548
if (isEditing) {
@@ -77,7 +80,7 @@ const FileSystemViewDirectoryNode: React.FC<FileSystemViewDirectoryNodeProps> =
7780
return;
7881
}
7982

80-
const fullPath = path.join(basePath, directoryName);
83+
dispatch(removeEditorTabsForDirectory(workspaceLocation, fullPath));
8184
rmdirRecursively(fileSystem, fullPath).then(refreshParentDirectory);
8285
});
8386
};

src/commons/fileSystemView/FileSystemViewFileNode.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import React from 'react';
66
import { useDispatch } from 'react-redux';
77

88
import { showSimpleConfirmDialog } from '../utils/DialogHelper';
9-
import { addEditorTab } from '../workspace/WorkspaceActions';
9+
import { addEditorTab, removeEditorTabForFile } from '../workspace/WorkspaceActions';
1010
import { WorkspaceLocation } from '../workspace/WorkspaceTypes';
1111
import FileSystemViewContextMenu from './FileSystemViewContextMenu';
1212
import FileSystemViewFileName from './FileSystemViewFileName';
@@ -71,6 +71,7 @@ const FileSystemViewFileNode: React.FC<FileSystemViewFileNodeProps> = (
7171
console.error(err);
7272
}
7373

74+
dispatch(removeEditorTabForFile(workspaceLocation, fullPath));
7475
refreshDirectory();
7576
});
7677
});

src/commons/workspace/WorkspaceActions.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
PLAYGROUND_EXTERNAL_SELECT,
3636
PROMPT_AUTOCOMPLETE,
3737
REMOVE_EDITOR_TAB,
38+
REMOVE_EDITOR_TAB_FOR_FILE,
39+
REMOVE_EDITOR_TABS_FOR_DIRECTORY,
3840
RESET_TESTCASE,
3941
RESET_WORKSPACE,
4042
SEND_REPL_INPUT_TO_OUTPUT,
@@ -219,6 +221,16 @@ export const shiftEditorTab = (
219221
export const removeEditorTab = (workspaceLocation: WorkspaceLocation, editorTabIndex: number) =>
220222
action(REMOVE_EDITOR_TAB, { workspaceLocation, editorTabIndex });
221223

224+
export const removeEditorTabForFile = (
225+
workspaceLocation: WorkspaceLocation,
226+
removedFilePath: string
227+
) => action(REMOVE_EDITOR_TAB_FOR_FILE, { workspaceLocation, removedFilePath });
228+
229+
export const removeEditorTabsForDirectory = (
230+
workspaceLocation: WorkspaceLocation,
231+
removedDirectoryPath: string
232+
) => action(REMOVE_EDITOR_TABS_FOR_DIRECTORY, { workspaceLocation, removedDirectoryPath });
233+
222234
export const updateReplValue = (newReplValue: string, workspaceLocation: WorkspaceLocation) =>
223235
action(UPDATE_REPL_VALUE, { newReplValue, workspaceLocation });
224236

src/commons/workspace/WorkspaceReducer.ts

Lines changed: 102 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ import {
4848
EVAL_REPL,
4949
MOVE_CURSOR,
5050
REMOVE_EDITOR_TAB,
51+
REMOVE_EDITOR_TAB_FOR_FILE,
52+
REMOVE_EDITOR_TABS_FOR_DIRECTORY,
5153
RESET_TESTCASE,
5254
RESET_WORKSPACE,
5355
SEND_REPL_INPUT_TO_OUTPUT,
@@ -815,28 +817,78 @@ export const WorkspaceReducer: Reducer<WorkspaceManagerState> = (
815817
);
816818

817819
const activeEditorTabIndex = state[workspaceLocation].activeEditorTabIndex;
818-
const newActiveEditorTabIndex =
819-
activeEditorTabIndex !== editorTabIndex
820-
? // If the active editor tab is not the one that is removed,
821-
// the active editor tab remains the same if its index is
822-
// less than the removed editor tab index or null.
823-
activeEditorTabIndex === null || activeEditorTabIndex < editorTabIndex
824-
? activeEditorTabIndex
825-
: // Otherwise, the active editor tab index needs to have 1
826-
// subtracted because every tab to the right of the editor
827-
// tab being removed has their index decremented by 1.
828-
activeEditorTabIndex - 1
829-
: newEditorTabs.length === 0
830-
? // If there are no editor tabs after removal, there cannot
831-
// be an active editor tab.
832-
null
833-
: editorTabIndex === 0
834-
? // If the removed editor tab is the leftmost tab, the active
835-
// editor tab will be the new leftmost tab.
836-
0
837-
: // Otherwise, the active editor tab will be the tab to the
838-
// left of the removed tab.
839-
editorTabIndex - 1;
820+
const newActiveEditorTabIndex = getNextActiveEditorTabIndexAfterTabRemoval(
821+
activeEditorTabIndex,
822+
editorTabIndex,
823+
newEditorTabs.length
824+
);
825+
826+
return {
827+
...state,
828+
[workspaceLocation]: {
829+
...state[workspaceLocation],
830+
activeEditorTabIndex: newActiveEditorTabIndex,
831+
editorTabs: newEditorTabs
832+
}
833+
};
834+
}
835+
case REMOVE_EDITOR_TAB_FOR_FILE: {
836+
const removedFilePath = action.payload.removedFilePath;
837+
838+
const editorTabs = state[workspaceLocation].editorTabs;
839+
const editorTabIndexToRemove = editorTabs.findIndex(
840+
(editorTab: EditorTabState) => editorTab.filePath === removedFilePath
841+
);
842+
if (editorTabIndexToRemove === -1) {
843+
return state;
844+
}
845+
const newEditorTabs = editorTabs.filter(
846+
(editorTab: EditorTabState, index: number) => index !== editorTabIndexToRemove
847+
);
848+
849+
const activeEditorTabIndex = state[workspaceLocation].activeEditorTabIndex;
850+
const newActiveEditorTabIndex = getNextActiveEditorTabIndexAfterTabRemoval(
851+
activeEditorTabIndex,
852+
editorTabIndexToRemove,
853+
newEditorTabs.length
854+
);
855+
856+
return {
857+
...state,
858+
[workspaceLocation]: {
859+
...state[workspaceLocation],
860+
activeEditorTabIndex: newActiveEditorTabIndex,
861+
editorTabs: newEditorTabs
862+
}
863+
};
864+
}
865+
case REMOVE_EDITOR_TABS_FOR_DIRECTORY: {
866+
const removedDirectoryPath = action.payload.removedDirectoryPath;
867+
868+
const editorTabs = state[workspaceLocation].editorTabs;
869+
const editorTabIndicesToRemove = editorTabs
870+
.map((editorTab: EditorTabState, index: number) => {
871+
if (editorTab.filePath?.startsWith(removedDirectoryPath)) {
872+
return index;
873+
}
874+
return null;
875+
})
876+
.filter((index: number | null): index is number => index !== null);
877+
if (editorTabIndicesToRemove.length === 0) {
878+
return state;
879+
}
880+
881+
let newActiveEditorTabIndex = state[workspaceLocation].activeEditorTabIndex;
882+
const newEditorTabs = [...editorTabs];
883+
for (let i = editorTabIndicesToRemove.length - 1; i >= 0; i--) {
884+
const editorTabIndexToRemove = editorTabIndicesToRemove[i];
885+
newEditorTabs.splice(editorTabIndexToRemove, 1);
886+
newActiveEditorTabIndex = getNextActiveEditorTabIndexAfterTabRemoval(
887+
newActiveEditorTabIndex,
888+
editorTabIndexToRemove,
889+
newEditorTabs.length
890+
);
891+
}
840892

841893
return {
842894
...state,
@@ -894,3 +946,31 @@ export const WorkspaceReducer: Reducer<WorkspaceManagerState> = (
894946
return state;
895947
}
896948
};
949+
950+
const getNextActiveEditorTabIndexAfterTabRemoval = (
951+
activeEditorTabIndex: number | null,
952+
removedEditorTabIndex: number,
953+
newEditorTabsLength: number
954+
) => {
955+
return activeEditorTabIndex !== removedEditorTabIndex
956+
? // If the active editor tab is not the one that is removed,
957+
// the active editor tab remains the same if its index is
958+
// less than the removed editor tab index or null.
959+
activeEditorTabIndex === null || activeEditorTabIndex < removedEditorTabIndex
960+
? activeEditorTabIndex
961+
: // Otherwise, the active editor tab index needs to have 1
962+
// subtracted because every tab to the right of the editor
963+
// tab being removed has their index decremented by 1.
964+
activeEditorTabIndex - 1
965+
: newEditorTabsLength === 0
966+
? // If there are no editor tabs after removal, there cannot
967+
// be an active editor tab.
968+
null
969+
: removedEditorTabIndex === 0
970+
? // If the removed editor tab is the leftmost tab, the active
971+
// editor tab will be the new leftmost tab.
972+
0
973+
: // Otherwise, the active editor tab will be the tab to the
974+
// left of the removed tab.
975+
removedEditorTabIndex - 1;
976+
};

src/commons/workspace/WorkspaceTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export const UPDATE_EDITOR_BREAKPOINTS = 'UPDATE_EDITOR_BREAKPOINTS';
4545
export const ADD_EDITOR_TAB = 'ADD_EDITOR_TAB';
4646
export const SHIFT_EDITOR_TAB = 'SHIFT_EDITOR_TAB';
4747
export const REMOVE_EDITOR_TAB = 'REMOVE_EDITOR_TAB';
48+
export const REMOVE_EDITOR_TAB_FOR_FILE = 'REMOVE_EDITOR_TAB_FOR_FILE';
49+
export const REMOVE_EDITOR_TABS_FOR_DIRECTORY = 'REMOVE_EDITOR_TABS_FOR_DIRECTORY';
4850
export const UPDATE_HAS_UNSAVED_CHANGES = 'UPDATE_HAS_UNSAVED_CHANGES';
4951
export const UPDATE_REPL_VALUE = 'UPDATE_REPL_VALUE';
5052
export const UPDATE_WORKSPACE = 'UPDATE_WORKSPACE';

src/commons/workspace/__tests__/WorkspaceActions.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import {
2525
moveCursor,
2626
navigateToDeclaration,
2727
removeEditorTab,
28+
removeEditorTabForFile,
29+
removeEditorTabsForDirectory,
2830
resetTestcase,
2931
resetWorkspace,
3032
sendReplInputToOutput,
@@ -64,6 +66,8 @@ import {
6466
NAV_DECLARATION,
6567
PLAYGROUND_EXTERNAL_SELECT,
6668
REMOVE_EDITOR_TAB,
69+
REMOVE_EDITOR_TAB_FOR_FILE,
70+
REMOVE_EDITOR_TABS_FOR_DIRECTORY,
6771
RESET_TESTCASE,
6872
RESET_WORKSPACE,
6973
SEND_REPL_INPUT_TO_OUTPUT,
@@ -404,6 +408,30 @@ test('removeEditorTab generates correct action object', () => {
404408
});
405409
});
406410

411+
test('removeEditorTabForFile generates correct action object', () => {
412+
const removedFilePath = '/dir1/a.js';
413+
const action = removeEditorTabForFile(playgroundWorkspace, removedFilePath);
414+
expect(action).toEqual({
415+
type: REMOVE_EDITOR_TAB_FOR_FILE,
416+
payload: {
417+
workspaceLocation: playgroundWorkspace,
418+
removedFilePath
419+
}
420+
});
421+
});
422+
423+
test('removeEditorTabsForDirectory generates correct action object', () => {
424+
const removedDirectoryPath = '/dir1';
425+
const action = removeEditorTabsForDirectory(playgroundWorkspace, removedDirectoryPath);
426+
expect(action).toEqual({
427+
type: REMOVE_EDITOR_TABS_FOR_DIRECTORY,
428+
payload: {
429+
workspaceLocation: playgroundWorkspace,
430+
removedDirectoryPath
431+
}
432+
});
433+
});
434+
407435
test('updateReplValue generates correct action object', () => {
408436
const newReplValue = 'new_repl_value';
409437
const action = updateReplValue(newReplValue, assessmentWorkspace);

0 commit comments

Comments
 (0)