Skip to content

Commit 05bf2ea

Browse files
authored
Remove root level flags used to track cell selection and focus (#9882)
For #9340 To be merged after #9840 Basically I got rid of the root level state info that keeps track of the cell that's selected and focused. This required us to keep the two in sync along with the state at the cell level. When syncing information between multiple editors this got messy. removing this removes the issues where things could go out of sync, i.e. two ways to storing the same thing. Now we get selected information from the cells directly - single source of truth.
1 parent 666e07d commit 05bf2ea

File tree

9 files changed

+117
-98
lines changed

9 files changed

+117
-98
lines changed

src/datascience-ui/history-react/redux/reducers/creation.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,7 @@ export namespace Creation {
120120
return {
121121
...arg.prevState,
122122
cellVMs: [],
123-
undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs),
124-
selectedCellId: undefined,
125-
focusedCellId: undefined
123+
undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs)
126124
};
127125
}
128126

src/datascience-ui/history-react/redux/reducers/execution.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,13 @@ export namespace Execution {
2424
const cells = arg.prevState.undoStack[arg.prevState.undoStack.length - 1];
2525
const undoStack = arg.prevState.undoStack.slice(0, arg.prevState.undoStack.length - 1);
2626
const redoStack = Helpers.pushStack(arg.prevState.redoStack, arg.prevState.cellVMs);
27-
const selected = cells.findIndex(c => c.selected);
2827
arg.queueAction(createPostableAction(InteractiveWindowMessages.Undo));
2928
return {
3029
...arg.prevState,
3130
cellVMs: cells,
3231
undoStack: undoStack,
3332
redoStack: redoStack,
34-
skipNextScroll: true,
35-
selectedCellId: selected >= 0 ? cells[selected].cell.id : undefined,
36-
focusedCellId: selected >= 0 && cells[selected].focused ? cells[selected].cell.id : undefined
33+
skipNextScroll: true
3734
};
3835
}
3936

@@ -46,16 +43,13 @@ export namespace Execution {
4643
const cells = arg.prevState.redoStack[arg.prevState.redoStack.length - 1];
4744
const redoStack = arg.prevState.redoStack.slice(0, arg.prevState.redoStack.length - 1);
4845
const undoStack = Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs);
49-
const selected = cells.findIndex(c => c.selected);
5046
arg.queueAction(createPostableAction(InteractiveWindowMessages.Redo));
5147
return {
5248
...arg.prevState,
5349
cellVMs: cells,
5450
undoStack: undoStack,
5551
redoStack: redoStack,
56-
skipNextScroll: true,
57-
selectedCellId: selected >= 0 ? cells[selected].cell.id : undefined,
58-
focusedCellId: selected >= 0 && cells[selected].focused ? cells[selected].cell.id : undefined
52+
skipNextScroll: true
5953
};
6054
}
6155

src/datascience-ui/interactive-common/mainState.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ export type IMainState = {
6060
currentExecutionCount: number;
6161
debugging: boolean;
6262
dirty?: boolean;
63-
selectedCellId?: string;
64-
focusedCellId?: string;
6563
isAtBottom: boolean;
6664
newCellId?: string;
6765
loadTotal?: number;
@@ -75,6 +73,29 @@ export type IMainState = {
7573
kernel: IServerState;
7674
};
7775

76+
/**
77+
* Returns the cell id and index of selected and focused cells.
78+
*/
79+
export function getSelectedAndFocusedInfo(state: IMainState) {
80+
const info: { selectedCellId?: string; selectedCellIndex?: number; focusedCellId?: string; focusedCellIndex?: number } = {};
81+
for (let index = 0; index < state.cellVMs.length; index += 1) {
82+
const cell = state.cellVMs[index];
83+
if (cell.selected) {
84+
info.selectedCellId = cell.cell.id;
85+
info.selectedCellIndex = index;
86+
}
87+
if (cell.focused) {
88+
info.focusedCellId = cell.cell.id;
89+
info.focusedCellIndex = index;
90+
}
91+
if (info.selectedCellId && info.focusedCellId) {
92+
break;
93+
}
94+
}
95+
96+
return info;
97+
}
98+
7899
export interface IFont {
79100
size: number;
80101
family: string;

src/datascience-ui/interactive-common/redux/reducers/transfer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
'use strict';
44
import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes';
55
import { CssMessages } from '../../../../client/datascience/messages';
6-
import { extractInputText, IMainState } from '../../mainState';
6+
import { extractInputText, getSelectedAndFocusedInfo, IMainState } from '../../mainState';
77
import { createPostableAction } from '../postOffice';
88
import { Helpers } from './helpers';
99
import { CommonActionType, CommonReducerArg, ICellAction, IEditCellAction, ILinkClickAction, ISendCommandAction, IShowDataViewerAction } from './types';
@@ -99,7 +99,8 @@ export namespace Transfer {
9999
// We keep this saved here so we don't re-render and we put this code into the input / code data
100100
// when focus is lost
101101
const index = arg.prevState.cellVMs.findIndex(c => c.cell.id === arg.payload.data.cellId);
102-
if (index >= 0 && arg.prevState.focusedCellId === arg.payload.data.cellId) {
102+
const selectionInfo = getSelectedAndFocusedInfo(arg.prevState);
103+
if (index >= 0 && selectionInfo.focusedCellId === arg.payload.data.cellId) {
103104
const newVMs = [...arg.prevState.cellVMs];
104105
const current = arg.prevState.cellVMs[index];
105106
const newCell = {

src/datascience-ui/interactive-common/redux/store.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { InteractiveWindowMessages } from '../../../client/datascience/interacti
99
import { BaseReduxActionPayload } from '../../../client/datascience/interactive-common/types';
1010
import { CssMessages } from '../../../client/datascience/messages';
1111
import { CellState } from '../../../client/datascience/types';
12-
import { IMainState, ServerStatus } from '../../interactive-common/mainState';
12+
import { getSelectedAndFocusedInfo, IMainState, ServerStatus } from '../../interactive-common/mainState';
1313
import { getLocString } from '../../react-common/locReactSide';
1414
import { PostOffice } from '../../react-common/postOffice';
1515
import { combineReducers, createQueueableActionMiddleware, QueuableAction } from '../../react-common/reduxUtils';
@@ -73,9 +73,10 @@ function createSendInfoMiddleware(): Redux.Middleware<{}, IStore> {
7373
const afterState = store.getState();
7474

7575
// If cell vm count changed or selected cell changed, send the message
76+
const currentSelection = getSelectedAndFocusedInfo(afterState.main);
7677
if (
7778
prevState.main.cellVMs.length !== afterState.main.cellVMs.length ||
78-
prevState.main.selectedCellId !== afterState.main.selectedCellId ||
79+
getSelectedAndFocusedInfo(prevState.main).selectedCellId !== currentSelection.selectedCellId ||
7980
prevState.main.undoStack.length !== afterState.main.undoStack.length ||
8081
prevState.main.redoStack.length !== afterState.main.redoStack.length
8182
) {
@@ -84,7 +85,7 @@ function createSendInfoMiddleware(): Redux.Middleware<{}, IStore> {
8485
cellCount: afterState.main.cellVMs.length,
8586
undoCount: afterState.main.undoStack.length,
8687
redoCount: afterState.main.redoStack.length,
87-
selectedCell: afterState.main.selectedCellId
88+
selectedCell: currentSelection.selectedCellId
8889
})
8990
);
9091
}
@@ -112,12 +113,14 @@ function createTestMiddleware(): Redux.Middleware<{}, IStore> {
112113
};
113114

114115
// Special case for focusing a cell
115-
if (prevState.main.focusedCellId !== afterState.main.focusedCellId && afterState.main.focusedCellId) {
116+
const previousSelection = getSelectedAndFocusedInfo(prevState.main);
117+
const currentSelection = getSelectedAndFocusedInfo(afterState.main);
118+
if (previousSelection.focusedCellId !== currentSelection.focusedCellId && currentSelection.focusedCellId) {
116119
// Send async so happens after render state changes (so our enzyme wrapper is up to date)
117120
sendMessage(InteractiveWindowMessages.FocusedCellEditor, { cellId: action.payload.cellId });
118121
}
119122
// Special case for unfocusing a cell
120-
if (prevState.main.focusedCellId !== afterState.main.focusedCellId && !afterState.main.focusedCellId) {
123+
if (previousSelection.focusedCellId !== currentSelection.focusedCellId && !currentSelection.focusedCellId) {
121124
// Send async so happens after render state changes (so our enzyme wrapper is up to date)
122125
sendMessage(InteractiveWindowMessages.UnfocusedCellEditor);
123126
}

src/datascience-ui/native-editor/nativeEditor.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { concatMultilineStringInput } from '../common';
99
import { ContentPanel, IContentPanelProps } from '../interactive-common/contentPanel';
1010
import { handleLinkClick } from '../interactive-common/handlers';
1111
import { KernelSelection } from '../interactive-common/kernelSelection';
12-
import { ICellViewModel, IMainState } from '../interactive-common/mainState';
12+
import { getSelectedAndFocusedInfo, ICellViewModel, IMainState } from '../interactive-common/mainState';
1313
import { IMainWithVariables, IStore } from '../interactive-common/redux/store';
1414
import { IVariablePanelProps, VariablePanel } from '../interactive-common/variablePanel';
1515
import { getOSType } from '../react-common/constants';
@@ -109,7 +109,7 @@ export class NativeEditor extends React.Component<INativeEditorProps> {
109109
// tslint:disable: react-this-binding-issue
110110
// tslint:disable-next-line: max-func-body-length
111111
private renderToolbarPanel() {
112-
const selectedIndex = this.props.cellVMs.findIndex(c => c.cell.id === this.props.selectedCellId);
112+
const selectedInfo = getSelectedAndFocusedInfo(this.props);
113113

114114
const addCell = () => {
115115
this.props.addCell();
@@ -132,16 +132,16 @@ export class NativeEditor extends React.Component<INativeEditorProps> {
132132
? getLocString('DataScience.collapseVariableExplorerTooltip', 'Hide variables active in jupyter kernel')
133133
: getLocString('DataScience.expandVariableExplorerTooltip', 'Show variables active in jupyter kernel');
134134
const runAbove = () => {
135-
if (this.props.selectedCellId) {
136-
this.props.executeAbove(this.props.selectedCellId);
135+
if (selectedInfo.selectedCellId) {
136+
this.props.executeAbove(selectedInfo.selectedCellId);
137137
this.props.sendCommand(NativeCommandType.RunAbove, 'mouse');
138138
}
139139
};
140140
const runBelow = () => {
141-
if (this.props.selectedCellId) {
141+
if (selectedInfo.selectedCellId && typeof selectedInfo.selectedCellIndex === 'number') {
142142
// tslint:disable-next-line: no-suspicious-comment
143143
// TODO: Is the source going to be up to date during run below?
144-
this.props.executeCellAndBelow(this.props.selectedCellId, concatMultilineStringInput(this.props.cellVMs[selectedIndex].cell.data.source));
144+
this.props.executeCellAndBelow(selectedInfo.selectedCellId, concatMultilineStringInput(this.props.cellVMs[selectedInfo.selectedCellIndex].cell.data.source));
145145
this.props.sendCommand(NativeCommandType.RunBelow, 'mouse');
146146
}
147147
};
@@ -153,8 +153,8 @@ export class NativeEditor extends React.Component<INativeEditorProps> {
153153
this.props.selectServer();
154154
this.props.sendCommand(NativeCommandType.SelectServer, 'mouse');
155155
};
156-
const canRunAbove = selectedIndex > 0;
157-
const canRunBelow = selectedIndex < this.props.cellVMs.length - 1 && this.props.selectedCellId;
156+
const canRunAbove = (selectedInfo.selectedCellIndex ?? -1) > 0;
157+
const canRunBelow = (selectedInfo.selectedCellIndex ?? -1) < this.props.cellVMs.length - 1 && selectedInfo.selectedCellId;
158158

159159
return (
160160
<div id="toolbar-panel">
@@ -315,7 +315,7 @@ export class NativeEditor extends React.Component<INativeEditorProps> {
315315
}
316316
case 'z':
317317
case 'Z':
318-
if (this.props.focusedCellId === undefined) {
318+
if (!getSelectedAndFocusedInfo(this.props).focusedCellId) {
319319
if (event.shiftKey && !event.ctrlKey && !event.altKey) {
320320
event.stopPropagation();
321321
this.props.redo();

src/datascience-ui/native-editor/redux/reducers/creation.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { ILoadAllCells, InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes';
66
import { ICell, IDataScienceExtraSettings } from '../../../../client/datascience/types';
7-
import { createCellVM, createEmptyCell, CursorPos, extractInputText, ICellViewModel, IMainState } from '../../../interactive-common/mainState';
7+
import { createCellVM, createEmptyCell, CursorPos, extractInputText, getSelectedAndFocusedInfo, ICellViewModel, IMainState } from '../../../interactive-common/mainState';
88
import { createPostableAction } from '../../../interactive-common/redux/postOffice';
99
import { Helpers } from '../../../interactive-common/redux/reducers/helpers';
1010
import { IAddCellAction, ICellAction } from '../../../interactive-common/redux/reducers/types';
@@ -113,7 +113,10 @@ export namespace Creation {
113113

114114
export function addNewCell(arg: NativeEditorReducerArg<IAddCellAction>): IMainState {
115115
// Do the same thing that an insertBelow does using the currently selected cell.
116-
return insertBelow({ ...arg, payload: { ...arg.payload, data: { cellId: arg.prevState.selectedCellId, newCellId: arg.payload.data.newCellId } } });
116+
return insertBelow({
117+
...arg,
118+
payload: { ...arg.payload, data: { cellId: getSelectedAndFocusedInfo(arg.prevState).selectedCellId, newCellId: arg.payload.data.newCellId } }
119+
});
117120
}
118121

119122
export function startCell(arg: NativeEditorReducerArg<ICell>): IMainState {
@@ -152,9 +155,7 @@ export namespace Creation {
152155
return {
153156
...arg.prevState,
154157
cellVMs: [newVM],
155-
undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs),
156-
selectedCellId: undefined,
157-
focusedCellId: undefined
158+
undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs)
158159
};
159160
}
160161

@@ -194,23 +195,18 @@ export namespace Creation {
194195
arg.queueAction(createPostableAction(InteractiveWindowMessages.RemoveCell, { id: arg.payload.data.cellId }));
195196

196197
// Recompute select/focus if this item has either
197-
let newSelection = arg.prevState.selectedCellId;
198-
let newFocused = arg.prevState.focusedCellId;
198+
const previousSelection = getSelectedAndFocusedInfo(arg.prevState);
199199
const newVMs = [...arg.prevState.cellVMs.filter(c => c.cell.id !== arg.payload.data.cellId)];
200200
const nextOrPrev = index === arg.prevState.cellVMs.length - 1 ? index - 1 : index;
201-
if (arg.prevState.selectedCellId === arg.payload.data.cellId || arg.prevState.focusedCellId === arg.payload.data.cellId) {
201+
if (previousSelection.selectedCellId === arg.payload.data.cellId || previousSelection.focusedCellId === arg.payload.data.cellId) {
202202
if (nextOrPrev >= 0) {
203-
newVMs[nextOrPrev] = { ...newVMs[nextOrPrev], selected: true, focused: arg.prevState.focusedCellId === arg.payload.data.cellId };
204-
newSelection = newVMs[nextOrPrev].cell.id;
205-
newFocused = newVMs[nextOrPrev].focused ? newVMs[nextOrPrev].cell.id : undefined;
203+
newVMs[nextOrPrev] = { ...newVMs[nextOrPrev], selected: true, focused: previousSelection.focusedCellId === arg.payload.data.cellId };
206204
}
207205
}
208206

209207
return {
210208
...arg.prevState,
211209
cellVMs: newVMs,
212-
selectedCellId: newSelection,
213-
focusedCellId: newFocused,
214210
undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs),
215211
skipNextScroll: true
216212
};

0 commit comments

Comments
 (0)