@lvce-editor/main-area-worker 8.5.0 → 8.6.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.
@@ -87,7 +87,231 @@ const terminate = () => {
87
87
  globalThis.close();
88
88
  };
89
89
 
90
- const Button$1 = 1;
90
+ const closeTab = (state, groupId, tabId) => {
91
+ const {
92
+ layout
93
+ } = state;
94
+ const {
95
+ activeGroupId,
96
+ groups
97
+ } = layout;
98
+
99
+ // Find the group to close the tab from
100
+ const groupIndex = groups.findIndex(g => g.id === groupId);
101
+ if (groupIndex === -1) {
102
+ return state;
103
+ }
104
+ const group = groups[groupIndex];
105
+ // Check if the tab exists in the group
106
+ const tabWasRemoved = group.tabs.some(tab => tab.id === tabId);
107
+ if (!tabWasRemoved) {
108
+ return state;
109
+ }
110
+ const newGroups = groups.map(grp => {
111
+ if (grp.id === groupId) {
112
+ const newTabs = grp.tabs.filter(tab => tab.id !== tabId);
113
+ let newActiveTabId = grp.activeTabId;
114
+ if (grp.activeTabId === tabId) {
115
+ const tabIndex = grp.tabs.findIndex(tab => tab.id === tabId);
116
+ if (newTabs.length > 0) {
117
+ newActiveTabId = newTabs[Math.min(tabIndex, newTabs.length - 1)].id;
118
+ } else {
119
+ newActiveTabId = undefined;
120
+ }
121
+ }
122
+ return {
123
+ ...grp,
124
+ activeTabId: newActiveTabId,
125
+ isEmpty: newTabs.length === 0,
126
+ tabs: newTabs
127
+ };
128
+ }
129
+ return grp;
130
+ });
131
+
132
+ // If the group has no tabs left after closing, remove the group
133
+ const groupIsNowEmpty = newGroups[groupIndex].tabs.length === 0;
134
+ if (groupIsNowEmpty) {
135
+ const remainingGroups = newGroups.filter(group => group.id !== groupId);
136
+
137
+ // If there are remaining groups, redistribute sizes
138
+ if (remainingGroups.length > 0) {
139
+ const redistributedGroups = remainingGroups.map(grp => ({
140
+ ...grp,
141
+ size: Math.round(100 / remainingGroups.length)
142
+ }));
143
+ const newActiveGroupId = activeGroupId === groupId ? remainingGroups[0]?.id : activeGroupId;
144
+ return {
145
+ ...state,
146
+ layout: {
147
+ ...layout,
148
+ activeGroupId: newActiveGroupId,
149
+ groups: redistributedGroups
150
+ }
151
+ };
152
+ }
153
+
154
+ // If no remaining groups, return empty layout
155
+ return {
156
+ ...state,
157
+ layout: {
158
+ ...layout,
159
+ activeGroupId: undefined,
160
+ groups: []
161
+ }
162
+ };
163
+ }
164
+ return {
165
+ ...state,
166
+ layout: {
167
+ ...layout,
168
+ groups: newGroups
169
+ }
170
+ };
171
+ };
172
+
173
+ const closeActiveEditor = state => {
174
+ const {
175
+ layout
176
+ } = state;
177
+ const {
178
+ groups
179
+ } = layout;
180
+ const focusedGroup = groups.find(group => group.focused);
181
+ if (!focusedGroup) {
182
+ return state;
183
+ }
184
+ const {
185
+ activeTabId
186
+ } = focusedGroup;
187
+ if (activeTabId === undefined) {
188
+ return state;
189
+ }
190
+ return closeTab(state, focusedGroup.id, activeTabId);
191
+ };
192
+
193
+ const closeAll$1 = state => {
194
+ return {
195
+ ...state,
196
+ layout: {
197
+ ...state.layout,
198
+ activeGroupId: undefined,
199
+ groups: []
200
+ }
201
+ };
202
+ };
203
+
204
+ const closeFocusedTab = state => {
205
+ const {
206
+ layout
207
+ } = state;
208
+ const {
209
+ groups
210
+ } = layout;
211
+ const focusedGroup = groups.find(group => group.focused);
212
+ if (!focusedGroup) {
213
+ return state;
214
+ }
215
+ const {
216
+ activeTabId
217
+ } = focusedGroup;
218
+ if (activeTabId === undefined) {
219
+ return state;
220
+ }
221
+ return closeTab(state, focusedGroup.id, activeTabId);
222
+ };
223
+
224
+ const closeOtherTabs = (state, groupId) => {
225
+ const {
226
+ layout
227
+ } = state;
228
+ const {
229
+ activeGroupId,
230
+ groups
231
+ } = layout;
232
+ const targetGroupId = groupId ?? activeGroupId;
233
+ if (targetGroupId === undefined) {
234
+ return state;
235
+ }
236
+ const group = groups.find(g => g.id === targetGroupId);
237
+ if (!group) {
238
+ return state;
239
+ }
240
+ const {
241
+ activeTabId
242
+ } = group;
243
+ if (activeTabId === undefined) {
244
+ return state;
245
+ }
246
+ const newGroups = groups.map(g => {
247
+ if (g.id === targetGroupId) {
248
+ const newTabs = g.tabs.filter(tab => tab.id === activeTabId);
249
+ return {
250
+ ...g,
251
+ activeTabId,
252
+ isEmpty: newTabs.length === 0,
253
+ tabs: newTabs
254
+ };
255
+ }
256
+ return g;
257
+ });
258
+ return {
259
+ ...state,
260
+ layout: {
261
+ ...layout,
262
+ groups: newGroups
263
+ }
264
+ };
265
+ };
266
+
267
+ const closeTabsRight = (state, groupId) => {
268
+ const {
269
+ layout
270
+ } = state;
271
+ const {
272
+ groups
273
+ } = layout;
274
+ const group = groups.find(g => g.id === groupId);
275
+ if (!group) {
276
+ return state;
277
+ }
278
+ const {
279
+ activeTabId,
280
+ tabs
281
+ } = group;
282
+ if (activeTabId === undefined) {
283
+ return state;
284
+ }
285
+ const activeTabIndex = tabs.findIndex(tab => tab.id === activeTabId);
286
+ if (activeTabIndex === -1) {
287
+ return state;
288
+ }
289
+ const newTabs = tabs.slice(0, activeTabIndex + 1);
290
+ if (newTabs.length === tabs.length) {
291
+ return state;
292
+ }
293
+ const newGroups = groups.map(g => {
294
+ if (g.id === groupId) {
295
+ return {
296
+ ...g,
297
+ isEmpty: newTabs.length === 0,
298
+ tabs: newTabs
299
+ };
300
+ }
301
+ return g;
302
+ });
303
+ return {
304
+ ...state,
305
+ layout: {
306
+ ...layout,
307
+ groups: newGroups
308
+ }
309
+ };
310
+ };
311
+
312
+ const None$1 = 'none';
313
+
314
+ const Button$2 = 1;
91
315
  const Div = 4;
92
316
  const Span = 8;
93
317
  const Text = 12;
@@ -95,9 +319,10 @@ const Img = 17;
95
319
  const Pre = 51;
96
320
  const Reference = 100;
97
321
 
98
- const Button = 'event.button';
322
+ const Button$1 = 'event.button';
99
323
  const ClientX = 'event.clientX';
100
324
  const ClientY = 'event.clientY';
325
+ const EventTargetClassName = 'event.target.className';
101
326
  const TargetName = 'event.target.name';
102
327
 
103
328
  const Tab = 13;
@@ -1433,10 +1658,17 @@ const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
1433
1658
  await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
1434
1659
  };
1435
1660
 
1436
- const handleAttach = async command => {
1437
- // TODO find a better way to append editors
1438
- const parentNodeSelector = '.editor-groups-container';
1439
- await invoke('Layout.attachViewlet', parentNodeSelector, command.instanceId);
1661
+ const copyPath$1 = async (state, path) => {
1662
+ string(path);
1663
+ await invoke('ClipBoard.writeText', path);
1664
+ return state;
1665
+ };
1666
+
1667
+ const copyRelativePath$1 = async (state, path) => {
1668
+ string(path);
1669
+ const relativePath = await invoke('Workspace.pathBaseName', path);
1670
+ await invoke('ClipBoard.writeText', relativePath);
1671
+ return state;
1440
1672
  };
1441
1673
 
1442
1674
  const {
@@ -1448,25 +1680,89 @@ const {
1448
1680
  wrapGetter
1449
1681
  } = create$6();
1450
1682
 
1451
- const create$1 = () => {
1452
- return Math.random();
1453
- };
1454
-
1455
- const updateTab = (state, tabId, updates) => {
1456
- const {
1457
- layout
1458
- } = state;
1459
- const {
1460
- groups
1461
- } = layout;
1462
- const updatedGroups = groups.map(group => {
1463
- const tabIndex = group.tabs.findIndex(t => t.id === tabId);
1464
- if (tabIndex === -1) {
1465
- return group;
1466
- }
1467
- const updatedTabs = group.tabs.map((tab, index) => {
1468
- if (index === tabIndex) {
1469
- return {
1683
+ const create$1 = (uid, uri, x, y, width, height, platform, assetDir, tabHeight = 35) => {
1684
+ const state = {
1685
+ assetDir,
1686
+ fileIconCache: {},
1687
+ height,
1688
+ iframes: [],
1689
+ initial: true,
1690
+ layout: {
1691
+ activeGroupId: undefined,
1692
+ direction: 'horizontal',
1693
+ groups: []
1694
+ },
1695
+ platform,
1696
+ splitButtonEnabled: false,
1697
+ tabHeight,
1698
+ uid,
1699
+ width,
1700
+ workspaceuri: uri,
1701
+ x,
1702
+ y
1703
+ };
1704
+ set(uid, state, state);
1705
+ };
1706
+
1707
+ const isEqual = (oldState, newState) => {
1708
+ return oldState.layout === newState.layout;
1709
+ };
1710
+
1711
+ const RenderItems = 4;
1712
+ const RenderIncremental = 11;
1713
+
1714
+ const modules = [isEqual];
1715
+ const numbers = [RenderIncremental];
1716
+
1717
+ const diff = (oldState, newState) => {
1718
+ const diffResult = [];
1719
+ for (let i = 0; i < modules.length; i++) {
1720
+ const fn = modules[i];
1721
+ if (!fn(oldState, newState)) {
1722
+ diffResult.push(numbers[i]);
1723
+ }
1724
+ }
1725
+ return diffResult;
1726
+ };
1727
+
1728
+ const diff2 = uid => {
1729
+ const {
1730
+ newState,
1731
+ oldState
1732
+ } = get(uid);
1733
+ const result = diff(oldState, newState);
1734
+ return result;
1735
+ };
1736
+
1737
+ const createViewlet = async (viewletModuleId, editorUid, tabId, bounds, uri) => {
1738
+ await invoke('Layout.createViewlet', viewletModuleId, editorUid, tabId, bounds, uri);
1739
+ };
1740
+
1741
+ const handleAttach = async command => {
1742
+ // TODO find a better way to append editors
1743
+ const parentNodeSelector = '.editor-groups-container';
1744
+ await invoke('Layout.attachViewlet', parentNodeSelector, command.instanceId);
1745
+ };
1746
+
1747
+ const create = () => {
1748
+ return Math.random();
1749
+ };
1750
+
1751
+ const updateTab = (state, tabId, updates) => {
1752
+ const {
1753
+ layout
1754
+ } = state;
1755
+ const {
1756
+ groups
1757
+ } = layout;
1758
+ const updatedGroups = groups.map(group => {
1759
+ const tabIndex = group.tabs.findIndex(t => t.id === tabId);
1760
+ if (tabIndex === -1) {
1761
+ return group;
1762
+ }
1763
+ const updatedTabs = group.tabs.map((tab, index) => {
1764
+ if (index === tabIndex) {
1765
+ return {
1470
1766
  ...tab,
1471
1767
  ...updates
1472
1768
  };
@@ -1525,7 +1821,7 @@ const loadTabContentAsync = async (tabId, path, requestId, getLatestState) => {
1525
1821
  }
1526
1822
 
1527
1823
  // Assign editorUid if tab doesn't have one yet
1528
- const editorUid = latestTab.editorUid === -1 ? create$1() : latestTab.editorUid;
1824
+ const editorUid = latestTab.editorUid === -1 ? create() : latestTab.editorUid;
1529
1825
  return updateTab(latestState, tabId, {
1530
1826
  editorUid,
1531
1827
  errorMessage: undefined,
@@ -1565,7 +1861,7 @@ const createViewletForTab = (state, tabId, viewletModuleId, bounds) => {
1565
1861
  if (tab.editorUid !== -1 || tab.loadingState === 'loading' || tab.loadingState === 'loaded') {
1566
1862
  return state;
1567
1863
  }
1568
- const editorUid = create$1();
1864
+ const editorUid = create();
1569
1865
  const newState = updateTab(state, tabId, {
1570
1866
  editorUid
1571
1867
  });
@@ -1689,387 +1985,94 @@ const executeViewletCommands = async commands => {
1689
1985
  }
1690
1986
  };
1691
1987
 
1692
- const closeTab = (state, groupId, tabId) => {
1988
+ const findTabById = (state, tabId) => {
1693
1989
  const {
1694
1990
  layout
1695
1991
  } = state;
1696
1992
  const {
1697
- activeGroupId,
1698
1993
  groups
1699
1994
  } = layout;
1700
-
1701
- // Find the group to close the tab from
1702
- const groupIndex = groups.findIndex(g => g.id === groupId);
1703
- if (groupIndex === -1) {
1704
- return state;
1705
- }
1706
- const group = groups[groupIndex];
1707
- // Check if the tab exists in the group
1708
- const tabWasRemoved = group.tabs.some(tab => tab.id === tabId);
1709
- if (!tabWasRemoved) {
1710
- return state;
1711
- }
1712
- const newGroups = groups.map(grp => {
1713
- if (grp.id === groupId) {
1714
- const newTabs = grp.tabs.filter(tab => tab.id !== tabId);
1715
- let newActiveTabId = grp.activeTabId;
1716
- if (grp.activeTabId === tabId) {
1717
- const tabIndex = grp.tabs.findIndex(tab => tab.id === tabId);
1718
- if (newTabs.length > 0) {
1719
- newActiveTabId = newTabs[Math.min(tabIndex, newTabs.length - 1)].id;
1720
- } else {
1721
- newActiveTabId = undefined;
1722
- }
1723
- }
1995
+ for (const group of groups) {
1996
+ const tab = group.tabs.find(t => t.id === tabId);
1997
+ if (tab) {
1724
1998
  return {
1725
- ...grp,
1726
- activeTabId: newActiveTabId,
1727
- isEmpty: newTabs.length === 0,
1728
- tabs: newTabs
1999
+ groupId: group.id,
2000
+ tab
1729
2001
  };
1730
2002
  }
1731
- return grp;
1732
- });
1733
-
1734
- // If the group has no tabs left after closing, remove the group
1735
- const groupIsNowEmpty = newGroups[groupIndex].tabs.length === 0;
1736
- if (groupIsNowEmpty) {
1737
- const remainingGroups = newGroups.filter(group => group.id !== groupId);
2003
+ }
2004
+ return undefined;
2005
+ };
1738
2006
 
1739
- // If there are remaining groups, redistribute sizes
1740
- if (remainingGroups.length > 0) {
1741
- const redistributedGroups = remainingGroups.map(grp => ({
1742
- ...grp,
1743
- size: Math.round(100 / remainingGroups.length)
1744
- }));
1745
- const newActiveGroupId = activeGroupId === groupId ? remainingGroups[0]?.id : activeGroupId;
1746
- return {
1747
- ...state,
1748
- layout: {
1749
- ...layout,
1750
- activeGroupId: newActiveGroupId,
1751
- groups: redistributedGroups
1752
- }
1753
- };
1754
- }
2007
+ // Counter for request IDs to handle race conditions
2008
+ let requestIdCounter = 0;
2009
+ const getNextRequestId = () => {
2010
+ return ++requestIdCounter;
2011
+ };
1755
2012
 
1756
- // If no remaining groups, return empty layout
1757
- return {
1758
- ...state,
1759
- layout: {
1760
- ...layout,
1761
- activeGroupId: undefined,
1762
- groups: []
1763
- }
2013
+ const startContentLoading = async (oldState, state, tabId, path, requestId) => {
2014
+ try {
2015
+ const getLatestState = () => {
2016
+ return get(state.uid).newState;
1764
2017
  };
2018
+ set(state.uid, oldState, state);
2019
+ const newState = await loadTabContentAsync(tabId, path, requestId, getLatestState);
2020
+ return newState;
2021
+ } catch {
2022
+ // Silently ignore errors - the tab may have been closed or the component unmounted
1765
2023
  }
1766
- return {
1767
- ...state,
1768
- layout: {
1769
- ...layout,
1770
- groups: newGroups
1771
- }
1772
- };
2024
+ return state;
1773
2025
  };
1774
2026
 
1775
- const closeActiveEditor = state => {
1776
- const {
1777
- layout
1778
- } = state;
1779
- const {
1780
- groups
1781
- } = layout;
1782
- const focusedGroup = groups.find(group => group.focused);
1783
- if (!focusedGroup) {
1784
- return state;
2027
+ const shouldLoadContent = tab => {
2028
+ // Load if:
2029
+ // - Has a path (file-based tab)
2030
+ // - Not already loaded or currently loading
2031
+ if (!tab.uri) {
2032
+ return false;
1785
2033
  }
1786
- const {
1787
- activeTabId
1788
- } = focusedGroup;
1789
- if (activeTabId === undefined) {
1790
- return state;
2034
+ if (tab.loadingState === 'loading') {
2035
+ return false;
1791
2036
  }
1792
- return closeTab(state, focusedGroup.id, activeTabId);
1793
- };
1794
-
1795
- const closeAll$1 = state => {
1796
- return {
1797
- ...state,
1798
- layout: {
1799
- ...state.layout,
1800
- activeGroupId: undefined,
1801
- groups: []
1802
- }
1803
- };
2037
+ if (tab.loadingState === 'loaded') {
2038
+ return false;
2039
+ }
2040
+ return true;
1804
2041
  };
1805
-
1806
- const closeFocusedTab = state => {
2042
+ const getActiveTabId$1 = state => {
1807
2043
  const {
1808
2044
  layout
1809
2045
  } = state;
1810
2046
  const {
2047
+ activeGroupId,
1811
2048
  groups
1812
2049
  } = layout;
1813
- const focusedGroup = groups.find(group => group.focused);
1814
- if (!focusedGroup) {
1815
- return state;
1816
- }
1817
- const {
1818
- activeTabId
1819
- } = focusedGroup;
1820
- if (activeTabId === undefined) {
1821
- return state;
1822
- }
1823
- return closeTab(state, focusedGroup.id, activeTabId);
2050
+ const activeGroup = groups.find(g => g.id === activeGroupId);
2051
+ return activeGroup?.activeTabId;
1824
2052
  };
1825
-
1826
- const closeOtherTabs = (state, groupId) => {
2053
+ const selectTab = async (state, groupIndex, index) => {
1827
2054
  const {
1828
- layout
2055
+ layout,
2056
+ uid
1829
2057
  } = state;
1830
2058
  const {
1831
- activeGroupId,
1832
2059
  groups
1833
2060
  } = layout;
1834
- const targetGroupId = groupId ?? activeGroupId;
1835
- if (targetGroupId === undefined) {
2061
+
2062
+ // Validate indexes
2063
+ if (groupIndex < 0 || groupIndex >= groups.length) {
1836
2064
  return state;
1837
2065
  }
1838
- const group = groups.find(g => g.id === targetGroupId);
1839
- if (!group) {
2066
+ const group = groups[groupIndex];
2067
+ if (index < 0 || index >= group.tabs.length) {
1840
2068
  return state;
1841
2069
  }
1842
- const {
1843
- activeTabId
1844
- } = group;
1845
- if (activeTabId === undefined) {
1846
- return state;
1847
- }
1848
- const newGroups = groups.map(g => {
1849
- if (g.id === targetGroupId) {
1850
- const newTabs = g.tabs.filter(tab => tab.id === activeTabId);
1851
- return {
1852
- ...g,
1853
- activeTabId,
1854
- isEmpty: newTabs.length === 0,
1855
- tabs: newTabs
1856
- };
1857
- }
1858
- return g;
1859
- });
1860
- return {
1861
- ...state,
1862
- layout: {
1863
- ...layout,
1864
- groups: newGroups
1865
- }
1866
- };
1867
- };
1868
-
1869
- const closeTabsRight = (state, groupId) => {
1870
- const {
1871
- layout
1872
- } = state;
1873
- const {
1874
- groups
1875
- } = layout;
1876
- const group = groups.find(g => g.id === groupId);
1877
- if (!group) {
1878
- return state;
1879
- }
1880
- const {
1881
- activeTabId,
1882
- tabs
1883
- } = group;
1884
- if (activeTabId === undefined) {
1885
- return state;
1886
- }
1887
- const activeTabIndex = tabs.findIndex(tab => tab.id === activeTabId);
1888
- if (activeTabIndex === -1) {
1889
- return state;
1890
- }
1891
- const newTabs = tabs.slice(0, activeTabIndex + 1);
1892
- if (newTabs.length === tabs.length) {
1893
- return state;
1894
- }
1895
- const newGroups = groups.map(g => {
1896
- if (g.id === groupId) {
1897
- return {
1898
- ...g,
1899
- isEmpty: newTabs.length === 0,
1900
- tabs: newTabs
1901
- };
1902
- }
1903
- return g;
1904
- });
1905
- return {
1906
- ...state,
1907
- layout: {
1908
- ...layout,
1909
- groups: newGroups
1910
- }
1911
- };
1912
- };
1913
-
1914
- const copyPath$1 = async (state, path) => {
1915
- string(path);
1916
- await invoke('ClipBoard.writeText', path);
1917
- return state;
1918
- };
1919
-
1920
- const copyRelativePath$1 = async (state, path) => {
1921
- string(path);
1922
- const relativePath = await invoke('Workspace.pathBaseName', path);
1923
- await invoke('ClipBoard.writeText', relativePath);
1924
- return state;
1925
- };
1926
-
1927
- const create = (uid, uri, x, y, width, height, platform, assetDir, tabHeight = 35) => {
1928
- const state = {
1929
- assetDir,
1930
- fileIconCache: {},
1931
- height,
1932
- iframes: [],
1933
- initial: true,
1934
- layout: {
1935
- activeGroupId: undefined,
1936
- direction: 'horizontal',
1937
- groups: []
1938
- },
1939
- platform,
1940
- splitButtonEnabled: false,
1941
- tabHeight,
1942
- uid,
1943
- width,
1944
- workspaceuri: uri,
1945
- x,
1946
- y
1947
- };
1948
- set(uid, state, state);
1949
- };
1950
-
1951
- const isEqual = (oldState, newState) => {
1952
- return oldState.layout === newState.layout;
1953
- };
1954
-
1955
- const RenderItems = 4;
1956
- const RenderIncremental = 11;
1957
-
1958
- const modules = [isEqual];
1959
- const numbers = [RenderIncremental];
1960
-
1961
- const diff = (oldState, newState) => {
1962
- const diffResult = [];
1963
- for (let i = 0; i < modules.length; i++) {
1964
- const fn = modules[i];
1965
- if (!fn(oldState, newState)) {
1966
- diffResult.push(numbers[i]);
1967
- }
1968
- }
1969
- return diffResult;
1970
- };
1971
-
1972
- const diff2 = uid => {
1973
- const {
1974
- newState,
1975
- oldState
1976
- } = get(uid);
1977
- const result = diff(oldState, newState);
1978
- return result;
1979
- };
1980
-
1981
- const createViewlet = async (viewletModuleId, editorUid, tabId, bounds, uri) => {
1982
- await invoke('Layout.createViewlet', viewletModuleId, editorUid, tabId, bounds, uri);
1983
- };
1984
-
1985
- const findTabById = (state, tabId) => {
1986
- const {
1987
- layout
1988
- } = state;
1989
- const {
1990
- groups
1991
- } = layout;
1992
- for (const group of groups) {
1993
- const tab = group.tabs.find(t => t.id === tabId);
1994
- if (tab) {
1995
- return {
1996
- groupId: group.id,
1997
- tab
1998
- };
1999
- }
2000
- }
2001
- return undefined;
2002
- };
2003
-
2004
- // Counter for request IDs to handle race conditions
2005
- let requestIdCounter = 0;
2006
- const getNextRequestId = () => {
2007
- return ++requestIdCounter;
2008
- };
2009
-
2010
- const startContentLoading = async (oldState, state, tabId, path, requestId) => {
2011
- try {
2012
- const getLatestState = () => {
2013
- return get(state.uid).newState;
2014
- };
2015
- set(state.uid, oldState, state);
2016
- const newState = await loadTabContentAsync(tabId, path, requestId, getLatestState);
2017
- return newState;
2018
- } catch {
2019
- // Silently ignore errors - the tab may have been closed or the component unmounted
2020
- }
2021
- return state;
2022
- };
2023
-
2024
- const shouldLoadContent = tab => {
2025
- // Load if:
2026
- // - Has a path (file-based tab)
2027
- // - Not already loaded or currently loading
2028
- if (!tab.uri) {
2029
- return false;
2030
- }
2031
- if (tab.loadingState === 'loading') {
2032
- return false;
2033
- }
2034
- if (tab.loadingState === 'loaded') {
2035
- return false;
2036
- }
2037
- return true;
2038
- };
2039
- const getActiveTabId$1 = state => {
2040
- const {
2041
- layout
2042
- } = state;
2043
- const {
2044
- activeGroupId,
2045
- groups
2046
- } = layout;
2047
- const activeGroup = groups.find(g => g.id === activeGroupId);
2048
- return activeGroup?.activeTabId;
2049
- };
2050
- const selectTab = async (state, groupIndex, index) => {
2051
- const {
2052
- layout,
2053
- uid
2054
- } = state;
2055
- const {
2056
- groups
2057
- } = layout;
2058
-
2059
- // Validate indexes
2060
- if (groupIndex < 0 || groupIndex >= groups.length) {
2061
- return state;
2062
- }
2063
- const group = groups[groupIndex];
2064
- if (index < 0 || index >= group.tabs.length) {
2065
- return state;
2066
- }
2067
- const tab = group.tabs[index];
2068
- const groupId = group.id;
2069
- const tabId = tab.id;
2070
-
2071
- // Return same state if this group and tab are already active
2072
- if (layout.activeGroupId === groupId && group.activeTabId === tabId) {
2070
+ const tab = group.tabs[index];
2071
+ const groupId = group.id;
2072
+ const tabId = tab.id;
2073
+
2074
+ // Return same state if this group and tab are already active
2075
+ if (layout.activeGroupId === groupId && group.activeTabId === tabId) {
2073
2076
  return state;
2074
2077
  }
2075
2078
 
@@ -2291,44 +2294,554 @@ const closeEditorGroup = (state, groupId) => {
2291
2294
  activeGroupId: newActiveGroupId,
2292
2295
  groups: redistributedGroups
2293
2296
  }
2294
- };
2295
- };
2297
+ };
2298
+ };
2299
+
2300
+ const getActiveGroup = (groups, activeGroupId) => {
2301
+ return groups.find(g => g.id === activeGroupId);
2302
+ };
2303
+
2304
+ const Left = 'left';
2305
+ const Right = 'right';
2306
+
2307
+ const getActiveTab = state => {
2308
+ const {
2309
+ layout
2310
+ } = state;
2311
+ const {
2312
+ groups
2313
+ } = layout;
2314
+ const activeGroup = groups.find(group => group.focused);
2315
+ if (!activeGroup || !activeGroup.activeTabId) {
2316
+ return undefined;
2317
+ }
2318
+ const activeTab = activeGroup.tabs.find(tab => tab.id === activeGroup.activeTabId);
2319
+ if (!activeTab) {
2320
+ return undefined;
2321
+ }
2322
+ return {
2323
+ groupId: activeGroup.id,
2324
+ tab: activeTab
2325
+ };
2326
+ };
2327
+
2328
+ const handleClickTogglePreview = async state => {
2329
+ const activeTabInfo = getActiveTab(state);
2330
+ if (!activeTabInfo || !activeTabInfo.tab.uri) {
2331
+ return state;
2332
+ }
2333
+ await invoke('Layout.showPreview', activeTabInfo.tab.uri);
2334
+ return state;
2335
+ };
2336
+
2337
+ const getBasename$1 = uri => {
2338
+ const lastSlashIndex = uri.lastIndexOf('/');
2339
+ if (lastSlashIndex === -1) {
2340
+ return uri;
2341
+ }
2342
+ return uri.slice(lastSlashIndex + 1);
2343
+ };
2344
+ const getLabel = uri => {
2345
+ if (uri.startsWith('settings://')) {
2346
+ return 'Settings';
2347
+ }
2348
+ if (uri.startsWith('simple-browser://')) {
2349
+ return 'Simple Browser';
2350
+ }
2351
+ if (uri.startsWith('language-models://')) {
2352
+ return 'Language Models';
2353
+ }
2354
+ return getBasename$1(uri);
2355
+ };
2356
+
2357
+ const createEmptyGroup = (state, uri, requestId) => {
2358
+ const {
2359
+ layout
2360
+ } = state;
2361
+ const {
2362
+ groups
2363
+ } = layout;
2364
+ const groupId = create();
2365
+ const title = getLabel(uri);
2366
+ const tabId = create();
2367
+ const editorUid = create();
2368
+ const newTab = {
2369
+ editorType: 'text',
2370
+ editorUid,
2371
+ errorMessage: '',
2372
+ icon: '',
2373
+ id: tabId,
2374
+ isDirty: false,
2375
+ language: '',
2376
+ loadingState: 'loading',
2377
+ title,
2378
+ uri
2379
+ };
2380
+ const newGroup = {
2381
+ activeTabId: newTab.id,
2382
+ focused: true,
2383
+ id: groupId,
2384
+ isEmpty: false,
2385
+ size: 100,
2386
+ tabs: [newTab]
2387
+ };
2388
+ return {
2389
+ ...state,
2390
+ layout: {
2391
+ ...layout,
2392
+ activeGroupId: groupId,
2393
+ groups: [...groups, newGroup]
2394
+ }
2395
+ };
2396
+ };
2397
+
2398
+ const openTab = (state, groupId, tab) => {
2399
+ const newTab = 'id' in tab && tab.id !== undefined ? tab : {
2400
+ ...tab,
2401
+ id: create()
2402
+ };
2403
+ const {
2404
+ layout
2405
+ } = state;
2406
+ const {
2407
+ groups
2408
+ } = layout;
2409
+ const updatedGroups = groups.map(group => {
2410
+ if (group.id === groupId) {
2411
+ const newTabs = [...group.tabs, newTab];
2412
+ return {
2413
+ ...group,
2414
+ activeTabId: newTab.id,
2415
+ isEmpty: newTabs.length === 0,
2416
+ tabs: newTabs
2417
+ };
2418
+ }
2419
+ return group;
2420
+ });
2421
+ return {
2422
+ ...state,
2423
+ layout: {
2424
+ ...layout,
2425
+ groups: updatedGroups
2426
+ }
2427
+ };
2428
+ };
2429
+
2430
+ const ensureActiveGroup = (state, uri) => {
2431
+ // Find the active group (by activeGroupId or focused flag)
2432
+ const {
2433
+ layout
2434
+ } = state;
2435
+ const {
2436
+ activeGroupId,
2437
+ groups
2438
+ } = layout;
2439
+ const activeGroup = activeGroupId === undefined ? groups.find(group => group.focused) : groups.find(group => group.id === activeGroupId);
2440
+
2441
+ // Generate a request ID for content loading
2442
+ getNextRequestId();
2443
+
2444
+ // If no active group exists, create one
2445
+ let newState;
2446
+ if (activeGroup) {
2447
+ // Create a new tab with the URI in the active group
2448
+ const title = getLabel(uri);
2449
+ const tabId = create();
2450
+ const editorUid = create();
2451
+ const newTab = {
2452
+ editorType: 'text',
2453
+ editorUid,
2454
+ errorMessage: '',
2455
+ icon: '',
2456
+ id: tabId,
2457
+ isDirty: false,
2458
+ language: '',
2459
+ loadingState: 'loading',
2460
+ title,
2461
+ uri: uri
2462
+ };
2463
+ newState = openTab(state, activeGroup.id, newTab);
2464
+ } else {
2465
+ newState = createEmptyGroup(state, uri);
2466
+ }
2467
+ return newState;
2468
+ };
2469
+
2470
+ const findTabByUri = (state, uri) => {
2471
+ const {
2472
+ layout
2473
+ } = state;
2474
+ const {
2475
+ groups
2476
+ } = layout;
2477
+ for (const group of groups) {
2478
+ const tab = group.tabs.find(t => t.uri === uri);
2479
+ if (tab) {
2480
+ return {
2481
+ groupId: group.id,
2482
+ tab
2483
+ };
2484
+ }
2485
+ }
2486
+ return undefined;
2487
+ };
2488
+
2489
+ const focusEditorGroup = (state, groupId) => {
2490
+ const {
2491
+ layout
2492
+ } = state;
2493
+ const {
2494
+ groups
2495
+ } = layout;
2496
+ const updatedGroups = groups.map(group => ({
2497
+ ...group,
2498
+ focused: group.id === groupId
2499
+ }));
2500
+ return {
2501
+ ...state,
2502
+ layout: {
2503
+ ...layout,
2504
+ activeGroupId: groupId,
2505
+ groups: updatedGroups
2506
+ }
2507
+ };
2508
+ };
2509
+
2510
+ const getActiveTabId = state => {
2511
+ const {
2512
+ layout
2513
+ } = state;
2514
+ const {
2515
+ activeGroupId,
2516
+ groups
2517
+ } = layout;
2518
+ const activeGroup = groups.find(g => g.id === activeGroupId);
2519
+ return activeGroup?.activeTabId;
2520
+ };
2521
+
2522
+ const getIconsCached = (dirents, fileIconCache) => {
2523
+ return dirents.map(dirent => fileIconCache[dirent]);
2524
+ };
2525
+
2526
+ const getBasename = uri => {
2527
+ const lastSlashIndex = uri.lastIndexOf('/');
2528
+ if (lastSlashIndex === -1) {
2529
+ return uri;
2530
+ }
2531
+ return uri.slice(lastSlashIndex + 1);
2532
+ };
2533
+ const getMissingTabs = (tabs, fileIconCache) => {
2534
+ const missingTabs = [];
2535
+ for (const tab of tabs) {
2536
+ if (tab.uri && !(tab.uri in fileIconCache)) {
2537
+ missingTabs.push(tab);
2538
+ }
2539
+ }
2540
+ return missingTabs;
2541
+ };
2542
+ const tabToIconRequest = tab => {
2543
+ const uri = tab.uri || '';
2544
+ return {
2545
+ name: getBasename(uri),
2546
+ path: uri,
2547
+ type: 0 // file type
2548
+ };
2549
+ };
2550
+ const getMissingIconRequestsForTabs = (tabs, fileIconCache) => {
2551
+ const missingRequests = getMissingTabs(tabs, fileIconCache);
2552
+ const iconRequests = missingRequests.map(tabToIconRequest);
2553
+ return iconRequests;
2554
+ };
2555
+
2556
+ const Directory = 3;
2557
+ const DirectoryExpanded = 4;
2558
+
2559
+ const getSimpleIconRequestType = direntType => {
2560
+ if (direntType === Directory || direntType === DirectoryExpanded) {
2561
+ return 2;
2562
+ }
2563
+ return 1;
2564
+ };
2565
+
2566
+ const toSimpleIconRequest = request => {
2567
+ return {
2568
+ name: request.name,
2569
+ type: getSimpleIconRequestType(request.type)
2570
+ };
2571
+ };
2572
+
2573
+ const requestFileIcons = async requests => {
2574
+ if (requests.length === 0) {
2575
+ return [];
2576
+ }
2577
+ const simpleRequests = requests.map(toSimpleIconRequest);
2578
+ const icons = await getIcons(simpleRequests);
2579
+ return icons;
2580
+ };
2581
+
2582
+ const updateIconCache = (iconCache, missingRequests, newIcons) => {
2583
+ if (missingRequests.length === 0) {
2584
+ return iconCache;
2585
+ }
2586
+ const newFileIconCache = {
2587
+ ...iconCache
2588
+ };
2589
+ for (let i = 0; i < missingRequests.length; i++) {
2590
+ const request = missingRequests[i];
2591
+ const icon = newIcons[i];
2592
+ newFileIconCache[request.path] = icon;
2593
+ }
2594
+ return newFileIconCache;
2595
+ };
2596
+
2597
+ const getFileIconsForTabs = async (tabs, fileIconCache) => {
2598
+ const missingRequests = getMissingIconRequestsForTabs(tabs, fileIconCache);
2599
+ const newIcons = await requestFileIcons(missingRequests);
2600
+ const newFileIconCache = updateIconCache(fileIconCache, missingRequests, newIcons);
2601
+ const tabUris = tabs.map(tab => tab.uri || '');
2602
+ const icons = getIconsCached(tabUris, newFileIconCache);
2603
+ return {
2604
+ icons,
2605
+ newFileIconCache
2606
+ };
2607
+ };
2608
+
2609
+ const getOptionUriOptions = options => {
2610
+ let uri = '';
2611
+ if (typeof options === 'string') {
2612
+ uri = options;
2613
+ } else {
2614
+ const {
2615
+ uri: optionsUri
2616
+ } = options;
2617
+ uri = optionsUri;
2618
+ }
2619
+ return uri;
2620
+ };
2621
+
2622
+ const getViewletModuleId = async uri => {
2623
+ // Query RendererWorker for viewlet module ID (optional, may fail in tests)
2624
+ let viewletModuleId;
2625
+ try {
2626
+ viewletModuleId = await invoke('Layout.getModuleId', uri);
2627
+ } catch {
2628
+ // Viewlet creation is optional - silently ignore if RendererWorker isn't available
2629
+ }
2630
+ return viewletModuleId;
2631
+ };
2632
+
2633
+ const switchTab = (state, groupId, tabId) => {
2634
+ const {
2635
+ layout
2636
+ } = state;
2637
+ const {
2638
+ groups
2639
+ } = layout;
2640
+ const updatedGroups = groups.map(group => {
2641
+ if (group.id === groupId) {
2642
+ const tabExists = group.tabs.some(tab => tab.id === tabId);
2643
+ if (tabExists) {
2644
+ return {
2645
+ ...group,
2646
+ activeTabId: tabId
2647
+ };
2648
+ }
2649
+ }
2650
+ return group;
2651
+ });
2652
+ return {
2653
+ ...state,
2654
+ layout: {
2655
+ ...layout,
2656
+ groups: updatedGroups
2657
+ }
2658
+ };
2659
+ };
2660
+
2661
+ const openUri = async (state, options) => {
2662
+ object(state);
2663
+ const {
2664
+ uid
2665
+ } = state;
2666
+ const uri = getOptionUriOptions(options);
2667
+
2668
+ // Check if a tab with this URI already exists in the passed-in state
2669
+ const existingTab = findTabByUri(state, uri);
2670
+ const shouldRetryExistingTab = existingTab && existingTab.tab.loadingState === 'error';
2671
+ if (existingTab && !shouldRetryExistingTab) {
2672
+ // Tab exists, switch to it and focus its group
2673
+ const focusedState = focusEditorGroup(state, existingTab.groupId);
2674
+ return switchTab(focusedState, existingTab.groupId, existingTab.tab.id);
2675
+ }
2676
+
2677
+ // Get previous active tab ID for viewlet switching
2678
+ const previousTabId = getActiveTabId(state);
2679
+
2680
+ // Check if there's existing state in the global store
2681
+ const stateFromStore = get(uid);
2682
+ let currentState;
2683
+ if (stateFromStore) {
2684
+ const storedState = stateFromStore.newState;
2685
+ // Use the stored state if it has more tabs than the passed-in state
2686
+ // (indicating concurrent calls have already added tabs)
2687
+ // Otherwise use the passed-in state (test setup with initial data)
2688
+ const storedTabCount = storedState.layout.groups.reduce((sum, g) => sum + g.tabs.length, 0);
2689
+ const passedTabCount = state.layout.groups.reduce((sum, g) => sum + g.tabs.length, 0);
2690
+ if (storedTabCount > passedTabCount) {
2691
+ // Stored state has more tabs - concurrent calls have added tabs
2692
+ currentState = storedState;
2693
+ } else {
2694
+ // Passed-in state has same or more tabs, use it (likely fresh test setup)
2695
+ currentState = state;
2696
+ set(uid, state, state);
2697
+ }
2698
+ } else {
2699
+ // No state in store yet, register the passed-in state
2700
+ currentState = state;
2701
+ set(uid, state, state);
2702
+ }
2703
+ let tabId;
2704
+ let stateWithTab;
2705
+ if (shouldRetryExistingTab && existingTab) {
2706
+ const focusedState = focusEditorGroup(currentState, existingTab.groupId);
2707
+ stateWithTab = updateTab(focusedState, existingTab.tab.id, {
2708
+ errorMessage: '',
2709
+ loadingState: 'loading'
2710
+ });
2711
+ tabId = existingTab.tab.id;
2712
+ } else {
2713
+ // Add tab to state BEFORE any async calls to prevent race conditions
2714
+ stateWithTab = ensureActiveGroup(currentState, uri);
2715
+ tabId = getActiveTabId(stateWithTab);
2716
+ }
2717
+
2718
+ // Save state immediately after adding tab or retrying
2719
+ set(uid, state, stateWithTab);
2720
+ try {
2721
+ const viewletModuleId = await getViewletModuleId(uri);
2722
+
2723
+ // After async call, get the latest state to account for any concurrent changes
2724
+ const {
2725
+ newState: stateAfterModuleId
2726
+ } = get(uid);
2727
+ if (!viewletModuleId) {
2728
+ return updateTab(stateAfterModuleId, tabId, {
2729
+ errorMessage: 'Could not determine editor type for this URI',
2730
+ loadingState: 'error'
2731
+ });
2732
+ }
2733
+
2734
+ // Calculate bounds: use main area bounds minus tab height
2735
+ const bounds = {
2736
+ height: stateAfterModuleId.height - stateAfterModuleId.tabHeight,
2737
+ width: stateAfterModuleId.width,
2738
+ x: stateAfterModuleId.x,
2739
+ y: stateAfterModuleId.y + stateAfterModuleId.tabHeight
2740
+ };
2741
+ const stateWithViewlet = createViewletForTab(stateAfterModuleId, tabId, viewletModuleId, bounds);
2742
+ let intermediateState1 = stateWithViewlet;
2743
+
2744
+ // Switch viewlet (detach old, attach new if ready)
2745
+ const {
2746
+ newState: switchedState
2747
+ } = switchViewlet(intermediateState1, previousTabId, tabId);
2748
+ intermediateState1 = switchedState;
2749
+ set(uid, state, intermediateState1);
2750
+
2751
+ // Get the tab to extract editorUid
2752
+ const tabWithViewlet = findTabById(intermediateState1, tabId);
2753
+ if (!tabWithViewlet) {
2754
+ return intermediateState1;
2755
+ }
2756
+ const {
2757
+ editorUid
2758
+ } = tabWithViewlet.tab;
2759
+ if (editorUid === -1) {
2760
+ throw new Error(`invalid editorUid`);
2761
+ }
2762
+ await createViewlet(viewletModuleId, editorUid, tabId, bounds, uri);
2296
2763
 
2297
- const getActiveGroup = (groups, activeGroupId) => {
2298
- return groups.find(g => g.id === activeGroupId);
2299
- };
2764
+ // After viewlet is created, get the latest state and mark it as ready
2765
+ // This ensures we have any state updates that occurred during viewlet creation
2766
+ const {
2767
+ newState: latestState
2768
+ } = get(uid);
2300
2769
 
2301
- const Left = 'left';
2302
- const Right = 'right';
2770
+ // Attachment is handled automatically by virtual DOM reference nodes
2771
+ const readyState = handleViewletReady(latestState, editorUid);
2303
2772
 
2304
- const getActiveTab = state => {
2305
- const {
2306
- layout
2307
- } = state;
2308
- const {
2309
- groups
2310
- } = layout;
2311
- const activeGroup = groups.find(group => group.focused);
2312
- if (!activeGroup || !activeGroup.activeTabId) {
2313
- return undefined;
2314
- }
2315
- const activeTab = activeGroup.tabs.find(tab => tab.id === activeGroup.activeTabId);
2316
- if (!activeTab) {
2317
- return undefined;
2773
+ // Save state before async icon request
2774
+ set(uid, state, readyState);
2775
+
2776
+ // Request file icon for the newly opened tab
2777
+ try {
2778
+ const newTab = findTabById(readyState, tabId);
2779
+ if (newTab && newTab.tab.uri) {
2780
+ const {
2781
+ newFileIconCache
2782
+ } = await getFileIconsForTabs([newTab.tab], readyState.fileIconCache);
2783
+
2784
+ // After async call, get the latest state again
2785
+ const {
2786
+ newState: stateBeforeIconUpdate
2787
+ } = get(uid);
2788
+ const icon = newFileIconCache[newTab.tab.uri] || '';
2789
+
2790
+ // Update the tab with the icon in the latest state
2791
+ const stateWithIcon = {
2792
+ ...stateBeforeIconUpdate,
2793
+ fileIconCache: newFileIconCache,
2794
+ layout: {
2795
+ ...stateBeforeIconUpdate.layout,
2796
+ groups: stateBeforeIconUpdate.layout.groups.map(group => ({
2797
+ ...group,
2798
+ tabs: group.tabs.map(tab => tab.id === tabId ? {
2799
+ ...tab,
2800
+ icon
2801
+ } : tab)
2802
+ }))
2803
+ }
2804
+ };
2805
+
2806
+ // Save the state with icon update so concurrent calls can see it
2807
+ set(uid, state, stateWithIcon);
2808
+ return stateWithIcon;
2809
+ }
2810
+ } catch {
2811
+ // If icon request fails, continue without icon
2812
+ }
2813
+
2814
+ // Get final latest state
2815
+ const {
2816
+ newState: finalState
2817
+ } = get(uid);
2818
+ return finalState;
2819
+ } catch (error) {
2820
+ const {
2821
+ newState: latestState
2822
+ } = get(uid);
2823
+ const errorMessage = error instanceof Error ? error.message : 'Failed to open URI';
2824
+ const errorState = updateTab(latestState, tabId, {
2825
+ errorMessage,
2826
+ loadingState: 'error'
2827
+ });
2828
+ set(uid, state, errorState);
2829
+ return errorState;
2318
2830
  }
2319
- return {
2320
- groupId: activeGroup.id,
2321
- tab: activeTab
2322
- };
2323
2831
  };
2324
2832
 
2325
- const handleClickTogglePreview = async state => {
2326
- const activeTabInfo = getActiveTab(state);
2327
- if (!activeTabInfo || !activeTabInfo.tab.uri) {
2833
+ const retryOpen = async state => {
2834
+ const activeTabData = getActiveTab(state);
2835
+ if (!activeTabData) {
2328
2836
  return state;
2329
2837
  }
2330
- await invoke('Layout.showPreview', activeTabInfo.tab.uri);
2331
- return state;
2838
+ const {
2839
+ tab
2840
+ } = activeTabData;
2841
+ if (!tab.uri) {
2842
+ return state;
2843
+ }
2844
+ return openUri(state, tab.uri);
2332
2845
  };
2333
2846
 
2334
2847
  const splitEditorGroup$1 = (state, groupId, direction) => {
@@ -2342,7 +2855,7 @@ const splitEditorGroup$1 = (state, groupId, direction) => {
2342
2855
  if (!sourceGroup) {
2343
2856
  return state;
2344
2857
  }
2345
- const newGroupId = create$1();
2858
+ const newGroupId = create();
2346
2859
  const isHorizontalSplit = direction === Left || direction === Right;
2347
2860
  const newLayoutDirection = isHorizontalSplit ? 'horizontal' : 'vertical';
2348
2861
  const updatedGroups = groups.map(group => {
@@ -2400,6 +2913,8 @@ const handleClickAction = async (state, action, rawGroupId) => {
2400
2913
  }
2401
2914
  }
2402
2915
  return state;
2916
+ case 'retry-open':
2917
+ return retryOpen(state);
2403
2918
  case 'split-right':
2404
2919
  return splitEditorGroup$1(state, activeGroup.id, Right);
2405
2920
  case 'toggle-preview':
@@ -2450,112 +2965,33 @@ const handleDoubleClick = async state => {
2450
2965
  return state;
2451
2966
  };
2452
2967
 
2968
+ const EditorGroupHeader = 'EditorGroupHeader';
2969
+ const EditorGroup = 'EditorGroup';
2970
+ const EditorContainer = 'EditorContainer';
2971
+ const EditorGroupActions = 'EditorGroupActions';
2972
+ const EditorGroupActionButton = 'EditorGroupActionButton';
2973
+ const SplitEditorGroupButton = 'SplitEditorGroupButton';
2974
+ const MaskIconPreview = 'MaskIcon MaskIconPreview';
2975
+ const MaskIconCircleFilled = 'MaskIcon MaskIconCircleFilled';
2976
+ const MaskIconClose = 'MaskIcon MaskIconClose';
2977
+ const TextEditor = 'TextEditor';
2978
+ const TextEditorLoading = 'TextEditor TextEditor--loading';
2979
+ const EditorContentLoading = 'EditorContent EditorContent--loading';
2980
+ const TextEditorError = 'TextEditor TextEditor--error';
2981
+ const EditorContentError = 'EditorContent EditorContent--error';
2982
+ const Main = 'Main';
2983
+ const EDITOR_GROUPS_CONTAINER = 'editor-groups-container';
2984
+ const TabIcon = 'TabIcon';
2985
+ const TabTitle = 'TabTitle';
2986
+ const Button = 'Button';
2987
+ const ButtonSecondary = 'ButtonSecondary';
2988
+ const EditorContent = 'EditorContent';
2989
+ const EditorTabCloseButton = 'EditorTabCloseButton';
2990
+ const IconButton = 'IconButton';
2991
+ const MainTab = 'MainTab';
2453
2992
  const MainTabs = 'MainTabs';
2454
-
2455
- const getBasename$1 = uri => {
2456
- const lastSlashIndex = uri.lastIndexOf('/');
2457
- if (lastSlashIndex === -1) {
2458
- return uri;
2459
- }
2460
- return uri.slice(lastSlashIndex + 1);
2461
- };
2462
- const getLabel = uri => {
2463
- if (uri.startsWith('settings://')) {
2464
- return 'Settings';
2465
- }
2466
- if (uri.startsWith('simple-browser://')) {
2467
- return 'Simple Browser';
2468
- }
2469
- if (uri.startsWith('language-models://')) {
2470
- return 'Language Models';
2471
- }
2472
- return getBasename$1(uri);
2473
- };
2474
-
2475
- const createEmptyGroup = (state, uri, requestId) => {
2476
- const {
2477
- layout
2478
- } = state;
2479
- const {
2480
- groups
2481
- } = layout;
2482
- const groupId = create$1();
2483
- const title = getLabel(uri);
2484
- const tabId = create$1();
2485
- const editorUid = create$1();
2486
- const newTab = {
2487
- editorType: 'text',
2488
- editorUid,
2489
- errorMessage: '',
2490
- icon: '',
2491
- id: tabId,
2492
- isDirty: false,
2493
- language: '',
2494
- loadingState: 'loading',
2495
- title,
2496
- uri
2497
- };
2498
- const newGroup = {
2499
- activeTabId: newTab.id,
2500
- focused: true,
2501
- id: groupId,
2502
- isEmpty: false,
2503
- size: 100,
2504
- tabs: [newTab]
2505
- };
2506
- return {
2507
- ...state,
2508
- layout: {
2509
- ...layout,
2510
- activeGroupId: groupId,
2511
- groups: [...groups, newGroup]
2512
- }
2513
- };
2514
- };
2515
-
2516
- const getActiveTabId = state => {
2517
- const {
2518
- layout
2519
- } = state;
2520
- const {
2521
- activeGroupId,
2522
- groups
2523
- } = layout;
2524
- const activeGroup = groups.find(g => g.id === activeGroupId);
2525
- return activeGroup?.activeTabId;
2526
- };
2527
-
2528
- const openTab = (state, groupId, tab) => {
2529
- const newTab = 'id' in tab && tab.id !== undefined ? tab : {
2530
- ...tab,
2531
- id: create$1()
2532
- };
2533
- const {
2534
- layout
2535
- } = state;
2536
- const {
2537
- groups
2538
- } = layout;
2539
- const updatedGroups = groups.map(group => {
2540
- if (group.id === groupId) {
2541
- const newTabs = [...group.tabs, newTab];
2542
- return {
2543
- ...group,
2544
- activeTabId: newTab.id,
2545
- isEmpty: newTabs.length === 0,
2546
- tabs: newTabs
2547
- };
2548
- }
2549
- return group;
2550
- });
2551
- return {
2552
- ...state,
2553
- layout: {
2554
- ...layout,
2555
- groups: updatedGroups
2556
- }
2557
- };
2558
- };
2993
+ const MainTabModified = 'MainTabModified';
2994
+ const MainTabSelected = 'MainTabSelected';
2559
2995
 
2560
2996
  const newFile = async state => {
2561
2997
  object(state);
@@ -2608,8 +3044,8 @@ const newFile = async state => {
2608
3044
  getActiveTabId(newState);
2609
3045
 
2610
3046
  // Create a new empty tab
2611
- const tabId = create$1();
2612
- const editorUid = create$1();
3047
+ const tabId = create();
3048
+ const editorUid = create();
2613
3049
  const newTab = {
2614
3050
  editorType: 'text',
2615
3051
  editorUid,
@@ -2745,101 +3181,14 @@ const show2 = async (uid, menuId, x, y, args) => {
2745
3181
 
2746
3182
  const handleTabContextMenu = async (state, button, x, y) => {
2747
3183
  number(x);
2748
- number(y);
2749
- const {
2750
- uid
2751
- } = state;
2752
- await show2(uid, Tab, x, y, {
2753
- menuId: Tab
2754
- });
2755
- return state;
2756
- };
2757
-
2758
- const getIconsCached = (dirents, fileIconCache) => {
2759
- return dirents.map(dirent => fileIconCache[dirent]);
2760
- };
2761
-
2762
- const getBasename = uri => {
2763
- const lastSlashIndex = uri.lastIndexOf('/');
2764
- if (lastSlashIndex === -1) {
2765
- return uri;
2766
- }
2767
- return uri.slice(lastSlashIndex + 1);
2768
- };
2769
- const getMissingTabs = (tabs, fileIconCache) => {
2770
- const missingTabs = [];
2771
- for (const tab of tabs) {
2772
- if (tab.uri && !(tab.uri in fileIconCache)) {
2773
- missingTabs.push(tab);
2774
- }
2775
- }
2776
- return missingTabs;
2777
- };
2778
- const tabToIconRequest = tab => {
2779
- const uri = tab.uri || '';
2780
- return {
2781
- name: getBasename(uri),
2782
- path: uri,
2783
- type: 0 // file type
2784
- };
2785
- };
2786
- const getMissingIconRequestsForTabs = (tabs, fileIconCache) => {
2787
- const missingRequests = getMissingTabs(tabs, fileIconCache);
2788
- const iconRequests = missingRequests.map(tabToIconRequest);
2789
- return iconRequests;
2790
- };
2791
-
2792
- const Directory = 3;
2793
- const DirectoryExpanded = 4;
2794
-
2795
- const getSimpleIconRequestType = direntType => {
2796
- if (direntType === Directory || direntType === DirectoryExpanded) {
2797
- return 2;
2798
- }
2799
- return 1;
2800
- };
2801
-
2802
- const toSimpleIconRequest = request => {
2803
- return {
2804
- name: request.name,
2805
- type: getSimpleIconRequestType(request.type)
2806
- };
2807
- };
2808
-
2809
- const requestFileIcons = async requests => {
2810
- if (requests.length === 0) {
2811
- return [];
2812
- }
2813
- const simpleRequests = requests.map(toSimpleIconRequest);
2814
- const icons = await getIcons(simpleRequests);
2815
- return icons;
2816
- };
2817
-
2818
- const updateIconCache = (iconCache, missingRequests, newIcons) => {
2819
- if (missingRequests.length === 0) {
2820
- return iconCache;
2821
- }
2822
- const newFileIconCache = {
2823
- ...iconCache
2824
- };
2825
- for (let i = 0; i < missingRequests.length; i++) {
2826
- const request = missingRequests[i];
2827
- const icon = newIcons[i];
2828
- newFileIconCache[request.path] = icon;
2829
- }
2830
- return newFileIconCache;
2831
- };
2832
-
2833
- const getFileIconsForTabs = async (tabs, fileIconCache) => {
2834
- const missingRequests = getMissingIconRequestsForTabs(tabs, fileIconCache);
2835
- const newIcons = await requestFileIcons(missingRequests);
2836
- const newFileIconCache = updateIconCache(fileIconCache, missingRequests, newIcons);
2837
- const tabUris = tabs.map(tab => tab.uri || '');
2838
- const icons = getIconsCached(tabUris, newFileIconCache);
2839
- return {
2840
- icons,
2841
- newFileIconCache
2842
- };
3184
+ number(y);
3185
+ const {
3186
+ uid
3187
+ } = state;
3188
+ await show2(uid, Tab, x, y, {
3189
+ menuId: Tab
3190
+ });
3191
+ return state;
2843
3192
  };
2844
3193
 
2845
3194
  const getAllTabs = layout => {
@@ -3017,7 +3366,7 @@ const createViewlets = async (layout, viewletModuleIds, bounds) => {
3017
3366
  for (const group of layout.groups) {
3018
3367
  const activeTab = group.tabs.find(tab => tab.id === group.activeTabId);
3019
3368
  if (activeTab && viewletModuleIds[activeTab.id]) {
3020
- const editorUid = activeTab.editorUid === -1 ? create$1() : activeTab.editorUid;
3369
+ const editorUid = activeTab.editorUid === -1 ? create() : activeTab.editorUid;
3021
3370
  editorUids[activeTab.id] = editorUid;
3022
3371
  await createViewlet(viewletModuleIds[activeTab.id], editorUid, activeTab.id, bounds, activeTab.uri);
3023
3372
  }
@@ -3025,17 +3374,6 @@ const createViewlets = async (layout, viewletModuleIds, bounds) => {
3025
3374
  return editorUids;
3026
3375
  };
3027
3376
 
3028
- const getViewletModuleId = async uri => {
3029
- // Query RendererWorker for viewlet module ID (optional, may fail in tests)
3030
- let viewletModuleId;
3031
- try {
3032
- viewletModuleId = await invoke('Layout.getModuleId', uri);
3033
- } catch {
3034
- // Viewlet creation is optional - silently ignore if RendererWorker isn't available
3035
- }
3036
- return viewletModuleId;
3037
- };
3038
-
3039
3377
  const getViewletModuleIds = async layout => {
3040
3378
  const viewletModuleIds = {};
3041
3379
  for (const group of layout.groups) {
@@ -3289,273 +3627,6 @@ const getMenuEntries = async (state, props) => {
3289
3627
  }
3290
3628
  };
3291
3629
 
3292
- const ensureActiveGroup = (state, uri) => {
3293
- // Find the active group (by activeGroupId or focused flag)
3294
- const {
3295
- layout
3296
- } = state;
3297
- const {
3298
- activeGroupId,
3299
- groups
3300
- } = layout;
3301
- const activeGroup = activeGroupId === undefined ? groups.find(group => group.focused) : groups.find(group => group.id === activeGroupId);
3302
-
3303
- // Generate a request ID for content loading
3304
- getNextRequestId();
3305
-
3306
- // If no active group exists, create one
3307
- let newState;
3308
- if (activeGroup) {
3309
- // Create a new tab with the URI in the active group
3310
- const title = getLabel(uri);
3311
- const tabId = create$1();
3312
- const editorUid = create$1();
3313
- const newTab = {
3314
- editorType: 'text',
3315
- editorUid,
3316
- errorMessage: '',
3317
- icon: '',
3318
- id: tabId,
3319
- isDirty: false,
3320
- language: '',
3321
- loadingState: 'loading',
3322
- title,
3323
- uri: uri
3324
- };
3325
- newState = openTab(state, activeGroup.id, newTab);
3326
- } else {
3327
- newState = createEmptyGroup(state, uri);
3328
- }
3329
- return newState;
3330
- };
3331
-
3332
- const findTabByUri = (state, uri) => {
3333
- const {
3334
- layout
3335
- } = state;
3336
- const {
3337
- groups
3338
- } = layout;
3339
- for (const group of groups) {
3340
- const tab = group.tabs.find(t => t.uri === uri);
3341
- if (tab) {
3342
- return {
3343
- groupId: group.id,
3344
- tab
3345
- };
3346
- }
3347
- }
3348
- return undefined;
3349
- };
3350
-
3351
- const focusEditorGroup = (state, groupId) => {
3352
- const {
3353
- layout
3354
- } = state;
3355
- const {
3356
- groups
3357
- } = layout;
3358
- const updatedGroups = groups.map(group => ({
3359
- ...group,
3360
- focused: group.id === groupId
3361
- }));
3362
- return {
3363
- ...state,
3364
- layout: {
3365
- ...layout,
3366
- activeGroupId: groupId,
3367
- groups: updatedGroups
3368
- }
3369
- };
3370
- };
3371
-
3372
- const getOptionUriOptions = options => {
3373
- let uri = '';
3374
- if (typeof options === 'string') {
3375
- uri = options;
3376
- } else {
3377
- const {
3378
- uri: optionsUri
3379
- } = options;
3380
- uri = optionsUri;
3381
- }
3382
- return uri;
3383
- };
3384
-
3385
- const switchTab = (state, groupId, tabId) => {
3386
- const {
3387
- layout
3388
- } = state;
3389
- const {
3390
- groups
3391
- } = layout;
3392
- const updatedGroups = groups.map(group => {
3393
- if (group.id === groupId) {
3394
- const tabExists = group.tabs.some(tab => tab.id === tabId);
3395
- if (tabExists) {
3396
- return {
3397
- ...group,
3398
- activeTabId: tabId
3399
- };
3400
- }
3401
- }
3402
- return group;
3403
- });
3404
- return {
3405
- ...state,
3406
- layout: {
3407
- ...layout,
3408
- groups: updatedGroups
3409
- }
3410
- };
3411
- };
3412
-
3413
- const openUri = async (state, options) => {
3414
- object(state);
3415
- const {
3416
- uid
3417
- } = state;
3418
- const uri = getOptionUriOptions(options);
3419
-
3420
- // Check if a tab with this URI already exists in the passed-in state
3421
- const existingTab = findTabByUri(state, uri);
3422
- if (existingTab) {
3423
- // Tab exists, switch to it and focus its group
3424
- const focusedState = focusEditorGroup(state, existingTab.groupId);
3425
- return switchTab(focusedState, existingTab.groupId, existingTab.tab.id);
3426
- }
3427
-
3428
- // Get previous active tab ID for viewlet switching
3429
- getActiveTabId(state);
3430
-
3431
- // Check if there's existing state in the global store
3432
- const stateFromStore = get(uid);
3433
- let currentState;
3434
- if (stateFromStore) {
3435
- const storedState = stateFromStore.newState;
3436
- // Use the stored state if it has more tabs than the passed-in state
3437
- // (indicating concurrent calls have already added tabs)
3438
- // Otherwise use the passed-in state (test setup with initial data)
3439
- const storedTabCount = storedState.layout.groups.reduce((sum, g) => sum + g.tabs.length, 0);
3440
- const passedTabCount = state.layout.groups.reduce((sum, g) => sum + g.tabs.length, 0);
3441
- if (storedTabCount > passedTabCount) {
3442
- // Stored state has more tabs - concurrent calls have added tabs
3443
- currentState = storedState;
3444
- } else {
3445
- // Passed-in state has same or more tabs, use it (likely fresh test setup)
3446
- currentState = state;
3447
- set(uid, state, state);
3448
- }
3449
- } else {
3450
- // No state in store yet, register the passed-in state
3451
- currentState = state;
3452
- set(uid, state, state);
3453
- }
3454
-
3455
- // Add tab to state BEFORE any async calls to prevent race conditions
3456
- const newState = ensureActiveGroup(currentState, uri);
3457
- const tabId = getActiveTabId(newState);
3458
-
3459
- // Save state immediately after adding tab
3460
- set(uid, state, newState);
3461
- const viewletModuleId = await getViewletModuleId(uri);
3462
-
3463
- // After async call, get the latest state to account for any concurrent changes
3464
- const {
3465
- newState: stateAfterModuleId
3466
- } = get(uid);
3467
- if (!viewletModuleId) {
3468
- // TODO display some kind of errro that editor couldn't be opened
3469
- return stateAfterModuleId;
3470
- }
3471
-
3472
- // Calculate bounds: use main area bounds minus tab height
3473
- const bounds = {
3474
- height: stateAfterModuleId.height - stateAfterModuleId.tabHeight,
3475
- width: stateAfterModuleId.width,
3476
- x: stateAfterModuleId.x,
3477
- y: stateAfterModuleId.y + stateAfterModuleId.tabHeight
3478
- };
3479
- const stateWithViewlet = createViewletForTab(stateAfterModuleId, tabId);
3480
- let intermediateState1 = stateWithViewlet;
3481
-
3482
- // Switch viewlet (detach old, attach new if ready)
3483
- const {
3484
- newState: switchedState
3485
- } = switchViewlet(intermediateState1);
3486
- intermediateState1 = switchedState;
3487
- set(uid, state, intermediateState1);
3488
-
3489
- // Get the tab to extract editorUid
3490
- const tabWithViewlet = findTabById(intermediateState1, tabId);
3491
- if (!tabWithViewlet) {
3492
- return intermediateState1;
3493
- }
3494
- const {
3495
- editorUid
3496
- } = tabWithViewlet.tab;
3497
- if (editorUid === -1) {
3498
- throw new Error(`invalid editorUid`);
3499
- }
3500
- await createViewlet(viewletModuleId, editorUid, tabId, bounds, uri);
3501
-
3502
- // After viewlet is created, get the latest state and mark it as ready
3503
- // This ensures we have any state updates that occurred during viewlet creation
3504
- const {
3505
- newState: latestState
3506
- } = get(uid);
3507
-
3508
- // Attachment is handled automatically by virtual DOM reference nodes
3509
- const readyState = handleViewletReady(latestState, editorUid);
3510
-
3511
- // Save state before async icon request
3512
- set(uid, state, readyState);
3513
-
3514
- // Request file icon for the newly opened tab
3515
- try {
3516
- const newTab = findTabById(readyState, tabId);
3517
- if (newTab && newTab.tab.uri) {
3518
- const {
3519
- newFileIconCache
3520
- } = await getFileIconsForTabs([newTab.tab], readyState.fileIconCache);
3521
-
3522
- // After async call, get the latest state again
3523
- const {
3524
- newState: stateBeforeIconUpdate
3525
- } = get(uid);
3526
- const icon = newFileIconCache[newTab.tab.uri] || '';
3527
-
3528
- // Update the tab with the icon in the latest state
3529
- const stateWithIcon = {
3530
- ...stateBeforeIconUpdate,
3531
- fileIconCache: newFileIconCache,
3532
- layout: {
3533
- ...stateBeforeIconUpdate.layout,
3534
- groups: stateBeforeIconUpdate.layout.groups.map(group => ({
3535
- ...group,
3536
- tabs: group.tabs.map(tab => tab.id === tabId ? {
3537
- ...tab,
3538
- icon
3539
- } : tab)
3540
- }))
3541
- }
3542
- };
3543
-
3544
- // Save the state with icon update so concurrent calls can see it
3545
- set(uid, state, stateWithIcon);
3546
- return stateWithIcon;
3547
- }
3548
- } catch {
3549
- // If icon request fails, continue without icon
3550
- }
3551
-
3552
- // Get final latest state
3553
- const {
3554
- newState: finalState
3555
- } = get(uid);
3556
- return finalState;
3557
- };
3558
-
3559
3630
  const refresh = state => {
3560
3631
  return {
3561
3632
  ...state
@@ -3858,41 +3929,51 @@ const diffTree = (oldNodes, newNodes) => {
3858
3929
  return removeTrailingNavigationPatches(patches);
3859
3930
  };
3860
3931
 
3861
- const CSS_CLASSES = {
3862
- EDITOR_GROUPS_CONTAINER: 'editor-groups-container'};
3863
-
3864
3932
  const renderContent = content => {
3865
3933
  return [{
3866
3934
  childCount: 1,
3867
- className: 'TextEditor',
3935
+ className: TextEditor,
3868
3936
  type: Div
3869
3937
  }, {
3870
3938
  childCount: 1,
3871
- className: 'EditorContent',
3939
+ className: EditorContent,
3872
3940
  type: Pre
3873
3941
  }, text(content)];
3874
3942
  };
3875
3943
 
3944
+ const HandleClickAction = 10;
3945
+ const HandleClick = 11;
3946
+ const HandleClickClose = 12;
3947
+ const HandleClickTab = 13;
3948
+ const HandleTabContextMenu = 14;
3949
+ const HandleHeaderDoubleClick = 15;
3950
+
3876
3951
  const renderError = errorMessage => {
3877
3952
  return [{
3878
- childCount: 1,
3879
- className: 'TextEditor TextEditor--error',
3953
+ childCount: 2,
3954
+ className: TextEditorError,
3880
3955
  type: Div
3881
3956
  }, {
3882
- childCount: 1,
3883
- className: 'EditorContent EditorContent--error',
3957
+ childCount: 2,
3958
+ className: EditorContentError,
3884
3959
  type: Div
3885
- }, text(`Error: ${errorMessage}`)];
3960
+ }, text(`Error: ${errorMessage}`), {
3961
+ childCount: 1,
3962
+ className: `${Button} ${ButtonSecondary}`,
3963
+ 'data-action': 'retry-open',
3964
+ onClick: HandleClickAction,
3965
+ type: Button$2
3966
+ }, text('Retry')];
3886
3967
  };
3887
3968
 
3888
3969
  const renderLoading = () => {
3889
3970
  return [{
3890
3971
  childCount: 1,
3891
- className: 'TextEditor TextEditor--loading',
3972
+ className: TextEditorLoading,
3892
3973
  type: Div
3893
3974
  }, {
3894
3975
  childCount: 1,
3895
- className: 'EditorContent EditorContent--loading',
3976
+ className: EditorContentLoading,
3896
3977
  type: Div
3897
3978
  }, text('Loading...')];
3898
3979
  };
@@ -3933,50 +4014,43 @@ const renderEditor = tab => {
3933
4014
  return renderContent('');
3934
4015
  };
3935
4016
 
3936
- const HandleClickAction = 10;
3937
- const HandleClick = 11;
3938
- const HandleClickClose = 12;
3939
- const HandleClickTab = 13;
3940
- const HandleTabContextMenu = 14;
3941
- const HandleHeaderDoubleClick = 15;
3942
-
3943
4017
  const renderTabActions = (isDirty, tabIndex, groupIndex) => {
3944
4018
  if (isDirty) {
3945
4019
  return [{
3946
4020
  childCount: 1,
3947
- className: 'EditorTabCloseButton',
4021
+ className: EditorTabCloseButton,
3948
4022
  'data-groupIndex': groupIndex,
3949
4023
  'data-index': tabIndex,
3950
4024
  type: Div
3951
4025
  }, {
3952
4026
  childCount: 0,
3953
- className: 'MaskIcon MaskIconCircleFilled',
4027
+ className: MaskIconCircleFilled,
3954
4028
  type: Div
3955
4029
  }];
3956
4030
  }
3957
4031
  return [{
3958
4032
  'aria-label': 'Close',
3959
4033
  childCount: 1,
3960
- className: 'EditorTabCloseButton',
4034
+ className: EditorTabCloseButton,
3961
4035
  'data-groupIndex': groupIndex,
3962
4036
  'data-index': tabIndex,
3963
4037
  onClick: HandleClickClose,
3964
- type: Button$1
4038
+ type: Button$2
3965
4039
  }, {
3966
4040
  childCount: 0,
3967
- className: 'MaskIcon MaskIconClose',
4041
+ className: MaskIconClose,
3968
4042
  type: Div
3969
4043
  }];
3970
4044
  };
3971
4045
 
3972
4046
  const renderTab = (tab, isActive, tabIndex, groupIndex) => {
3973
4047
  const closeButtonNodes = renderTabActions(tab.isDirty, tabIndex, groupIndex);
3974
- let className = 'MainTab';
4048
+ let className = MainTab;
3975
4049
  if (isActive) {
3976
- className += ' MainTabSelected';
4050
+ className += ' ' + MainTabSelected;
3977
4051
  }
3978
4052
  if (tab.isDirty) {
3979
- className += ' MainTabModified';
4053
+ className += ' ' + MainTabModified;
3980
4054
  }
3981
4055
  return [{
3982
4056
  'aria-selected': isActive,
@@ -3991,13 +4065,13 @@ const renderTab = (tab, isActive, tabIndex, groupIndex) => {
3991
4065
  type: Div
3992
4066
  }, {
3993
4067
  childCount: 0,
3994
- className: 'TabIcon',
3995
- role: 'none',
4068
+ className: TabIcon,
4069
+ role: None$1,
3996
4070
  src: tab.icon,
3997
4071
  type: Img
3998
4072
  }, {
3999
4073
  childCount: 1,
4000
- className: 'TabTitle',
4074
+ className: TabTitle,
4001
4075
  type: Span
4002
4076
  }, text(tab.title), ...closeButtonNodes];
4003
4077
  };
@@ -4005,7 +4079,7 @@ const renderTab = (tab, isActive, tabIndex, groupIndex) => {
4005
4079
  const getTabsVirtualDom = (group, groupIndex, tabsChildCount) => {
4006
4080
  return [{
4007
4081
  childCount: tabsChildCount,
4008
- className: 'MainTabs',
4082
+ className: MainTabs,
4009
4083
  role: 'tablist',
4010
4084
  type: Div
4011
4085
  }, ...group.tabs.flatMap((tab, tabIndex) => renderTab(tab, tab.id === group.activeTabId, tabIndex, groupIndex))];
@@ -4029,34 +4103,34 @@ const renderEditorGroupActions = (group, groupIndex, splitButtonEnabled) => {
4029
4103
  buttons.push({
4030
4104
  ariaLabel: 'Preview',
4031
4105
  childCount: 1,
4032
- className: 'IconButton',
4106
+ className: IconButton,
4033
4107
  'data-action': 'toggle-preview',
4034
4108
  'data-groupId': String(group.id),
4035
4109
  name: 'toggle-preview',
4036
4110
  onClick: HandleClickAction,
4037
4111
  title: togglePreview(),
4038
- type: Button$1
4112
+ type: Button$2
4039
4113
  }, {
4040
4114
  childCount: 0,
4041
- className: 'MaskIcon MaskIconPreview',
4115
+ className: MaskIconPreview,
4042
4116
  type: Div
4043
4117
  });
4044
4118
  }
4045
4119
  if (splitButtonEnabled) {
4046
4120
  buttons.push({
4047
4121
  childCount: 1,
4048
- className: 'EditorGroupActionButton SplitEditorGroupButton',
4122
+ className: EditorGroupActionButton + ' ' + SplitEditorGroupButton,
4049
4123
  'data-action': 'split-right',
4050
4124
  'data-groupId': String(group.id),
4051
4125
  onClick: HandleClickAction,
4052
4126
  title: splitEditorGroup(),
4053
- type: Button$1
4127
+ type: Button$2
4054
4128
  }, text('split'));
4055
4129
  }
4056
4130
  return [{
4057
4131
  childCount: buttons.length / 2,
4058
4132
  // Each button has 2 nodes (button + text)
4059
- className: 'EditorGroupActions',
4133
+ className: EditorGroupActions,
4060
4134
  role: 'toolbar',
4061
4135
  type: Div
4062
4136
  }, ...buttons];
@@ -4068,9 +4142,9 @@ const renderEditorGroupHeader = (group, groupIndex, splitButtonEnabled) => {
4068
4142
  const hasActions = actions.length > 0;
4069
4143
  return [{
4070
4144
  childCount: hasActions ? 2 : 1,
4071
- className: 'EditorGroupHeader',
4145
+ className: EditorGroupHeader,
4072
4146
  onDblClick: HandleHeaderDoubleClick,
4073
- role: 'none',
4147
+ role: None$1,
4074
4148
  type: Div
4075
4149
  }, ...getTabsVirtualDom(group, groupIndex, tabsChildCount), ...actions];
4076
4150
  };
@@ -4079,26 +4153,42 @@ const renderEditorGroup = (group, groupIndex, splitButtonEnabled = false) => {
4079
4153
  const activeTab = group.tabs.find(tab => tab.id === group.activeTabId);
4080
4154
  return [{
4081
4155
  childCount: 2,
4082
- className: 'EditorGroup',
4156
+ className: EditorGroup,
4083
4157
  type: Div
4084
4158
  }, ...renderEditorGroupHeader(group, groupIndex, splitButtonEnabled), {
4085
4159
  childCount: 1,
4086
- className: 'EditorContainer',
4160
+ className: EditorContainer,
4087
4161
  type: Div
4088
4162
  }, ...renderEditor(activeTab)];
4089
4163
  };
4090
4164
 
4165
+ const renderSash = direction => {
4166
+ return {
4167
+ childCount: 0,
4168
+ className: direction === 'vertical' ? 'SashVertical' : 'SashHorizontal',
4169
+ type: Div
4170
+ };
4171
+ };
4172
+
4091
4173
  const getMainAreaVirtualDom = (layout, splitButtonEnabled = false) => {
4174
+ const children = [];
4175
+ for (let i = 0; i < layout.groups.length; i++) {
4176
+ if (i > 0) {
4177
+ // Insert sash between groups
4178
+ children.push(renderSash(layout.direction));
4179
+ }
4180
+ children.push(...renderEditorGroup(layout.groups[i], i, splitButtonEnabled));
4181
+ }
4092
4182
  return [{
4093
4183
  childCount: 1,
4094
- className: 'Main',
4184
+ className: Main,
4095
4185
  type: Div
4096
4186
  }, {
4097
- childCount: layout.groups.length,
4098
- className: CSS_CLASSES.EDITOR_GROUPS_CONTAINER,
4099
- role: 'none',
4187
+ childCount: children.length,
4188
+ className: EDITOR_GROUPS_CONTAINER,
4189
+ role: None$1,
4100
4190
  type: Div
4101
- }, ...layout.groups.flatMap((group, groupIndex) => renderEditorGroup(group, groupIndex, splitButtonEnabled))];
4191
+ }, ...children];
4102
4192
  };
4103
4193
 
4104
4194
  const renderItems = (oldState, newState) => {
@@ -4167,14 +4257,14 @@ const renderEventListeners = () => {
4167
4257
  params: ['handleClickTab', 'event.target.dataset.groupIndex', 'event.target.dataset.index']
4168
4258
  }, {
4169
4259
  name: HandleTabContextMenu,
4170
- params: ['handleTabContextMenu', Button, ClientX, ClientY],
4260
+ params: ['handleTabContextMenu', Button$1, ClientX, ClientY],
4171
4261
  preventDefault: true
4172
4262
  }, {
4173
4263
  name: HandleClickAction,
4174
4264
  params: ['handleClickAction', TargetName, 'event.target.dataset.groupId']
4175
4265
  }, {
4176
4266
  name: HandleHeaderDoubleClick,
4177
- params: ['handleHeaderDoubleClick', 'event.target.className', 'event.target.dataset.groupId']
4267
+ params: ['handleHeaderDoubleClick', EventTargetClassName, 'event.target.dataset.groupId']
4178
4268
  }];
4179
4269
  };
4180
4270
 
@@ -4239,7 +4329,7 @@ const splitDown = (state, groupId) => {
4239
4329
 
4240
4330
  // If there are no groups, create an initial empty group first
4241
4331
  if (groups.length === 0) {
4242
- const initialGroupId = create$1();
4332
+ const initialGroupId = create();
4243
4333
  const initialGroup = {
4244
4334
  activeTabId: undefined,
4245
4335
  focused: true,
@@ -4276,7 +4366,7 @@ const splitRight = (state, groupId) => {
4276
4366
 
4277
4367
  // If there are no groups, create an initial empty group first
4278
4368
  if (groups.length === 0) {
4279
- const initialGroupId = create$1();
4369
+ const initialGroupId = create();
4280
4370
  const initialGroup = {
4281
4371
  activeTabId: undefined,
4282
4372
  focused: true,
@@ -4308,7 +4398,7 @@ const commandMap = {
4308
4398
  'MainArea.closeTabsRight': wrapCommand(closeTabsRight),
4309
4399
  'MainArea.copyPath': wrapCommand(copyPath$1),
4310
4400
  'MainArea.copyRelativePath': wrapCommand(copyRelativePath$1),
4311
- 'MainArea.create': create,
4401
+ 'MainArea.create': create$1,
4312
4402
  'MainArea.diff2': diff2,
4313
4403
  'MainArea.focusNext': wrapCommand(focusNextTab),
4314
4404
  'MainArea.focusNextTab': wrapCommand(focusNextTab),