vzcode 1.11.0 → 1.13.0
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/README.md +1 -1
- package/dist/assets/index-Bm65AzMP.js +425 -0
- package/dist/assets/index-BpeGnq7b.js +157 -0
- package/dist/assets/{index-BKRpkJER.css → index-Wxh-Jaj6.css} +1 -1
- package/dist/assets/worker-CJWrLBN2.js +286 -0
- package/dist/index.html +2 -2
- package/package.json +18 -17
- package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +5 -2
- package/src/client/AIAssist/startAIAssist.ts +1 -1
- package/src/client/CodeEditor/getOrCreateEditor.ts +11 -4
- package/src/client/CodeEditor/index.tsx +2 -0
- package/src/client/CodeEditor/json1PresenceDisplay.ts +1 -1
- package/src/client/Icons/SplitEditorSVG.tsx +34 -0
- package/src/client/RunCodeWidget/index.tsx +48 -16
- package/src/client/RunCodeWidget/style.scss +6 -1
- package/src/client/TabList/Tab.tsx +1 -2
- package/src/client/TabList/index.tsx +1 -1
- package/src/client/VZCodeContext.tsx +30 -7
- package/src/client/VZKeyboardShortcutsDoc.tsx +2 -1
- package/src/client/VZSidebar/DirectoryListing.tsx +2 -1
- package/src/client/VZSidebar/FileListing.tsx +23 -3
- package/src/client/VZSidebar/Item.tsx +2 -2
- package/src/client/VZSidebar/Listing.tsx +8 -1
- package/src/client/VZSidebar/Search.tsx +7 -2
- package/src/client/VZSidebar/index.tsx +130 -24
- package/src/client/VZSidebar/styles.scss +25 -0
- package/src/client/tabsSearchParameters.ts +1 -2
- package/src/client/useActions.ts +29 -1
- package/src/client/useKeyboardShortcuts.ts +31 -1
- package/src/client/useURLSync.ts +1 -2
- package/src/client/vzReducer/closeTabReducer.test.ts +49 -33
- package/src/client/vzReducer/closeTabsReducer.ts +51 -19
- package/src/client/vzReducer/createInitialState.ts +9 -3
- package/src/client/vzReducer/findPane.ts +19 -0
- package/src/client/vzReducer/index.ts +38 -27
- package/src/client/vzReducer/openTabReducer.test.ts +38 -21
- package/src/client/vzReducer/openTabReducer.ts +26 -10
- package/src/client/vzReducer/searchReducer.ts +21 -1
- package/src/client/vzReducer/setActiveFileIdReducer.ts +23 -4
- package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +37 -8
- package/src/client/vzReducer/splitCurrentPaneReducer.ts +71 -0
- package/src/client/vzReducer/updatePane.ts +44 -0
- package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +30 -0
- package/src/server/index.js +1 -0
- package/src/types.ts +86 -1
- package/dist/assets/index--bvX81xc.js +0 -425
- package/dist/assets/index-BSuijdti.js +0 -157
- package/dist/assets/worker-Cs0ZU3m1.js +0 -291
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { VZAction, VZState } from '.';
|
|
2
|
+
import {
|
|
3
|
+
FileId,
|
|
4
|
+
Pane,
|
|
5
|
+
PaneId,
|
|
6
|
+
TabState,
|
|
7
|
+
} from '../../types';
|
|
8
|
+
import { findPane } from './findPane';
|
|
9
|
+
import { updatePane } from './updatePane';
|
|
3
10
|
|
|
4
11
|
export const closeTabsReducer = (
|
|
5
12
|
state: VZState,
|
|
@@ -9,35 +16,56 @@ export const closeTabsReducer = (
|
|
|
9
16
|
return state;
|
|
10
17
|
}
|
|
11
18
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
19
|
+
const { pane, activePaneId } = state;
|
|
20
|
+
const activePane: Pane = findPane(pane, activePaneId);
|
|
21
|
+
|
|
22
|
+
if (activePane.type !== 'leafPane') {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'closeTabsReducer: activePane is not a leafPane',
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Derive the new tab list by filtering out the tabs to close.
|
|
29
|
+
const newTabList: Array<TabState> =
|
|
30
|
+
activePane.tabList.filter(
|
|
31
|
+
(tabState) =>
|
|
32
|
+
!action.fileIdsToClose.includes(tabState.fileId),
|
|
33
|
+
);
|
|
16
34
|
|
|
17
35
|
// If the active tab wasn't closed, keep it active.
|
|
18
36
|
if (
|
|
19
|
-
!action.fileIdsToClose.includes(
|
|
37
|
+
!action.fileIdsToClose.includes(
|
|
38
|
+
activePane.activeFileId!,
|
|
39
|
+
)
|
|
20
40
|
) {
|
|
21
41
|
return {
|
|
22
42
|
...state,
|
|
23
|
-
|
|
43
|
+
pane: updatePane({
|
|
44
|
+
pane,
|
|
45
|
+
activePaneId,
|
|
46
|
+
newTabList,
|
|
47
|
+
}),
|
|
24
48
|
};
|
|
25
49
|
}
|
|
26
50
|
|
|
27
|
-
|
|
28
|
-
(tabState) => tabState.fileId === state.activeFileId,
|
|
29
|
-
);
|
|
30
|
-
|
|
51
|
+
// Otherwise, we'll need to also find a new active tab.
|
|
31
52
|
let newActiveFileId: FileId | null = null;
|
|
32
53
|
|
|
54
|
+
// The question becomes: which tab should be active now?
|
|
55
|
+
// - Either the tab to the left or right of the closed tab.
|
|
56
|
+
|
|
33
57
|
// Try getting the previous available tab first.
|
|
58
|
+
const originalIndex = activePane.tabList.findIndex(
|
|
59
|
+
(tabState) =>
|
|
60
|
+
tabState.fileId === activePane.activeFileId,
|
|
61
|
+
);
|
|
34
62
|
for (let i = originalIndex - 1; i >= 0; i--) {
|
|
35
63
|
if (
|
|
36
64
|
!action.fileIdsToClose.includes(
|
|
37
|
-
|
|
65
|
+
activePane.tabList[i].fileId,
|
|
38
66
|
)
|
|
39
67
|
) {
|
|
40
|
-
newActiveFileId =
|
|
68
|
+
newActiveFileId = activePane.tabList[i].fileId;
|
|
41
69
|
break;
|
|
42
70
|
}
|
|
43
71
|
}
|
|
@@ -46,15 +74,15 @@ export const closeTabsReducer = (
|
|
|
46
74
|
if (!newActiveFileId) {
|
|
47
75
|
for (
|
|
48
76
|
let i = originalIndex + 1;
|
|
49
|
-
i <
|
|
77
|
+
i < activePane.tabList.length;
|
|
50
78
|
i++
|
|
51
79
|
) {
|
|
52
80
|
if (
|
|
53
81
|
!action.fileIdsToClose.includes(
|
|
54
|
-
|
|
82
|
+
activePane.tabList[i].fileId,
|
|
55
83
|
)
|
|
56
84
|
) {
|
|
57
|
-
newActiveFileId =
|
|
85
|
+
newActiveFileId = activePane.tabList[i].fileId;
|
|
58
86
|
break;
|
|
59
87
|
}
|
|
60
88
|
}
|
|
@@ -62,7 +90,11 @@ export const closeTabsReducer = (
|
|
|
62
90
|
|
|
63
91
|
return {
|
|
64
92
|
...state,
|
|
65
|
-
|
|
66
|
-
|
|
93
|
+
pane: updatePane({
|
|
94
|
+
pane,
|
|
95
|
+
activePaneId,
|
|
96
|
+
newTabList,
|
|
97
|
+
newActiveFileId,
|
|
98
|
+
}),
|
|
67
99
|
};
|
|
68
100
|
};
|
|
@@ -9,14 +9,20 @@ export const createInitialState = ({
|
|
|
9
9
|
defaultTheme: ThemeLabel;
|
|
10
10
|
initialUsername?: Username;
|
|
11
11
|
}): VZState => ({
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
pane: {
|
|
13
|
+
id: 'root',
|
|
14
|
+
type: 'leafPane',
|
|
15
|
+
tabList: [],
|
|
16
|
+
activeFileId: null,
|
|
17
|
+
},
|
|
18
|
+
activePaneId: 'root',
|
|
14
19
|
theme: defaultTheme,
|
|
15
|
-
search: { pattern: '', results: {} },
|
|
20
|
+
search: { pattern: '', results: {}, focused: false },
|
|
16
21
|
isSearchOpen: false,
|
|
17
22
|
isSettingsOpen: false,
|
|
18
23
|
isDocOpen: false,
|
|
19
24
|
editorWantsFocus: false,
|
|
20
25
|
username: initialUsername,
|
|
21
26
|
enableAutoFollow: false,
|
|
27
|
+
sidebarPresenceIndicators: [],
|
|
22
28
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Pane, PaneId } from '../../types';
|
|
2
|
+
|
|
3
|
+
// Recursively finds a pane by id, given a root pane node.
|
|
4
|
+
export const findPane = (
|
|
5
|
+
pane: Pane,
|
|
6
|
+
paneId: PaneId,
|
|
7
|
+
): Pane => {
|
|
8
|
+
if (pane.id === paneId) {
|
|
9
|
+
return pane;
|
|
10
|
+
}
|
|
11
|
+
if (pane.type === 'splitPane') {
|
|
12
|
+
for (const child of pane.children) {
|
|
13
|
+
const foundPane = findPane(child, paneId);
|
|
14
|
+
if (foundPane) {
|
|
15
|
+
return foundPane;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FileId,
|
|
3
|
+
Pane,
|
|
4
|
+
PaneId,
|
|
5
|
+
PresenceIndicator,
|
|
3
6
|
SearchFileVisibility,
|
|
4
7
|
SearchResults,
|
|
5
8
|
ShareDBDoc,
|
|
@@ -24,18 +27,20 @@ import {
|
|
|
24
27
|
setSearchReducer,
|
|
25
28
|
setSearchResultsReducer,
|
|
26
29
|
setSearchFileVisibilityReducer,
|
|
30
|
+
toggleSearchFocusedReducer,
|
|
27
31
|
} from './searchReducer';
|
|
28
32
|
import { toggleAutoFollowReducer } from './toggleAutoFollowReducer';
|
|
33
|
+
import { updatePresenceIndicatorReducer } from './updatePresenceIndicatorReducer';
|
|
34
|
+
import { splitCurrentPaneReducer } from './splitCurrentPaneReducer';
|
|
29
35
|
export { createInitialState } from './createInitialState';
|
|
30
36
|
|
|
31
37
|
// The shape of the state managed by the reducer.
|
|
32
38
|
export type VZState = {
|
|
33
|
-
// The
|
|
34
|
-
|
|
39
|
+
// The state of the split pane (tree data structure).
|
|
40
|
+
pane: Pane;
|
|
35
41
|
|
|
36
|
-
// The
|
|
37
|
-
|
|
38
|
-
activeFileId: FileId | null;
|
|
42
|
+
// The id of the currently active pane.
|
|
43
|
+
activePaneId: PaneId;
|
|
39
44
|
|
|
40
45
|
// The theme that is currently active.
|
|
41
46
|
theme: ThemeLabel;
|
|
@@ -61,6 +66,10 @@ export type VZState = {
|
|
|
61
66
|
// This is a feature that automatically scrolls the editor to the
|
|
62
67
|
// currently active remote user's cursor position.
|
|
63
68
|
enableAutoFollow: boolean;
|
|
69
|
+
|
|
70
|
+
// The list of remote users and which file they have open,
|
|
71
|
+
// for presence display in the sidebar.
|
|
72
|
+
sidebarPresenceIndicators: Array<PresenceIndicator>;
|
|
64
73
|
};
|
|
65
74
|
|
|
66
75
|
// The shape of the actions that can be dispatched to the reducer.
|
|
@@ -86,7 +95,11 @@ export type VZAction =
|
|
|
86
95
|
|
|
87
96
|
// `close_tabs`
|
|
88
97
|
// * Closes a set of tabs.
|
|
89
|
-
| {
|
|
98
|
+
| {
|
|
99
|
+
type: 'close_tabs';
|
|
100
|
+
fileIdsToClose: Array<FileId>;
|
|
101
|
+
// The pane id to close tabs from.
|
|
102
|
+
}
|
|
90
103
|
|
|
91
104
|
// `set_theme`
|
|
92
105
|
// * Sets the theme.
|
|
@@ -121,6 +134,10 @@ export type VZAction =
|
|
|
121
134
|
visibility: SearchFileVisibility;
|
|
122
135
|
}
|
|
123
136
|
|
|
137
|
+
// `toggle_search_focused`
|
|
138
|
+
// * Toggles focused variable to trigger search input focus
|
|
139
|
+
| { type: 'toggle_search_focused' }
|
|
140
|
+
|
|
124
141
|
// `editor_no_longer_wants_focus`
|
|
125
142
|
// * Sets `editorWantsFocus` to `false`.
|
|
126
143
|
| { type: 'editor_no_longer_wants_focus' }
|
|
@@ -129,28 +146,19 @@ export type VZAction =
|
|
|
129
146
|
// * Sets the username.
|
|
130
147
|
| { type: 'set_username'; username: Username }
|
|
131
148
|
|
|
132
|
-
// `toggle_auto_follow
|
|
149
|
+
// `toggle_auto_follow`
|
|
133
150
|
// * Toggles the auto-follow feature.
|
|
134
|
-
| { type: 'toggle_auto_follow' }
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
// appears as italic, and it will be automatically
|
|
146
|
-
// closed when the user switches to another file.
|
|
147
|
-
// If `true` and the tab is opened, the editor will not focus.
|
|
148
|
-
// * `false` or `undefined` if the tab is persistent, meaning its text
|
|
149
|
-
// appears as normal, and it will not be automatically
|
|
150
|
-
// closed when the user switches to another file.
|
|
151
|
-
// If `false` and the tab is opened, the editor will focus.
|
|
152
|
-
isTransient?: boolean;
|
|
153
|
-
};
|
|
151
|
+
| { type: 'toggle_auto_follow' }
|
|
152
|
+
|
|
153
|
+
// `update_presence_indicator
|
|
154
|
+
// * Updates a single presence indicator
|
|
155
|
+
| {
|
|
156
|
+
type: 'update_presence_indicator';
|
|
157
|
+
presenceIndicator: PresenceIndicator;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// `split_current_pane`
|
|
161
|
+
| { type: 'split_current_pane' };
|
|
154
162
|
|
|
155
163
|
const reducers = [
|
|
156
164
|
setActiveFileIdReducer,
|
|
@@ -162,12 +170,15 @@ const reducers = [
|
|
|
162
170
|
setSearchReducer,
|
|
163
171
|
setSearchResultsReducer,
|
|
164
172
|
setSearchFileVisibilityReducer,
|
|
173
|
+
toggleSearchFocusedReducer,
|
|
165
174
|
setIsSearchOpenReducer,
|
|
166
175
|
setIsSettingsOpenReducer,
|
|
167
176
|
setIsDocOpenReducer,
|
|
168
177
|
editorNoLongerWantsFocusReducer,
|
|
169
178
|
setUsernameReducer,
|
|
170
179
|
toggleAutoFollowReducer,
|
|
180
|
+
updatePresenceIndicatorReducer,
|
|
181
|
+
splitCurrentPaneReducer,
|
|
171
182
|
];
|
|
172
183
|
|
|
173
184
|
export const vzReducer = (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { assert, describe, expect, it } from 'vitest';
|
|
2
2
|
import { openTabReducer } from './openTabReducer';
|
|
3
3
|
import { VZAction, VZState, createInitialState } from '.';
|
|
4
4
|
import { defaultTheme } from '../themes';
|
|
@@ -16,16 +16,22 @@ describe('openTabReducer', () => {
|
|
|
16
16
|
};
|
|
17
17
|
const newState = openTabReducer(initialState, action);
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
expect(newState.tabList
|
|
21
|
-
expect(newState.tabList[0].
|
|
22
|
-
expect(newState.
|
|
19
|
+
assert(newState.pane.type === 'leafPane');
|
|
20
|
+
expect(newState.pane.tabList.length).toBe(1);
|
|
21
|
+
expect(newState.pane.tabList[0].fileId).toBe('file1');
|
|
22
|
+
expect(newState.pane.tabList[0].isTransient).toBe(
|
|
23
|
+
false,
|
|
24
|
+
);
|
|
25
|
+
expect(newState.pane.activeFileId).toBe('file1');
|
|
23
26
|
});
|
|
24
27
|
|
|
25
28
|
it('Replaces a transient tab', () => {
|
|
26
29
|
const stateWithTransientTab = {
|
|
27
30
|
...initialState,
|
|
28
|
-
|
|
31
|
+
pane: {
|
|
32
|
+
...initialState.pane,
|
|
33
|
+
tabList: [{ fileId: 'file2', isTransient: true }],
|
|
34
|
+
},
|
|
29
35
|
};
|
|
30
36
|
const action: VZAction = {
|
|
31
37
|
type: 'open_tab',
|
|
@@ -37,16 +43,20 @@ describe('openTabReducer', () => {
|
|
|
37
43
|
action,
|
|
38
44
|
);
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
expect(newState.tabList
|
|
42
|
-
expect(newState.tabList[0].
|
|
43
|
-
expect(newState.
|
|
46
|
+
assert(newState.pane.type === 'leafPane');
|
|
47
|
+
expect(newState.pane.tabList.length).toBe(1);
|
|
48
|
+
expect(newState.pane.tabList[0].fileId).toBe('file1');
|
|
49
|
+
expect(newState.pane.tabList[0].isTransient).toBe(true);
|
|
50
|
+
expect(newState.pane.activeFileId).toBe('file1');
|
|
44
51
|
});
|
|
45
52
|
|
|
46
53
|
it('Does not modify a non-transient tab', () => {
|
|
47
54
|
const stateWithNonTransientTab = {
|
|
48
55
|
...initialState,
|
|
49
|
-
|
|
56
|
+
pane: {
|
|
57
|
+
...initialState.pane,
|
|
58
|
+
tabList: [{ fileId: 'file1', isTransient: false }],
|
|
59
|
+
},
|
|
50
60
|
};
|
|
51
61
|
const action: VZAction = {
|
|
52
62
|
type: 'open_tab',
|
|
@@ -58,19 +68,25 @@ describe('openTabReducer', () => {
|
|
|
58
68
|
action,
|
|
59
69
|
);
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
expect(newState.tabList
|
|
63
|
-
expect(newState.tabList[0].
|
|
64
|
-
expect(newState.
|
|
71
|
+
assert(newState.pane.type === 'leafPane');
|
|
72
|
+
expect(newState.pane.tabList.length).toBe(1);
|
|
73
|
+
expect(newState.pane.tabList[0].fileId).toBe('file1');
|
|
74
|
+
expect(newState.pane.tabList[0].isTransient).toBe(
|
|
75
|
+
false,
|
|
76
|
+
); // should remain non-transient
|
|
77
|
+
expect(newState.pane.activeFileId).toBe('file1');
|
|
65
78
|
});
|
|
66
79
|
|
|
67
80
|
it('Activates an already open tab', () => {
|
|
68
81
|
const stateWithMultipleTabs = {
|
|
69
82
|
...initialState,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
pane: {
|
|
84
|
+
...initialState.pane,
|
|
85
|
+
tabList: [
|
|
86
|
+
{ fileId: 'file1', isTransient: false },
|
|
87
|
+
{ fileId: 'file2', isTransient: false },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
74
90
|
};
|
|
75
91
|
const action: VZAction = {
|
|
76
92
|
type: 'open_tab',
|
|
@@ -82,8 +98,9 @@ describe('openTabReducer', () => {
|
|
|
82
98
|
action,
|
|
83
99
|
);
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
expect(newState.
|
|
101
|
+
assert(newState.pane.type === 'leafPane');
|
|
102
|
+
expect(newState.pane.tabList.length).toBe(2);
|
|
103
|
+
expect(newState.pane.activeFileId).toBe('file1');
|
|
87
104
|
});
|
|
88
105
|
|
|
89
106
|
it('Sets editorWantsFocus to true for non-transient tabs', () => {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { VZAction, VZState } from '.';
|
|
2
|
+
import { Pane, TabState } from '../../types';
|
|
3
|
+
import { findPane } from './findPane';
|
|
4
|
+
import { updatePane } from './updatePane';
|
|
2
5
|
|
|
3
6
|
export const openTabReducer = (
|
|
4
7
|
state: VZState,
|
|
@@ -6,17 +9,26 @@ export const openTabReducer = (
|
|
|
6
9
|
): VZState => {
|
|
7
10
|
if (action.type !== 'open_tab') return state;
|
|
8
11
|
|
|
12
|
+
const { pane, activePaneId } = state;
|
|
13
|
+
const activePane: Pane = findPane(pane, activePaneId);
|
|
14
|
+
|
|
15
|
+
if (activePane.type !== 'leafPane') {
|
|
16
|
+
throw new Error(
|
|
17
|
+
'closeTabsReducer: activePane is not a leafPane',
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
9
21
|
// Check if the tab is already open and if it's transient.
|
|
10
|
-
const existingTabIndex =
|
|
22
|
+
const existingTabIndex = activePane.tabList.findIndex(
|
|
11
23
|
(tab) => tab.fileId === action.fileId,
|
|
12
24
|
);
|
|
13
25
|
const tabExists = existingTabIndex !== -1;
|
|
14
26
|
const existingTabIsTransient =
|
|
15
27
|
tabExists &&
|
|
16
|
-
|
|
28
|
+
activePane.tabList[existingTabIndex].isTransient;
|
|
17
29
|
|
|
18
30
|
// Find if there's any other transient tab.
|
|
19
|
-
const transientTabIndex =
|
|
31
|
+
const transientTabIndex = activePane.tabList.findIndex(
|
|
20
32
|
(tab) =>
|
|
21
33
|
tab.isTransient && tab.fileId !== action.fileId,
|
|
22
34
|
);
|
|
@@ -31,25 +43,29 @@ export const openTabReducer = (
|
|
|
31
43
|
|
|
32
44
|
if (tabExists) {
|
|
33
45
|
if (existingTabIsTransient) {
|
|
34
|
-
newTabList =
|
|
46
|
+
newTabList = activePane.tabList.map((tabState) =>
|
|
35
47
|
tabState.fileId === action.fileId
|
|
36
48
|
? newTabState
|
|
37
49
|
: tabState,
|
|
38
50
|
);
|
|
39
51
|
} else {
|
|
40
|
-
newTabList = [...
|
|
52
|
+
newTabList = [...activePane.tabList];
|
|
41
53
|
}
|
|
42
54
|
} else if (transientTabIndex !== -1) {
|
|
43
|
-
newTabList = [...
|
|
55
|
+
newTabList = [...activePane.tabList];
|
|
44
56
|
newTabList[transientTabIndex] = newTabState;
|
|
45
57
|
} else {
|
|
46
|
-
newTabList = [...
|
|
58
|
+
newTabList = [...activePane.tabList, newTabState];
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
return {
|
|
50
62
|
...state,
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
pane: updatePane({
|
|
64
|
+
pane,
|
|
65
|
+
activePaneId,
|
|
66
|
+
newTabList,
|
|
67
|
+
newActiveFileId: action.fileId,
|
|
68
|
+
}),
|
|
53
69
|
|
|
54
70
|
// If the tab is persistent, the editor should focus on the next render.
|
|
55
71
|
editorWantsFocus: !action.isTransient,
|
|
@@ -74,7 +74,11 @@ export const setSearchReducer = (
|
|
|
74
74
|
action.type === 'set_search'
|
|
75
75
|
? {
|
|
76
76
|
...state,
|
|
77
|
-
search: {
|
|
77
|
+
search: {
|
|
78
|
+
pattern: action.value,
|
|
79
|
+
results: {},
|
|
80
|
+
focused: true,
|
|
81
|
+
},
|
|
78
82
|
}
|
|
79
83
|
: state;
|
|
80
84
|
|
|
@@ -91,6 +95,7 @@ export const setSearchResultsReducer = (
|
|
|
91
95
|
action.files,
|
|
92
96
|
state.search.pattern,
|
|
93
97
|
),
|
|
98
|
+
focused: state.search.focused,
|
|
94
99
|
},
|
|
95
100
|
}
|
|
96
101
|
: state;
|
|
@@ -109,6 +114,21 @@ export const setSearchFileVisibilityReducer = (
|
|
|
109
114
|
action.id,
|
|
110
115
|
action.visibility,
|
|
111
116
|
),
|
|
117
|
+
focused: state.search.focused,
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
: state;
|
|
121
|
+
|
|
122
|
+
export const toggleSearchFocusedReducer = (
|
|
123
|
+
state: VZState,
|
|
124
|
+
action: VZAction,
|
|
125
|
+
): VZState =>
|
|
126
|
+
action.type === 'toggle_search_focused'
|
|
127
|
+
? {
|
|
128
|
+
...state,
|
|
129
|
+
search: {
|
|
130
|
+
...state.search,
|
|
131
|
+
focused: !state.search.focused,
|
|
112
132
|
},
|
|
113
133
|
}
|
|
114
134
|
: state;
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import { VZAction, VZState } from '.';
|
|
2
|
+
import { Pane } from '../../types';
|
|
3
|
+
import { findPane } from './findPane';
|
|
4
|
+
import { updatePane } from './updatePane';
|
|
2
5
|
|
|
3
6
|
export const setActiveFileIdReducer = (
|
|
4
7
|
state: VZState,
|
|
5
8
|
action: VZAction,
|
|
6
|
-
): VZState =>
|
|
7
|
-
action.type
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
): VZState => {
|
|
10
|
+
if (action.type !== 'set_active_file_id') return state;
|
|
11
|
+
|
|
12
|
+
const { pane, activePaneId } = state;
|
|
13
|
+
const activePane: Pane = findPane(pane, activePaneId);
|
|
14
|
+
|
|
15
|
+
if (activePane.type !== 'leafPane') {
|
|
16
|
+
throw new Error(
|
|
17
|
+
'closeTabsReducer: activePane is not a leafPane',
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
...state,
|
|
22
|
+
pane: updatePane({
|
|
23
|
+
pane,
|
|
24
|
+
activePaneId,
|
|
25
|
+
newActiveFileId: action.activeFileId,
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { VZAction, VZState } from '.';
|
|
2
|
+
import { Pane } from '../../types';
|
|
3
|
+
import { findPane } from './findPane';
|
|
4
|
+
import { updatePane } from './updatePane';
|
|
2
5
|
|
|
3
6
|
export const setActiveFileLeftReducer = (
|
|
4
7
|
state: VZState,
|
|
@@ -8,18 +11,31 @@ export const setActiveFileLeftReducer = (
|
|
|
8
11
|
return state;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
const { pane, activePaneId } = state;
|
|
15
|
+
const activePane: Pane = findPane(pane, activePaneId);
|
|
16
|
+
|
|
17
|
+
if (activePane.type !== 'leafPane') {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'closeTabsReducer: activePane is not a leafPane',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let index = activePane.tabList.findIndex(
|
|
24
|
+
(tab) => tab.fileId === activePane.activeFileId,
|
|
13
25
|
);
|
|
14
26
|
|
|
15
27
|
index -= 1;
|
|
16
28
|
if (index < 0) {
|
|
17
|
-
index =
|
|
29
|
+
index = activePane.tabList.length - 1;
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
return {
|
|
21
33
|
...state,
|
|
22
|
-
|
|
34
|
+
pane: updatePane({
|
|
35
|
+
pane,
|
|
36
|
+
activePaneId,
|
|
37
|
+
newActiveFileId: activePane.tabList[index].fileId,
|
|
38
|
+
}),
|
|
23
39
|
};
|
|
24
40
|
};
|
|
25
41
|
|
|
@@ -31,17 +47,30 @@ export const setActiveFileRightReducer = (
|
|
|
31
47
|
return state;
|
|
32
48
|
}
|
|
33
49
|
|
|
34
|
-
|
|
35
|
-
|
|
50
|
+
const { pane, activePaneId } = state;
|
|
51
|
+
const activePane: Pane = findPane(pane, activePaneId);
|
|
52
|
+
|
|
53
|
+
if (activePane.type !== 'leafPane') {
|
|
54
|
+
throw new Error(
|
|
55
|
+
'closeTabsReducer: activePane is not a leafPane',
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let index = activePane.tabList.findIndex(
|
|
60
|
+
(tab) => tab.fileId === activePane.activeFileId,
|
|
36
61
|
);
|
|
37
62
|
|
|
38
63
|
index += 1;
|
|
39
|
-
if (index >
|
|
64
|
+
if (index > activePane.tabList.length - 1) {
|
|
40
65
|
index = 0;
|
|
41
66
|
}
|
|
42
67
|
|
|
43
68
|
return {
|
|
44
69
|
...state,
|
|
45
|
-
|
|
70
|
+
pane: updatePane({
|
|
71
|
+
pane,
|
|
72
|
+
activePaneId,
|
|
73
|
+
newActiveFileId: activePane.tabList[index].fileId,
|
|
74
|
+
}),
|
|
46
75
|
};
|
|
47
76
|
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PaneId,
|
|
3
|
+
LeafPane,
|
|
4
|
+
SplitPane,
|
|
5
|
+
Pane,
|
|
6
|
+
} from '../../types';
|
|
7
|
+
import { VZAction, VZState } from '.';
|
|
8
|
+
import { updatePane } from './updatePane';
|
|
9
|
+
|
|
10
|
+
export const splitCurrentPaneReducer = (
|
|
11
|
+
state: VZState,
|
|
12
|
+
action: VZAction,
|
|
13
|
+
): VZState => {
|
|
14
|
+
console.log('splitCurrentPaneReducer');
|
|
15
|
+
if (action.type !== 'split_current_pane') {
|
|
16
|
+
return state;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const activePaneId = state.activePaneId;
|
|
20
|
+
|
|
21
|
+
const newLeafPane1: Pane = {
|
|
22
|
+
id: `${activePaneId}-1`, // Generate unique IDs for new panes
|
|
23
|
+
type: 'leafPane',
|
|
24
|
+
tabList: [],
|
|
25
|
+
activeFileId: null,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const newLeafPane2: Pane = {
|
|
29
|
+
id: `${activePaneId}-2`,
|
|
30
|
+
type: 'leafPane',
|
|
31
|
+
tabList: [],
|
|
32
|
+
activeFileId: null,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const newSplitPane: Pane = {
|
|
36
|
+
id: activePaneId,
|
|
37
|
+
type: 'splitPane',
|
|
38
|
+
orientation: 'horizontal',
|
|
39
|
+
children: [newLeafPane1, newLeafPane2],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const updatedPane = updatePane({
|
|
43
|
+
pane: state.pane,
|
|
44
|
+
activePaneId,
|
|
45
|
+
newTabList: undefined,
|
|
46
|
+
newActiveFileId: undefined,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const updateSplitPane = (pane: Pane, id: PaneId): Pane =>
|
|
50
|
+
pane.id === id
|
|
51
|
+
? newSplitPane
|
|
52
|
+
: pane.type === 'splitPane'
|
|
53
|
+
? {
|
|
54
|
+
...pane,
|
|
55
|
+
children: pane.children.map((child) =>
|
|
56
|
+
updateSplitPane(child, id),
|
|
57
|
+
),
|
|
58
|
+
}
|
|
59
|
+
: pane;
|
|
60
|
+
|
|
61
|
+
const finalPane = updateSplitPane(
|
|
62
|
+
updatedPane,
|
|
63
|
+
activePaneId,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
...state,
|
|
68
|
+
pane: finalPane,
|
|
69
|
+
activePaneId: newLeafPane1.id,
|
|
70
|
+
};
|
|
71
|
+
};
|