vzcode 1.11.0 → 1.12.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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{index-BSuijdti.js → index-4cYZN5Uf.js} +56 -56
  3. package/dist/assets/{index--bvX81xc.js → index-CvSVuMne.js} +57 -57
  4. package/dist/assets/worker-CJWrLBN2.js +286 -0
  5. package/dist/index.html +1 -1
  6. package/package.json +13 -12
  7. package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +5 -2
  8. package/src/client/AIAssist/startAIAssist.ts +1 -1
  9. package/src/client/CodeEditor/getOrCreateEditor.ts +1 -1
  10. package/src/client/CodeEditor/json1PresenceDisplay.ts +1 -1
  11. package/src/client/TabList/Tab.tsx +1 -2
  12. package/src/client/TabList/index.tsx +1 -1
  13. package/src/client/VZCodeContext.tsx +14 -7
  14. package/src/client/VZKeyboardShortcutsDoc.tsx +2 -1
  15. package/src/client/VZSidebar/Search.tsx +7 -2
  16. package/src/client/VZSidebar/index.tsx +87 -24
  17. package/src/client/tabsSearchParameters.ts +1 -2
  18. package/src/client/useActions.ts +10 -1
  19. package/src/client/useKeyboardShortcuts.ts +31 -1
  20. package/src/client/useURLSync.ts +1 -2
  21. package/src/client/vzReducer/closeTabReducer.test.ts +49 -33
  22. package/src/client/vzReducer/closeTabsReducer.ts +51 -19
  23. package/src/client/vzReducer/createInitialState.ts +8 -3
  24. package/src/client/vzReducer/findPane.ts +19 -0
  25. package/src/client/vzReducer/index.ts +17 -25
  26. package/src/client/vzReducer/openTabReducer.test.ts +38 -21
  27. package/src/client/vzReducer/openTabReducer.ts +26 -10
  28. package/src/client/vzReducer/searchReducer.ts +21 -1
  29. package/src/client/vzReducer/setActiveFileIdReducer.ts +23 -4
  30. package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +37 -8
  31. package/src/client/vzReducer/updatePane.ts +44 -0
  32. package/src/server/index.js +1 -0
  33. package/src/types.ts +62 -0
  34. package/dist/assets/worker-Cs0ZU3m1.js +0 -291
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { assert, describe, expect, it } from 'vitest';
2
2
  import { closeTabsReducer } from './closeTabsReducer';
3
3
  import { VZAction, VZState, createInitialState } from '.';
4
4
  import { defaultTheme } from '../themes';
@@ -25,8 +25,9 @@ describe('closeTabsReducer', () => {
25
25
  action,
26
26
  );
27
27
 
28
- expect(newState.tabList.length).toBe(0);
29
- expect(newState.activeFileId).toBeNull();
28
+ assert(newState.pane.type === 'leafPane');
29
+ expect(newState.pane.tabList.length).toBe(0);
30
+ expect(newState.pane.activeFileId).toBeNull();
30
31
  });
31
32
 
32
33
  it('Keeps other tabs open', () => {
@@ -36,11 +37,14 @@ describe('closeTabsReducer', () => {
36
37
  };
37
38
  const stateWithTabs = {
38
39
  ...initialState,
39
- tabList: [
40
- { fileId: 'file1', isTransient: false },
41
- { fileId: 'file2', isTransient: false },
42
- ],
43
- activeFileId: 'file1',
40
+ pane: {
41
+ ...initialState.pane,
42
+ tabList: [
43
+ { fileId: 'file1', isTransient: false },
44
+ { fileId: 'file2', isTransient: false },
45
+ ],
46
+ activeFileId: 'file1',
47
+ },
44
48
  };
45
49
 
46
50
  const newState = closeTabsReducer(
@@ -48,24 +52,29 @@ describe('closeTabsReducer', () => {
48
52
  action,
49
53
  );
50
54
 
51
- expect(newState.tabList.length).toBe(1);
52
- expect(newState.tabList[0].fileId).toBe('file1');
53
- expect(newState.activeFileId).toBe('file1');
55
+ assert(newState.pane.type === 'leafPane');
56
+ expect(newState.pane.tabList.length).toBe(1);
57
+ expect(newState.pane.tabList[0].fileId).toBe('file1');
58
+ expect(newState.pane.activeFileId).toBe('file1');
54
59
  });
55
60
 
56
- it('Activates previous tab after closing an active tab', () => {
61
+ it.only('Activates previous tab after closing an active tab', () => {
57
62
  const action: VZAction = {
58
63
  type: 'close_tabs',
59
64
  fileIdsToClose: ['file2'],
60
65
  };
61
- const stateWithTabs = {
66
+ const stateWithTabs: VZState = {
62
67
  ...initialState,
63
- tabList: [
64
- { fileId: 'file1', isTransient: false },
65
- { fileId: 'file2', isTransient: false },
66
- { fileId: 'file3', isTransient: false },
67
- ],
68
- activeFileId: 'file2',
68
+ pane: {
69
+ ...initialState.pane,
70
+ type: 'leafPane',
71
+ tabList: [
72
+ { fileId: 'file1', isTransient: false },
73
+ { fileId: 'file2', isTransient: false },
74
+ { fileId: 'file3', isTransient: false },
75
+ ],
76
+ activeFileId: 'file2',
77
+ },
69
78
  };
70
79
 
71
80
  const newState = closeTabsReducer(
@@ -73,8 +82,9 @@ describe('closeTabsReducer', () => {
73
82
  action,
74
83
  );
75
84
 
76
- expect(newState.tabList.length).toBe(2);
77
- expect(newState.activeFileId).toBe('file1'); // should switch to the previous tab
85
+ assert(newState.pane.type === 'leafPane');
86
+ expect(newState.pane.tabList.length).toBe(2);
87
+ expect(newState.pane.activeFileId).toBe('file1'); // should switch to the previous tab
78
88
  });
79
89
 
80
90
  it('Does not modify the state if closing non-existing tabs', () => {
@@ -84,12 +94,15 @@ describe('closeTabsReducer', () => {
84
94
  };
85
95
  const stateWithTabs = {
86
96
  ...initialState,
87
- tabList: [
88
- { fileId: 'file1', isTransient: false },
89
- { fileId: 'file2', isTransient: false },
90
- { fileId: 'file3', isTransient: false },
91
- ],
92
- activeFileId: 'file2',
97
+ pane: {
98
+ ...initialState.pane,
99
+ tabList: [
100
+ { fileId: 'file1', isTransient: false },
101
+ { fileId: 'file2', isTransient: false },
102
+ { fileId: 'file3', isTransient: false },
103
+ ],
104
+ activeFileId: 'file2',
105
+ },
93
106
  };
94
107
 
95
108
  const newState = closeTabsReducer(
@@ -121,8 +134,9 @@ describe('closeTabsReducer', () => {
121
134
  action,
122
135
  );
123
136
 
124
- expect(newState.tabList.length).toBe(2);
125
- expect(newState.activeFileId).toBe('file3'); // remains unchanged
137
+ assert(newState.pane.type === 'leafPane');
138
+ expect(newState.pane.tabList.length).toBe(2);
139
+ expect(newState.pane.activeFileId).toBe('file3'); // remains unchanged
126
140
  });
127
141
 
128
142
  it('Closes the first tab out of many, when it is active', () => {
@@ -146,8 +160,9 @@ describe('closeTabsReducer', () => {
146
160
  action,
147
161
  );
148
162
 
149
- expect(newState.tabList.length).toBe(2);
150
- expect(newState.activeFileId).toBe('file2'); // switches to the next available tab
163
+ assert(newState.pane.type === 'leafPane');
164
+ expect(newState.pane.tabList.length).toBe(2);
165
+ expect(newState.pane.activeFileId).toBe('file2'); // switches to the next available tab
151
166
  });
152
167
 
153
168
  it('Closes multiple tabs at once', () => {
@@ -171,7 +186,8 @@ describe('closeTabsReducer', () => {
171
186
  action,
172
187
  );
173
188
 
174
- expect(newState.tabList.length).toBe(1);
175
- expect(newState.activeFileId).toBe('file3'); // switches to the next available tab
189
+ assert(newState.pane.type === 'leafPane');
190
+ expect(newState.pane.tabList.length).toBe(1);
191
+ expect(newState.pane.activeFileId).toBe('file3'); // switches to the next available tab
176
192
  });
177
193
  });
@@ -1,5 +1,12 @@
1
- import { TabState, VZAction, VZState } from '.';
2
- import { FileId } from '../../types';
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 newTabList: Array<TabState> = state.tabList.filter(
13
- (tabState) =>
14
- !action.fileIdsToClose.includes(tabState.fileId),
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(state.activeFileId!)
37
+ !action.fileIdsToClose.includes(
38
+ activePane.activeFileId!,
39
+ )
20
40
  ) {
21
41
  return {
22
42
  ...state,
23
- tabList: newTabList,
43
+ pane: updatePane({
44
+ pane,
45
+ activePaneId,
46
+ newTabList,
47
+ }),
24
48
  };
25
49
  }
26
50
 
27
- const originalIndex = state.tabList.findIndex(
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
- state.tabList[i].fileId,
65
+ activePane.tabList[i].fileId,
38
66
  )
39
67
  ) {
40
- newActiveFileId = state.tabList[i].fileId;
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 < state.tabList.length;
77
+ i < activePane.tabList.length;
50
78
  i++
51
79
  ) {
52
80
  if (
53
81
  !action.fileIdsToClose.includes(
54
- state.tabList[i].fileId,
82
+ activePane.tabList[i].fileId,
55
83
  )
56
84
  ) {
57
- newActiveFileId = state.tabList[i].fileId;
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
- tabList: newTabList,
66
- activeFileId: newActiveFileId,
93
+ pane: updatePane({
94
+ pane,
95
+ activePaneId,
96
+ newTabList,
97
+ newActiveFileId,
98
+ }),
67
99
  };
68
100
  };
@@ -9,10 +9,15 @@ export const createInitialState = ({
9
9
  defaultTheme: ThemeLabel;
10
10
  initialUsername?: Username;
11
11
  }): VZState => ({
12
- tabList: [],
13
- activeFileId: null,
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,
@@ -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,7 @@
1
1
  import {
2
2
  FileId,
3
+ Pane,
4
+ PaneId,
3
5
  SearchFileVisibility,
4
6
  SearchResults,
5
7
  ShareDBDoc,
@@ -24,18 +26,18 @@ import {
24
26
  setSearchReducer,
25
27
  setSearchResultsReducer,
26
28
  setSearchFileVisibilityReducer,
29
+ toggleSearchFocusedReducer,
27
30
  } from './searchReducer';
28
31
  import { toggleAutoFollowReducer } from './toggleAutoFollowReducer';
29
32
  export { createInitialState } from './createInitialState';
30
33
 
31
34
  // The shape of the state managed by the reducer.
32
35
  export type VZState = {
33
- // The list of open tabs.
34
- tabList: Array<TabState>;
36
+ // The state of the split pane (tree data structure).
37
+ pane: Pane;
35
38
 
36
- // The ID of the file that is currently active.
37
- // Invariant: `activeFileId` is always in `tabList`.
38
- activeFileId: FileId | null;
39
+ // The id of the currently active pane.
40
+ activePaneId: PaneId;
39
41
 
40
42
  // The theme that is currently active.
41
43
  theme: ThemeLabel;
@@ -86,7 +88,11 @@ export type VZAction =
86
88
 
87
89
  // `close_tabs`
88
90
  // * Closes a set of tabs.
89
- | { type: 'close_tabs'; fileIdsToClose: Array<FileId> }
91
+ | {
92
+ type: 'close_tabs';
93
+ fileIdsToClose: Array<FileId>;
94
+ // The pane id to close tabs from.
95
+ }
90
96
 
91
97
  // `set_theme`
92
98
  // * Sets the theme.
@@ -121,6 +127,10 @@ export type VZAction =
121
127
  visibility: SearchFileVisibility;
122
128
  }
123
129
 
130
+ // `toggle_search_focused`
131
+ // * Toggles focused variable to trigger search input focus
132
+ | { type: 'toggle_search_focused' }
133
+
124
134
  // `editor_no_longer_wants_focus`
125
135
  // * Sets `editorWantsFocus` to `false`.
126
136
  | { type: 'editor_no_longer_wants_focus' }
@@ -133,25 +143,6 @@ export type VZAction =
133
143
  // * Toggles the auto-follow feature.
134
144
  | { type: 'toggle_auto_follow' };
135
145
 
136
- // Representation of an open tab.
137
- export type TabState = {
138
- // `fileId`
139
- // The ID of the file that the tab represents.
140
- fileId: FileId;
141
-
142
- // `isTransient`
143
- // Represents whether the tab is temporary or persistent.
144
- // * `true` if the tab is temporary, meaning its text
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
- };
154
-
155
146
  const reducers = [
156
147
  setActiveFileIdReducer,
157
148
  setActiveFileLeftReducer,
@@ -162,6 +153,7 @@ const reducers = [
162
153
  setSearchReducer,
163
154
  setSearchResultsReducer,
164
155
  setSearchFileVisibilityReducer,
156
+ toggleSearchFocusedReducer,
165
157
  setIsSearchOpenReducer,
166
158
  setIsSettingsOpenReducer,
167
159
  setIsDocOpenReducer,
@@ -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
- expect(newState.tabList.length).toBe(1);
20
- expect(newState.tabList[0].fileId).toBe('file1');
21
- expect(newState.tabList[0].isTransient).toBe(false);
22
- expect(newState.activeFileId).toBe('file1');
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
- tabList: [{ fileId: 'file2', isTransient: true }],
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
- expect(newState.tabList.length).toBe(1);
41
- expect(newState.tabList[0].fileId).toBe('file1');
42
- expect(newState.tabList[0].isTransient).toBe(true);
43
- expect(newState.activeFileId).toBe('file1');
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
- tabList: [{ fileId: 'file1', isTransient: false }],
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
- expect(newState.tabList.length).toBe(1);
62
- expect(newState.tabList[0].fileId).toBe('file1');
63
- expect(newState.tabList[0].isTransient).toBe(false); // should remain non-transient
64
- expect(newState.activeFileId).toBe('file1');
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
- tabList: [
71
- { fileId: 'file1', isTransient: false },
72
- { fileId: 'file2', isTransient: false },
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
- expect(newState.tabList.length).toBe(2);
86
- expect(newState.activeFileId).toBe('file1');
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 { TabState, VZAction, VZState } from '.';
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 = state.tabList.findIndex(
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
- state.tabList[existingTabIndex].isTransient;
28
+ activePane.tabList[existingTabIndex].isTransient;
17
29
 
18
30
  // Find if there's any other transient tab.
19
- const transientTabIndex = state.tabList.findIndex(
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 = state.tabList.map((tabState) =>
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 = [...state.tabList];
52
+ newTabList = [...activePane.tabList];
41
53
  }
42
54
  } else if (transientTabIndex !== -1) {
43
- newTabList = [...state.tabList];
55
+ newTabList = [...activePane.tabList];
44
56
  newTabList[transientTabIndex] = newTabState;
45
57
  } else {
46
- newTabList = [...state.tabList, newTabState];
58
+ newTabList = [...activePane.tabList, newTabState];
47
59
  }
48
60
 
49
61
  return {
50
62
  ...state,
51
- activeFileId: action.fileId,
52
- tabList: newTabList,
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: { pattern: action.value, results: {} },
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 === 'set_active_file_id'
8
- ? { ...state, activeFileId: action.activeFileId }
9
- : state;
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
+ };