@lvce-editor/explorer-view 7.25.0 → 7.27.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.
@@ -2816,6 +2816,7 @@ const collapseAll = async state => {
2816
2816
  const newDirents = items.filter(isTopLevel).map(toCollapsedDirent);
2817
2817
  return {
2818
2818
  ...state,
2819
+ expandedPaths: [],
2819
2820
  focusedIndex: 0,
2820
2821
  items: newDirents
2821
2822
  };
@@ -3717,6 +3718,34 @@ const getVisibleExplorerItems = (items, minLineY, maxLineY, focusedIndex, editin
3717
3718
  return visible;
3718
3719
  };
3719
3720
 
3721
+ const syncExpandedPaths = state => {
3722
+ const {
3723
+ expandedPaths: oldExpandedPaths,
3724
+ items,
3725
+ preserveExpandState
3726
+ } = state;
3727
+ if (!preserveExpandState) {
3728
+ return state;
3729
+ }
3730
+ const expandedPaths = new Set(oldExpandedPaths);
3731
+ for (const item of items) {
3732
+ const type = normalizeDirentType(item.type);
3733
+ if (type === DirectoryExpanded || type === DirectoryExpanding) {
3734
+ expandedPaths.add(item.path);
3735
+ } else if (type === Directory || type === SymLinkFolder) {
3736
+ expandedPaths.delete(item.path);
3737
+ }
3738
+ }
3739
+ const newExpandedPaths = [...expandedPaths];
3740
+ if (newExpandedPaths.length === oldExpandedPaths.length && newExpandedPaths.every((path, index) => path === oldExpandedPaths[index])) {
3741
+ return state;
3742
+ }
3743
+ return {
3744
+ ...state,
3745
+ expandedPaths: newExpandedPaths
3746
+ };
3747
+ };
3748
+
3720
3749
  const {
3721
3750
  get,
3722
3751
  getCommandIds,
@@ -3821,8 +3850,9 @@ const wrapListItemCommandInternal = (fn, queued) => {
3821
3850
  const {
3822
3851
  newState
3823
3852
  } = get(id);
3824
- const rawUpdatedState = await fn(newState, ...args);
3825
- const completion = take(rawUpdatedState);
3853
+ const commandState = await fn(newState, ...args);
3854
+ const completion = take(commandState);
3855
+ const rawUpdatedState = syncExpandedPaths(commandState);
3826
3856
  if (newState === rawUpdatedState) {
3827
3857
  return {
3828
3858
  completion
@@ -3938,6 +3968,7 @@ const create = (id, uri, x, y, width, height, args, parentUid, platform = 0, ass
3938
3968
  errorMessageTop: 0,
3939
3969
  errorMessageWidth: 0,
3940
3970
  excluded: [],
3971
+ expandedPaths: [],
3941
3972
  fileIconCache: Object.create(null),
3942
3973
  fileIconWidth: 16,
3943
3974
  finalDeltaY: 0,
@@ -3969,6 +4000,7 @@ const create = (id, uri, x, y, width, height, args, parentUid, platform = 0, ass
3969
4000
  pathSeparator: Slash,
3970
4001
  platform,
3971
4002
  pointerDownIndex: -1,
4003
+ preserveExpandState: true,
3972
4004
  root: '',
3973
4005
  scrollBarActive: false,
3974
4006
  scrollBarHeight: 0,
@@ -4688,11 +4720,82 @@ const handleArrowRightDirectoryExpanded = (state, dirent) => {
4688
4720
  return state;
4689
4721
  };
4690
4722
 
4723
+ const getPathDirentsMap = async allPaths => {
4724
+ const pathToDirents = Object.create(null);
4725
+ await Promise.all(allPaths.map(async path => {
4726
+ try {
4727
+ const dirents = await readDirWithFileTypes(path);
4728
+ pathToDirents[path] = dirents;
4729
+ } catch {
4730
+ // ignore
4731
+ }
4732
+ }));
4733
+ return pathToDirents;
4734
+ };
4735
+
4736
+ const restoreDirentType = (rawDirentType, path, expandedPaths) => {
4737
+ if (rawDirentType === Directory && expandedPaths.includes(path)) {
4738
+ return DirectoryExpanded;
4739
+ }
4740
+ return rawDirentType;
4741
+ };
4742
+
4743
+ const getProtoMapInternal = (root, pathToDirents, expandedPaths, depth, excluded = [], workspaceRoot = root) => {
4744
+ if (!(root in pathToDirents)) {
4745
+ return [];
4746
+ }
4747
+ const items = (pathToDirents[root] || []).filter(item => !isExcluded(workspaceRoot, join2(root, item.name), excluded));
4748
+ const protoMap = [];
4749
+ for (let i = 0; i < items.length; i++) {
4750
+ const item = items[i];
4751
+ const path = join2(root, item.name);
4752
+ const displayDirent = {
4753
+ depth,
4754
+ icon: '',
4755
+ name: item.name,
4756
+ path,
4757
+ posInSet: i + 1,
4758
+ selected: false,
4759
+ setSize: items.length,
4760
+ type: restoreDirentType(item.type, path, expandedPaths)
4761
+ };
4762
+ const children = getProtoMapInternal(path, pathToDirents, expandedPaths, depth + 1, excluded, workspaceRoot);
4763
+ protoMap.push(displayDirent, ...children);
4764
+ }
4765
+ return protoMap;
4766
+ };
4767
+
4768
+ const sortPathDirentsMap = map => {
4769
+ const sortedMap = Object.create(null);
4770
+ for (const [key, value] of Object.entries(map)) {
4771
+ const sorted = sortExplorerItems(value);
4772
+ sortedMap[key] = sorted;
4773
+ }
4774
+ return sortedMap;
4775
+ };
4776
+
4777
+ const getRestoredChildDirents = async (state, dirent) => {
4778
+ const {
4779
+ excluded,
4780
+ expandedPaths,
4781
+ pathSeparator,
4782
+ preserveExpandState,
4783
+ root
4784
+ } = state;
4785
+ const descendantPrefix = dirent.path.endsWith(pathSeparator) ? dirent.path : `${dirent.path}${pathSeparator}`;
4786
+ const descendantExpandedPaths = preserveExpandState ? expandedPaths.filter(path => path.startsWith(descendantPrefix)) : [];
4787
+ if (descendantExpandedPaths.length === 0) {
4788
+ return getChildDirents(pathSeparator, dirent.path, dirent.depth, excluded, root);
4789
+ }
4790
+ const pathToDirents = await getPathDirentsMap([dirent.path, ...descendantExpandedPaths]);
4791
+ const sortedPathDirents = sortPathDirentsMap(pathToDirents);
4792
+ return getProtoMapInternal(dirent.path, sortedPathDirents, descendantExpandedPaths, dirent.depth + 1, excluded, root);
4793
+ };
4691
4794
  const handleClickDirectory = async (state, dirent, index, keepFocus) => {
4692
4795
  // @ts-ignore
4693
4796
  dirent.type = DirectoryExpanding;
4694
4797
  // TODO handle error
4695
- const dirents = await getChildDirents(state.pathSeparator, dirent.path, dirent.depth, state.excluded, state.root);
4798
+ const dirents = await getRestoredChildDirents(state, dirent);
4696
4799
  const state2 = state;
4697
4800
  if (!state2) {
4698
4801
  return state;
@@ -4989,19 +5092,6 @@ const getExpandedDirents = items => {
4989
5092
  return items.filter(isExpanded);
4990
5093
  };
4991
5094
 
4992
- const getPathDirentsMap = async allPaths => {
4993
- const pathToDirents = Object.create(null);
4994
- await Promise.all(allPaths.map(async path => {
4995
- try {
4996
- const dirents = await readDirWithFileTypes(path);
4997
- pathToDirents[path] = dirents;
4998
- } catch {
4999
- // ignore
5000
- }
5001
- }));
5002
- return pathToDirents;
5003
- };
5004
-
5005
5095
  const getPath = item => {
5006
5096
  return item.path;
5007
5097
  };
@@ -5010,62 +5100,23 @@ const getPaths = items => {
5010
5100
  return items.map(getPath);
5011
5101
  };
5012
5102
 
5013
- const restoreDirentType = (rawDirentType, path, expandedPaths) => {
5014
- if (rawDirentType === Directory && expandedPaths.includes(path)) {
5015
- return DirectoryExpanded;
5016
- }
5017
- return rawDirentType;
5018
- };
5019
-
5020
- const getProtoMapInternal = (root, pathToDirents, expandedPaths, depth, excluded = [], workspaceRoot = root) => {
5021
- if (!(root in pathToDirents)) {
5022
- return [];
5023
- }
5024
- const items = (pathToDirents[root] || []).filter(item => !isExcluded(workspaceRoot, join2(root, item.name), excluded));
5025
- const protoMap = [];
5026
- for (let i = 0; i < items.length; i++) {
5027
- const item = items[i];
5028
- const path = join2(root, item.name);
5029
- const displayDirent = {
5030
- depth,
5031
- icon: '',
5032
- name: item.name,
5033
- path,
5034
- posInSet: i + 1,
5035
- selected: false,
5036
- setSize: items.length,
5037
- type: restoreDirentType(item.type, path, expandedPaths)
5038
- };
5039
- const children = getProtoMapInternal(path, pathToDirents, expandedPaths, depth + 1, excluded, workspaceRoot);
5040
- protoMap.push(displayDirent, ...children);
5041
- }
5042
- return protoMap;
5043
- };
5044
-
5045
5103
  const getProtoMap = (root, pathToDirents, expandedPaths, excluded = []) => {
5046
5104
  return getProtoMapInternal(root, pathToDirents, expandedPaths, 1, excluded, root);
5047
5105
  };
5048
5106
 
5049
- const sortPathDirentsMap = map => {
5050
- const sortedMap = Object.create(null);
5051
- for (const [key, value] of Object.entries(map)) {
5052
- const sorted = sortExplorerItems(value);
5053
- sortedMap[key] = sorted;
5054
- }
5055
- return sortedMap;
5056
- };
5057
-
5058
5107
  const refresh = async state => {
5059
5108
  const {
5060
5109
  excluded,
5110
+ expandedPaths: preservedExpandedPaths,
5061
5111
  focusedIndex,
5062
5112
  gitIgnoreDecorations,
5063
5113
  items,
5064
5114
  pathSeparator,
5115
+ preserveExpandState,
5065
5116
  root
5066
5117
  } = state;
5067
- const expandedDirents = getExpandedDirents(items);
5068
- const expandedPaths = getPaths(expandedDirents);
5118
+ const legacyExpandedPaths = getPaths(getExpandedDirents(items));
5119
+ const expandedPaths = preserveExpandState ? preservedExpandedPaths : legacyExpandedPaths;
5069
5120
  const allPaths = [root, ...expandedPaths];
5070
5121
  const pathToDirents = await getPathDirentsMap(allPaths);
5071
5122
  const sortedPathDirents = sortPathDirentsMap(pathToDirents);
@@ -5697,6 +5748,8 @@ const getSettings = async () => {
5697
5748
  const excluded = getExcluded(excludedRaw);
5698
5749
  const gitIgnoreDecorationsRaw = await invoke$3('Preferences.get', 'explorer.gitIgnoreDecorations');
5699
5750
  const gitIgnoreDecorations = gitIgnoreDecorationsRaw === false ? false : true;
5751
+ const preserveExpandStateRaw = await invoke$3('Preferences.get', 'explorer.preserveExpandState');
5752
+ const preserveExpandState = preserveExpandStateRaw === false ? false : true;
5700
5753
  const sourceControlDecorationsRaw = await invoke$3('Preferences.get', 'explorer.sourceControlDecorations');
5701
5754
  const sourceControlDecorations = sourceControlDecorationsRaw === false ? false : true;
5702
5755
  return {
@@ -5704,6 +5757,7 @@ const getSettings = async () => {
5704
5757
  confirmPaste,
5705
5758
  excluded,
5706
5759
  gitIgnoreDecorations,
5760
+ preserveExpandState,
5707
5761
  sourceControlDecorations,
5708
5762
  useChevrons
5709
5763
  };
@@ -5784,17 +5838,16 @@ const getSavedExpandedPaths = (savedState, root) => {
5784
5838
  return [];
5785
5839
  }
5786
5840
  if (savedState && savedState.expandedPaths && Array.isArray(savedState.expandedPaths)) {
5787
- return savedState.expandedPaths;
5841
+ return savedState.expandedPaths.filter(path => typeof path === 'string');
5788
5842
  }
5789
5843
  return [];
5790
5844
  };
5791
- const restoreExpandedState = async (savedState, root, pathSeparator, excluded) => {
5845
+ const restoreExpandedState = async (expandedPaths, root, pathSeparator, excluded) => {
5792
5846
  // TODO read all opened folders in parallel
5793
5847
  // ignore ENOENT errors
5794
5848
  // ignore ENOTDIR errors
5795
5849
  // merge all dirents
5796
5850
  // restore scroll location
5797
- const expandedPaths = getSavedExpandedPaths(savedState, root);
5798
5851
  if (root === EmptyString) {
5799
5852
  return [];
5800
5853
  }
@@ -5807,28 +5860,44 @@ const restoreExpandedState = async (savedState, root, pathSeparator, excluded) =
5807
5860
  return dirents;
5808
5861
  };
5809
5862
 
5863
+ const getExpandedPaths = (savedState, root, currentRoot, currentExpandedPaths, preserveExpandState) => {
5864
+ if (!preserveExpandState) {
5865
+ return [];
5866
+ }
5867
+ if (savedState !== undefined && savedState !== null) {
5868
+ return getSavedExpandedPaths(savedState, root);
5869
+ }
5870
+ if (currentRoot === root) {
5871
+ return currentExpandedPaths;
5872
+ }
5873
+ return [];
5874
+ };
5810
5875
  const loadContent = async (state, savedState) => {
5811
5876
  const {
5812
5877
  assetDir,
5878
+ expandedPaths: currentExpandedPaths,
5813
5879
  height,
5814
5880
  itemHeight,
5815
- platform
5881
+ platform,
5882
+ root: currentRoot
5816
5883
  } = state;
5817
5884
  const {
5818
5885
  confirmDelete,
5819
5886
  excluded,
5820
5887
  gitIgnoreDecorations,
5888
+ preserveExpandState,
5821
5889
  sourceControlDecorations,
5822
5890
  useChevrons
5823
5891
  } = await getSettings();
5824
5892
  const workspacePath = await getWorkspacePath();
5825
5893
  const root = getSavedRoot(savedState, workspacePath);
5894
+ const expandedPaths = getExpandedPaths(savedState, root, currentRoot, currentExpandedPaths, preserveExpandState);
5826
5895
  try {
5827
5896
  // TODO path separator could be restored from saved state
5828
5897
  const [pathSeparator, isReadonly$1] = await Promise.all([getPathSeparator(root),
5829
5898
  // TODO only load path separator once
5830
5899
  root === '' ? false : isReadonly(root)]);
5831
- const restoredDirents = await restoreExpandedState(savedState, root, pathSeparator, excluded);
5900
+ const restoredDirents = await restoreExpandedState(expandedPaths, root, pathSeparator, excluded);
5832
5901
  const rawDeltaY = getRestoredDeltaY(savedState);
5833
5902
  const maxDeltaY = Math.max(restoredDirents.length * itemHeight - height, 0);
5834
5903
  const deltaY = Math.min(Math.max(rawDeltaY, 0), maxDeltaY);
@@ -5844,6 +5913,7 @@ const loadContent = async (state, savedState) => {
5844
5913
  errorCode: '',
5845
5914
  errorMessage: '',
5846
5915
  excluded,
5916
+ expandedPaths,
5847
5917
  gitIgnoreDecorations,
5848
5918
  hasError: false,
5849
5919
  initial: false,
@@ -5852,6 +5922,7 @@ const loadContent = async (state, savedState) => {
5852
5922
  maxIndent: 10,
5853
5923
  minLineY,
5854
5924
  pathSeparator,
5925
+ preserveExpandState,
5855
5926
  root,
5856
5927
  sourceControlIgnoredUris,
5857
5928
  useChevrons
@@ -5864,11 +5935,13 @@ const loadContent = async (state, savedState) => {
5864
5935
  confirmDelete,
5865
5936
  errorCode,
5866
5937
  errorMessage,
5938
+ expandedPaths,
5867
5939
  gitIgnoreDecorations,
5868
5940
  hasError: true,
5869
5941
  initial: false,
5870
5942
  isReadonly: false,
5871
5943
  items: [],
5944
+ preserveExpandState,
5872
5945
  root,
5873
5946
  useChevrons
5874
5947
  };
@@ -6200,8 +6273,10 @@ const expandTargetFolder = (items, targetFolder) => {
6200
6273
  };
6201
6274
  const handleInternalDrop = async (state, sourcePaths, index) => {
6202
6275
  const {
6276
+ expandedPaths: oldExpandedPaths,
6203
6277
  isReadonly,
6204
- items
6278
+ items,
6279
+ preserveExpandState
6205
6280
  } = state;
6206
6281
  if (isReadonly) {
6207
6282
  return state;
@@ -6218,8 +6293,10 @@ const handleInternalDrop = async (state, sourcePaths, index) => {
6218
6293
  await rename$1(operation.from, operation.path);
6219
6294
  }
6220
6295
  const expandedItems = expandTargetFolder(items, targetFolder);
6296
+ const expandedPaths = preserveExpandState ? [...new Set([...oldExpandedPaths, targetFolder])] : oldExpandedPaths;
6221
6297
  const updated = await refresh({
6222
6298
  ...state,
6299
+ expandedPaths,
6223
6300
  items: expandedItems
6224
6301
  });
6225
6302
  return {
@@ -7054,9 +7131,13 @@ const handleWheel = (state, deltaMode, deltaY) => {
7054
7131
  };
7055
7132
 
7056
7133
  const handleWorkspaceChange = async (state, _workspacePath, savedState) => {
7134
+ const {
7135
+ root
7136
+ } = state;
7057
7137
  const newRoot = await getWorkspacePath();
7058
- const state1 = {
7138
+ const state1 = newRoot === root ? state : {
7059
7139
  ...state,
7140
+ expandedPaths: [],
7060
7141
  root: newRoot
7061
7142
  };
7062
7143
  const newState = await loadContent(state1, savedState);
@@ -8215,12 +8296,14 @@ const isExpandedDirectory = dirent => {
8215
8296
  const saveState = state => {
8216
8297
  const {
8217
8298
  deltaY,
8299
+ expandedPaths: preservedExpandedPaths,
8218
8300
  items,
8219
8301
  maxLineY,
8220
8302
  minLineY,
8303
+ preserveExpandState,
8221
8304
  root
8222
8305
  } = state;
8223
- const expandedPaths = items.filter(isExpandedDirectory).map(getPath);
8306
+ const expandedPaths = preserveExpandState ? preservedExpandedPaths : items.filter(isExpandedDirectory).map(getPath);
8224
8307
  return {
8225
8308
  deltaY,
8226
8309
  expandedPaths,
@@ -0,0 +1,64 @@
1
+ [
2
+ {
3
+ "category": "explorer",
4
+ "description": "Controls whether the Explorer uses chevrons instead of folder icons to show expanded and collapsed folders.",
5
+ "heading": "Use Chevrons",
6
+ "id": "explorer.useChevrons",
7
+ "type": 3,
8
+ "value": "true"
9
+ },
10
+ {
11
+ "category": "explorer",
12
+ "description": "Controls whether the Explorer asks for confirmation before deleting files and folders.",
13
+ "heading": "Confirm Delete",
14
+ "id": "explorer.confirmdelete",
15
+ "type": 3,
16
+ "value": "true"
17
+ },
18
+ {
19
+ "category": "explorer",
20
+ "description": "Controls whether the Explorer asks for confirmation before pasting files and folders.",
21
+ "heading": "Confirm Paste",
22
+ "id": "explorer.confirmpaste",
23
+ "type": 3,
24
+ "value": "false"
25
+ },
26
+ {
27
+ "category": "explorer",
28
+ "description": "Controls whether files ignored by Git are decorated in the Explorer.",
29
+ "heading": "Git Ignore Decorations",
30
+ "id": "explorer.gitIgnoreDecorations",
31
+ "type": 3,
32
+ "value": "true"
33
+ },
34
+ {
35
+ "category": "explorer",
36
+ "description": "Controls whether expanded folders are preserved for the current workspace when the Explorer rebuilds and across editor restarts.",
37
+ "heading": "Preserve Expand State",
38
+ "id": "explorer.preserveExpandState",
39
+ "type": 3,
40
+ "value": "true"
41
+ },
42
+ {
43
+ "category": "explorer",
44
+ "description": "Controls whether source control decorations are shown in the Explorer.",
45
+ "heading": "Source Control Decorations",
46
+ "id": "explorer.sourceControlDecorations",
47
+ "type": 3,
48
+ "value": "true"
49
+ },
50
+ {
51
+ "category": "explorer",
52
+ "description": "Configures glob patterns for excluding files and folders from the Explorer.",
53
+ "heading": "Exclude",
54
+ "id": "files.exclude",
55
+ "type": 4,
56
+ "value": {
57
+ "**/.DS_Store": true,
58
+ "**/.git": true,
59
+ "**/.hg": true,
60
+ "**/.svn": true,
61
+ "**/Thumbs.db": true
62
+ }
63
+ }
64
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/explorer-view",
3
- "version": "7.25.0",
3
+ "version": "7.27.0",
4
4
  "description": "Explorer Worker",
5
5
  "repository": {
6
6
  "type": "git",