Skip to content

Remove deleted files from open editor tabs #2386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 26, 2023
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
5 changes: 4 additions & 1 deletion src/commons/fileSystemView/FileSystemViewDirectoryNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { IconNames } from '@blueprintjs/icons';
import { FSModule } from 'browserfs/dist/node/core/FS';
import path from 'path';
import React from 'react';
import { useDispatch } from 'react-redux';

import { showSimpleConfirmDialog, showSimpleErrorDialog } from '../utils/DialogHelper';
import { rmdirRecursively } from '../utils/FileSystemUtils';
import { removeEditorTabsForDirectory } from '../workspace/WorkspaceActions';
import { WorkspaceLocation } from '../workspace/WorkspaceTypes';
import FileSystemViewContextMenu from './FileSystemViewContextMenu';
import FileSystemViewFileName from './FileSystemViewFileName';
Expand Down Expand Up @@ -40,6 +42,7 @@ const FileSystemViewDirectoryNode: React.FC<FileSystemViewDirectoryNodeProps> =
const [isAddingNewFile, setIsAddingNewFile] = React.useState<boolean>(false);
const [isAddingNewDirectory, setIsAddingNewDirectory] = React.useState<boolean>(false);
const [fileSystemViewListKey, setFileSystemViewListKey] = React.useState<number>(0);
const dispatch = useDispatch();

const toggleIsExpanded = () => {
if (isEditing) {
Expand Down Expand Up @@ -77,7 +80,7 @@ const FileSystemViewDirectoryNode: React.FC<FileSystemViewDirectoryNodeProps> =
return;
}

const fullPath = path.join(basePath, directoryName);
dispatch(removeEditorTabsForDirectory(workspaceLocation, fullPath));
rmdirRecursively(fileSystem, fullPath).then(refreshParentDirectory);
});
};
Expand Down
3 changes: 2 additions & 1 deletion src/commons/fileSystemView/FileSystemViewFileNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React from 'react';
import { useDispatch } from 'react-redux';

import { showSimpleConfirmDialog } from '../utils/DialogHelper';
import { addEditorTab } from '../workspace/WorkspaceActions';
import { addEditorTab, removeEditorTabForFile } from '../workspace/WorkspaceActions';
import { WorkspaceLocation } from '../workspace/WorkspaceTypes';
import FileSystemViewContextMenu from './FileSystemViewContextMenu';
import FileSystemViewFileName from './FileSystemViewFileName';
Expand Down Expand Up @@ -71,6 +71,7 @@ const FileSystemViewFileNode: React.FC<FileSystemViewFileNodeProps> = (
console.error(err);
}

dispatch(removeEditorTabForFile(workspaceLocation, fullPath));
refreshDirectory();
});
});
Expand Down
12 changes: 12 additions & 0 deletions src/commons/workspace/WorkspaceActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import {
PLAYGROUND_EXTERNAL_SELECT,
PROMPT_AUTOCOMPLETE,
REMOVE_EDITOR_TAB,
REMOVE_EDITOR_TAB_FOR_FILE,
REMOVE_EDITOR_TABS_FOR_DIRECTORY,
RESET_TESTCASE,
RESET_WORKSPACE,
SEND_REPL_INPUT_TO_OUTPUT,
Expand Down Expand Up @@ -219,6 +221,16 @@ export const shiftEditorTab = (
export const removeEditorTab = (workspaceLocation: WorkspaceLocation, editorTabIndex: number) =>
action(REMOVE_EDITOR_TAB, { workspaceLocation, editorTabIndex });

export const removeEditorTabForFile = (
workspaceLocation: WorkspaceLocation,
removedFilePath: string
) => action(REMOVE_EDITOR_TAB_FOR_FILE, { workspaceLocation, removedFilePath });

export const removeEditorTabsForDirectory = (
workspaceLocation: WorkspaceLocation,
removedDirectoryPath: string
) => action(REMOVE_EDITOR_TABS_FOR_DIRECTORY, { workspaceLocation, removedDirectoryPath });

export const updateReplValue = (newReplValue: string, workspaceLocation: WorkspaceLocation) =>
action(UPDATE_REPL_VALUE, { newReplValue, workspaceLocation });

Expand Down
124 changes: 102 additions & 22 deletions src/commons/workspace/WorkspaceReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import {
EVAL_REPL,
MOVE_CURSOR,
REMOVE_EDITOR_TAB,
REMOVE_EDITOR_TAB_FOR_FILE,
REMOVE_EDITOR_TABS_FOR_DIRECTORY,
RESET_TESTCASE,
RESET_WORKSPACE,
SEND_REPL_INPUT_TO_OUTPUT,
Expand Down Expand Up @@ -815,28 +817,78 @@ export const WorkspaceReducer: Reducer<WorkspaceManagerState> = (
);

const activeEditorTabIndex = state[workspaceLocation].activeEditorTabIndex;
const newActiveEditorTabIndex =
activeEditorTabIndex !== editorTabIndex
? // If the active editor tab is not the one that is removed,
// the active editor tab remains the same if its index is
// less than the removed editor tab index or null.
activeEditorTabIndex === null || activeEditorTabIndex < editorTabIndex
? activeEditorTabIndex
: // Otherwise, the active editor tab index needs to have 1
// subtracted because every tab to the right of the editor
// tab being removed has their index decremented by 1.
activeEditorTabIndex - 1
: newEditorTabs.length === 0
? // If there are no editor tabs after removal, there cannot
// be an active editor tab.
null
: editorTabIndex === 0
? // If the removed editor tab is the leftmost tab, the active
// editor tab will be the new leftmost tab.
0
: // Otherwise, the active editor tab will be the tab to the
// left of the removed tab.
editorTabIndex - 1;
const newActiveEditorTabIndex = getNextActiveEditorTabIndexAfterTabRemoval(
activeEditorTabIndex,
editorTabIndex,
newEditorTabs.length
);

return {
...state,
[workspaceLocation]: {
...state[workspaceLocation],
activeEditorTabIndex: newActiveEditorTabIndex,
editorTabs: newEditorTabs
}
};
}
case REMOVE_EDITOR_TAB_FOR_FILE: {
const removedFilePath = action.payload.removedFilePath;

const editorTabs = state[workspaceLocation].editorTabs;
const editorTabIndexToRemove = editorTabs.findIndex(
(editorTab: EditorTabState) => editorTab.filePath === removedFilePath
);
if (editorTabIndexToRemove === -1) {
return state;
}
const newEditorTabs = editorTabs.filter(
(editorTab: EditorTabState, index: number) => index !== editorTabIndexToRemove
);

const activeEditorTabIndex = state[workspaceLocation].activeEditorTabIndex;
const newActiveEditorTabIndex = getNextActiveEditorTabIndexAfterTabRemoval(
activeEditorTabIndex,
editorTabIndexToRemove,
newEditorTabs.length
);

return {
...state,
[workspaceLocation]: {
...state[workspaceLocation],
activeEditorTabIndex: newActiveEditorTabIndex,
editorTabs: newEditorTabs
}
};
}
case REMOVE_EDITOR_TABS_FOR_DIRECTORY: {
const removedDirectoryPath = action.payload.removedDirectoryPath;

const editorTabs = state[workspaceLocation].editorTabs;
const editorTabIndicesToRemove = editorTabs
.map((editorTab: EditorTabState, index: number) => {
if (editorTab.filePath?.startsWith(removedDirectoryPath)) {
return index;
}
return null;
})
.filter((index: number | null): index is number => index !== null);
if (editorTabIndicesToRemove.length === 0) {
return state;
}

let newActiveEditorTabIndex = state[workspaceLocation].activeEditorTabIndex;
const newEditorTabs = [...editorTabs];
for (let i = editorTabIndicesToRemove.length - 1; i >= 0; i--) {
const editorTabIndexToRemove = editorTabIndicesToRemove[i];
newEditorTabs.splice(editorTabIndexToRemove, 1);
newActiveEditorTabIndex = getNextActiveEditorTabIndexAfterTabRemoval(
newActiveEditorTabIndex,
editorTabIndexToRemove,
newEditorTabs.length
);
}

return {
...state,
Expand Down Expand Up @@ -894,3 +946,31 @@ export const WorkspaceReducer: Reducer<WorkspaceManagerState> = (
return state;
}
};

const getNextActiveEditorTabIndexAfterTabRemoval = (
activeEditorTabIndex: number | null,
removedEditorTabIndex: number,
newEditorTabsLength: number
) => {
return activeEditorTabIndex !== removedEditorTabIndex
? // If the active editor tab is not the one that is removed,
// the active editor tab remains the same if its index is
// less than the removed editor tab index or null.
activeEditorTabIndex === null || activeEditorTabIndex < removedEditorTabIndex
? activeEditorTabIndex
: // Otherwise, the active editor tab index needs to have 1
// subtracted because every tab to the right of the editor
// tab being removed has their index decremented by 1.
activeEditorTabIndex - 1
: newEditorTabsLength === 0
? // If there are no editor tabs after removal, there cannot
// be an active editor tab.
null
: removedEditorTabIndex === 0
? // If the removed editor tab is the leftmost tab, the active
// editor tab will be the new leftmost tab.
0
: // Otherwise, the active editor tab will be the tab to the
// left of the removed tab.
removedEditorTabIndex - 1;
};
2 changes: 2 additions & 0 deletions src/commons/workspace/WorkspaceTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export const UPDATE_EDITOR_BREAKPOINTS = 'UPDATE_EDITOR_BREAKPOINTS';
export const ADD_EDITOR_TAB = 'ADD_EDITOR_TAB';
export const SHIFT_EDITOR_TAB = 'SHIFT_EDITOR_TAB';
export const REMOVE_EDITOR_TAB = 'REMOVE_EDITOR_TAB';
export const REMOVE_EDITOR_TAB_FOR_FILE = 'REMOVE_EDITOR_TAB_FOR_FILE';
export const REMOVE_EDITOR_TABS_FOR_DIRECTORY = 'REMOVE_EDITOR_TABS_FOR_DIRECTORY';
export const UPDATE_HAS_UNSAVED_CHANGES = 'UPDATE_HAS_UNSAVED_CHANGES';
export const UPDATE_REPL_VALUE = 'UPDATE_REPL_VALUE';
export const UPDATE_WORKSPACE = 'UPDATE_WORKSPACE';
Expand Down
28 changes: 28 additions & 0 deletions src/commons/workspace/__tests__/WorkspaceActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
moveCursor,
navigateToDeclaration,
removeEditorTab,
removeEditorTabForFile,
removeEditorTabsForDirectory,
resetTestcase,
resetWorkspace,
sendReplInputToOutput,
Expand Down Expand Up @@ -64,6 +66,8 @@ import {
NAV_DECLARATION,
PLAYGROUND_EXTERNAL_SELECT,
REMOVE_EDITOR_TAB,
REMOVE_EDITOR_TAB_FOR_FILE,
REMOVE_EDITOR_TABS_FOR_DIRECTORY,
RESET_TESTCASE,
RESET_WORKSPACE,
SEND_REPL_INPUT_TO_OUTPUT,
Expand Down Expand Up @@ -404,6 +408,30 @@ test('removeEditorTab generates correct action object', () => {
});
});

test('removeEditorTabForFile generates correct action object', () => {
const removedFilePath = '/dir1/a.js';
const action = removeEditorTabForFile(playgroundWorkspace, removedFilePath);
expect(action).toEqual({
type: REMOVE_EDITOR_TAB_FOR_FILE,
payload: {
workspaceLocation: playgroundWorkspace,
removedFilePath
}
});
});

test('removeEditorTabsForDirectory generates correct action object', () => {
const removedDirectoryPath = '/dir1';
const action = removeEditorTabsForDirectory(playgroundWorkspace, removedDirectoryPath);
expect(action).toEqual({
type: REMOVE_EDITOR_TABS_FOR_DIRECTORY,
payload: {
workspaceLocation: playgroundWorkspace,
removedDirectoryPath
}
});
});

test('updateReplValue generates correct action object', () => {
const newReplValue = 'new_repl_value';
const action = updateReplValue(newReplValue, assessmentWorkspace);
Expand Down
Loading