@lvce-editor/explorer-view 1.2.0 → 1.4.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.
@@ -69,7 +69,7 @@ class JsonRpcError extends Error {
69
69
  this.name = 'JsonRpcError';
70
70
  }
71
71
  }
72
- const NewLine$2 = '\n';
72
+ const NewLine$3 = '\n';
73
73
  const DomException = 'DOMException';
74
74
  const ReferenceError$1 = 'ReferenceError';
75
75
  const SyntaxError$1 = 'SyntaxError';
@@ -114,23 +114,23 @@ const constructError = (message, type, name) => {
114
114
  }
115
115
  return new ErrorConstructor(message);
116
116
  };
117
- const getNewLineIndex$1 = (string, startIndex = undefined) => {
118
- return string.indexOf(NewLine$2, startIndex);
117
+ const getNewLineIndex$2 = (string, startIndex = undefined) => {
118
+ return string.indexOf(NewLine$3, startIndex);
119
119
  };
120
120
  const getParentStack = error => {
121
121
  let parentStack = error.stack || error.data || error.message || '';
122
122
  if (parentStack.startsWith(' at')) {
123
- parentStack = error.message + NewLine$2 + parentStack;
123
+ parentStack = error.message + NewLine$3 + parentStack;
124
124
  }
125
125
  return parentStack;
126
126
  };
127
127
  const joinLines$1 = lines => {
128
- return lines.join(NewLine$2);
128
+ return lines.join(NewLine$3);
129
129
  };
130
130
  const MethodNotFound = -32601;
131
131
  const Custom = -32001;
132
132
  const splitLines$1 = lines => {
133
- return lines.split(NewLine$2);
133
+ return lines.split(NewLine$3);
134
134
  };
135
135
  const restoreJsonRpcError = error => {
136
136
  if (error && error instanceof Error) {
@@ -140,14 +140,14 @@ const restoreJsonRpcError = error => {
140
140
  if (error && error.code && error.code === MethodNotFound) {
141
141
  const restoredError = new JsonRpcError(error.message);
142
142
  const parentStack = getParentStack(error);
143
- restoredError.stack = parentStack + NewLine$2 + currentStack;
143
+ restoredError.stack = parentStack + NewLine$3 + currentStack;
144
144
  return restoredError;
145
145
  }
146
146
  if (error && error.message) {
147
147
  const restoredError = constructError(error.message, error.type, error.name);
148
148
  if (error.data) {
149
149
  if (error.data.stack && error.data.type && error.message) {
150
- restoredError.stack = error.data.type + ': ' + error.message + NewLine$2 + error.data.stack + NewLine$2 + currentStack;
150
+ restoredError.stack = error.data.type + ': ' + error.message + NewLine$3 + error.data.stack + NewLine$3 + currentStack;
151
151
  } else if (error.data.stack) {
152
152
  restoredError.stack = error.data.stack;
153
153
  }
@@ -167,7 +167,7 @@ const restoreJsonRpcError = error => {
167
167
  if (error.stack) {
168
168
  const lowerStack = restoredError.stack || '';
169
169
  // @ts-ignore
170
- const indexNewLine = getNewLineIndex$1(lowerStack);
170
+ const indexNewLine = getNewLineIndex$2(lowerStack);
171
171
  const parentStack = getParentStack(error);
172
172
  // @ts-ignore
173
173
  restoredError.stack = parentStack + lowerStack.slice(indexNewLine);
@@ -224,7 +224,7 @@ const getErrorProperty = (error, prettyError) => {
224
224
  }
225
225
  };
226
226
  };
227
- const create$1$1 = (message, error) => {
227
+ const create$1 = (message, error) => {
228
228
  return {
229
229
  jsonrpc: Two,
230
230
  id: message.id,
@@ -235,9 +235,9 @@ const getErrorResponse = (message, error, preparePrettyError, logError) => {
235
235
  const prettyError = preparePrettyError(error);
236
236
  logError(error, prettyError);
237
237
  const errorProperty = getErrorProperty(error, prettyError);
238
- return create$1$1(message, errorProperty);
238
+ return create$1(message, errorProperty);
239
239
  };
240
- const create = (message, result) => {
240
+ const create$5 = (message, result) => {
241
241
  return {
242
242
  jsonrpc: Two,
243
243
  id: message.id,
@@ -246,7 +246,7 @@ const create = (message, result) => {
246
246
  };
247
247
  const getSuccessResponse = (message, result) => {
248
248
  const resultProperty = result ?? null;
249
- return create(message, resultProperty);
249
+ return create$5(message, resultProperty);
250
250
  };
251
251
  const getResponse = async (message, ipc, execute, preparePrettyError, logError, requiresSocket) => {
252
252
  try {
@@ -584,7 +584,7 @@ const getHelpfulChildProcessError = (stdout, stderr) => {
584
584
  stack: rest
585
585
  };
586
586
  };
587
- const normalizeLine = line => {
587
+ const normalizeLine$1 = line => {
588
588
  if (line.startsWith('Error: ')) {
589
589
  return line.slice('Error: '.length);
590
590
  }
@@ -593,41 +593,41 @@ const normalizeLine = line => {
593
593
  }
594
594
  return line;
595
595
  };
596
- const getCombinedMessage = (error, message) => {
597
- const stringifiedError = normalizeLine(`${error}`);
596
+ const getCombinedMessage$1 = (error, message) => {
597
+ const stringifiedError = normalizeLine$1(`${error}`);
598
598
  if (message) {
599
599
  return `${message}: ${stringifiedError}`;
600
600
  }
601
601
  return stringifiedError;
602
602
  };
603
- const NewLine = '\n';
604
- const getNewLineIndex = (string, startIndex = undefined) => {
605
- return string.indexOf(NewLine, startIndex);
603
+ const NewLine$2 = '\n';
604
+ const getNewLineIndex$1 = (string, startIndex = undefined) => {
605
+ return string.indexOf(NewLine$2, startIndex);
606
606
  };
607
- const mergeStacks = (parent, child) => {
607
+ const mergeStacks$1 = (parent, child) => {
608
608
  if (!child) {
609
609
  return parent;
610
610
  }
611
- const parentNewLineIndex = getNewLineIndex(parent);
612
- const childNewLineIndex = getNewLineIndex(child);
611
+ const parentNewLineIndex = getNewLineIndex$1(parent);
612
+ const childNewLineIndex = getNewLineIndex$1(child);
613
613
  if (childNewLineIndex === -1) {
614
614
  return parent;
615
615
  }
616
616
  const parentFirstLine = parent.slice(0, parentNewLineIndex);
617
617
  const childRest = child.slice(childNewLineIndex);
618
- const childFirstLine = normalizeLine(child.slice(0, childNewLineIndex));
618
+ const childFirstLine = normalizeLine$1(child.slice(0, childNewLineIndex));
619
619
  if (parentFirstLine.includes(childFirstLine)) {
620
620
  return parentFirstLine + childRest;
621
621
  }
622
622
  return child;
623
623
  };
624
- class VError extends Error {
624
+ let VError$1 = class VError extends Error {
625
625
  constructor(error, message) {
626
- const combinedMessage = getCombinedMessage(error, message);
626
+ const combinedMessage = getCombinedMessage$1(error, message);
627
627
  super(combinedMessage);
628
628
  this.name = 'VError';
629
629
  if (error instanceof Error) {
630
- this.stack = mergeStacks(this.stack, error.stack);
630
+ this.stack = mergeStacks$1(this.stack, error.stack);
631
631
  }
632
632
  if (error.codeFrame) {
633
633
  // @ts-ignore
@@ -638,8 +638,8 @@ class VError extends Error {
638
638
  this.code = error.code;
639
639
  }
640
640
  }
641
- }
642
- class IpcError extends VError {
641
+ };
642
+ class IpcError extends VError$1 {
643
643
  // @ts-ignore
644
644
  constructor(betterMessage, stdout = '', stderr = '') {
645
645
  if (stdout || stderr) {
@@ -784,7 +784,7 @@ const listen$1 = async () => {
784
784
  const ipc = module.wrap(rawIpc);
785
785
  return ipc;
786
786
  };
787
- const create$1 = async ({
787
+ const create = async ({
788
788
  commandMap
789
789
  }) => {
790
790
  // TODO create a commandMap per rpc instance
@@ -796,7 +796,7 @@ const create$1 = async ({
796
796
  };
797
797
  const WebWorkerRpcClient = {
798
798
  __proto__: null,
799
- create: create$1
799
+ create
800
800
  };
801
801
 
802
802
  const RE_CHARACTERS = /^[a-zA-Z.-]+$/;
@@ -943,7 +943,7 @@ const computeExplorerRenamedDirent = (dirents, index, newName) => {
943
943
  };
944
944
  };
945
945
 
946
- const None$1 = 0;
946
+ const None$3 = 0;
947
947
  const CreateFile = 1;
948
948
  const CreateFolder = 2;
949
949
  const Rename = 3;
@@ -955,60 +955,8 @@ const getRealPath = async path => {};
955
955
  const stat = async dirent => {};
956
956
  const createFile = async uri => {};
957
957
  const mkdir = async uri => {};
958
- const rename = async (oldUri, newUri) => {};
959
-
960
- class AssertionError extends Error {
961
- constructor(message) {
962
- super(message);
963
- this.name = 'AssertionError';
964
- }
965
- }
966
- const getType = value => {
967
- switch (typeof value) {
968
- case 'number':
969
- return 'number';
970
- case 'function':
971
- return 'function';
972
- case 'string':
973
- return 'string';
974
- case 'object':
975
- if (value === null) {
976
- return 'null';
977
- }
978
- if (Array.isArray(value)) {
979
- return 'array';
980
- }
981
- return 'object';
982
- case 'boolean':
983
- return 'boolean';
984
- default:
985
- return 'unknown';
986
- }
987
- };
988
- const object = value => {
989
- const type = getType(value);
990
- if (type !== 'object') {
991
- throw new AssertionError('expected value to be of type object');
992
- }
993
- };
994
- const number = value => {
995
- const type = getType(value);
996
- if (type !== 'number') {
997
- throw new AssertionError('expected value to be of type number');
998
- }
999
- };
1000
- const array = value => {
1001
- const type = getType(value);
1002
- if (type !== 'array') {
1003
- throw new AssertionError('expected value to be of type array');
1004
- }
1005
- };
1006
- const string = value => {
1007
- const type = getType(value);
1008
- if (type !== 'string') {
1009
- throw new AssertionError('expected value to be of type string');
1010
- }
1011
- };
958
+ const rename$1 = async (oldUri, newUri) => {};
959
+ const copy$1 = async (oldUri, newUri) => {};
1012
960
 
1013
961
  const dirname = (pathSeparator, path) => {
1014
962
  const index = path.lastIndexOf(pathSeparator);
@@ -1017,6 +965,12 @@ const dirname = (pathSeparator, path) => {
1017
965
  }
1018
966
  return path.slice(0, index);
1019
967
  };
968
+ const join = (pathSeparator, ...parts) => {
969
+ return parts.join(pathSeparator);
970
+ };
971
+ const getBaseName = (pathSeparator, path) => {
972
+ return path.slice(path.lastIndexOf(pathSeparator) + 1);
973
+ };
1020
974
 
1021
975
  const getParentFolder = (dirents, index, root) => {
1022
976
  if (index < 0) {
@@ -1097,7 +1051,7 @@ const acceptCreate = async (state, newDirentType, createFn) => {
1097
1051
  items: newDirents,
1098
1052
  editingIndex: -1,
1099
1053
  focusedIndex: insertIndex + 1,
1100
- editingType: None$1,
1054
+ editingType: None$3,
1101
1055
  maxLineY: newMaxlineY
1102
1056
  };
1103
1057
  };
@@ -1114,7 +1068,7 @@ const acceptRename = async state => {
1114
1068
  const oldAbsolutePath = renamedDirent.path;
1115
1069
  const oldParentPath = dirname(pathSeparator, oldAbsolutePath);
1116
1070
  const newAbsolutePath = [oldParentPath, editingValue].join(pathSeparator);
1117
- await rename(oldAbsolutePath, newAbsolutePath);
1071
+ await rename$1(oldAbsolutePath, newAbsolutePath);
1118
1072
  } catch (error) {
1119
1073
  // TODO
1120
1074
  // await ErrorHandling.showErrorDialog(error)
@@ -1130,7 +1084,7 @@ const acceptRename = async state => {
1130
1084
  ...state,
1131
1085
  editingIndex: -1,
1132
1086
  editingValue: '',
1133
- editingType: None$1,
1087
+ editingType: None$3,
1134
1088
  editingIcon: '',
1135
1089
  focusedIndex,
1136
1090
  focused: true
@@ -1152,77 +1106,107 @@ const acceptEdit = state => {
1152
1106
  }
1153
1107
  };
1154
1108
 
1155
- const ENOENT = 'ENOENT';
1109
+ const getFocusedDirent$1 = state => {
1110
+ const {
1111
+ focusedIndex,
1112
+ minLineY,
1113
+ items
1114
+ } = state;
1115
+ const dirent = items[focusedIndex + minLineY];
1116
+ return dirent;
1117
+ };
1156
1118
 
1157
- const sortExplorerItems = rawDirents => {
1158
- rawDirents.sort(compareDirent);
1119
+ const copyPath$1 = async state => {
1120
+ // await Command.execute(RendererWorkerCommandType.ClipBoardWriteText, /* text */ path)
1121
+ return state;
1159
1122
  };
1160
1123
 
1161
- const toDisplayDirents = (pathSeparator, rawDirents, parentDirent, excluded) => {
1162
- sortExplorerItems(rawDirents);
1163
- // TODO figure out whether this uses too much memory (name,path -> redundant, depth could be computed on demand)
1164
- const toDisplayDirent = (rawDirent, index) => {
1165
- const path = [parentDirent.path, rawDirent.name].join(pathSeparator);
1166
- return {
1167
- name: rawDirent.name,
1168
- posInSet: index + 1,
1169
- setSize: rawDirents.length,
1170
- depth: parentDirent.depth + 1,
1171
- type: rawDirent.type,
1172
- path,
1173
- // TODO storing absolute path might be too costly, could also store relative path here
1174
- icon: getIcon()
1175
- };
1176
- };
1177
- const result = [];
1178
- let i = 0;
1179
- for (const rawDirent of rawDirents) {
1180
- if (excluded.includes(rawDirent.name)) {
1181
- continue;
1182
- }
1183
- result.push(toDisplayDirent(rawDirent, i));
1184
- i++;
1185
- }
1186
- return result;
1124
+ const state = {
1125
+ rpc: undefined
1126
+ };
1127
+ const invoke = (method, ...params) => {
1128
+ const rpc = state.rpc;
1129
+ // @ts-ignore
1130
+ return rpc.invoke(method, ...params);
1131
+ };
1132
+ const setRpc = rpc => {
1133
+ state.rpc = rpc;
1187
1134
  };
1188
1135
 
1189
- const getIndexFromPosition = (state, eventX, eventY) => {
1190
- const {
1191
- y,
1192
- itemHeight,
1193
- items
1194
- } = state;
1195
- const index = Math.floor((eventY - y) / itemHeight);
1196
- if (index < 0) {
1197
- return 0;
1136
+ const writeText = async text => {
1137
+ await invoke('ClipBoard.writeText', /* text */text);
1138
+ };
1139
+ const readNativeFiles = async () => {
1140
+ return invoke('ClipBoard.readNativeFiles');
1141
+ };
1142
+
1143
+ const copyRelativePath$1 = async state => {
1144
+ const dirent = getFocusedDirent$1(state);
1145
+ const relativePath = dirent.path.slice(1);
1146
+ // TODO handle error
1147
+ await writeText(relativePath);
1148
+ return state;
1149
+ };
1150
+
1151
+ class AssertionError extends Error {
1152
+ constructor(message) {
1153
+ super(message);
1154
+ this.name = 'AssertionError';
1198
1155
  }
1199
- if (index >= items.length) {
1200
- return -1;
1156
+ }
1157
+ const getType = value => {
1158
+ switch (typeof value) {
1159
+ case 'number':
1160
+ return 'number';
1161
+ case 'function':
1162
+ return 'function';
1163
+ case 'string':
1164
+ return 'string';
1165
+ case 'object':
1166
+ if (value === null) {
1167
+ return 'null';
1168
+ }
1169
+ if (Array.isArray(value)) {
1170
+ return 'array';
1171
+ }
1172
+ return 'object';
1173
+ case 'boolean':
1174
+ return 'boolean';
1175
+ default:
1176
+ return 'unknown';
1201
1177
  }
1202
- return index;
1203
1178
  };
1204
- const getParentStartIndex = (dirents, index) => {
1205
- const dirent = dirents[index];
1206
- let startIndex = index - 1;
1207
- while (startIndex >= 0 && dirents[startIndex].depth >= dirent.depth) {
1208
- startIndex--;
1179
+ const object = value => {
1180
+ const type = getType(value);
1181
+ if (type !== 'object') {
1182
+ throw new AssertionError('expected value to be of type object');
1209
1183
  }
1210
- return startIndex;
1211
1184
  };
1212
- const getParentEndIndex = (dirents, index) => {
1213
- const dirent = dirents[index];
1214
- let endIndex = index + 1;
1215
- while (endIndex < dirents.length && dirents[endIndex].depth > dirent.depth) {
1216
- endIndex++;
1185
+ const number = value => {
1186
+ const type = getType(value);
1187
+ if (type !== 'number') {
1188
+ throw new AssertionError('expected value to be of type number');
1189
+ }
1190
+ };
1191
+ const array = value => {
1192
+ const type = getType(value);
1193
+ if (type !== 'array') {
1194
+ throw new AssertionError('expected value to be of type array');
1217
1195
  }
1218
- return endIndex;
1219
1196
  };
1197
+ const string = value => {
1198
+ const type = getType(value);
1199
+ if (type !== 'string') {
1200
+ throw new AssertionError('expected value to be of type string');
1201
+ }
1202
+ };
1203
+
1220
1204
  const isSymbolicLink = dirent => {
1221
1205
  return dirent.type === Symlink;
1222
1206
  };
1223
- const hasSymbolicLinks = rawDirents => {
1224
- return rawDirents.some(isSymbolicLink);
1225
- };
1207
+
1208
+ const ENOENT = 'ENOENT';
1209
+
1226
1210
  const getSymlinkType = type => {
1227
1211
  switch (type) {
1228
1212
  case File:
@@ -1233,6 +1217,7 @@ const getSymlinkType = type => {
1233
1217
  return Symlink;
1234
1218
  }
1235
1219
  };
1220
+
1236
1221
  // TODO maybe resolving of symbolic links should happen in shared process?
1237
1222
  // so that there is less code and less work in the frontend
1238
1223
  const resolveSymbolicLink = async (uri, rawDirent) => {
@@ -1270,6 +1255,42 @@ const resolveSymbolicLinks = async (uri, rawDirents) => {
1270
1255
  const resolvedDirents = await Promise.all(promises);
1271
1256
  return resolvedDirents;
1272
1257
  };
1258
+
1259
+ const sortExplorerItems = rawDirents => {
1260
+ rawDirents.sort(compareDirent);
1261
+ };
1262
+
1263
+ const toDisplayDirents = (pathSeparator, rawDirents, parentDirent, excluded) => {
1264
+ sortExplorerItems(rawDirents);
1265
+ // TODO figure out whether this uses too much memory (name,path -> redundant, depth could be computed on demand)
1266
+ const toDisplayDirent = (rawDirent, index) => {
1267
+ const path = [parentDirent.path, rawDirent.name].join(pathSeparator);
1268
+ return {
1269
+ name: rawDirent.name,
1270
+ posInSet: index + 1,
1271
+ setSize: rawDirents.length,
1272
+ depth: parentDirent.depth + 1,
1273
+ type: rawDirent.type,
1274
+ path,
1275
+ // TODO storing absolute path might be too costly, could also store relative path here
1276
+ icon: getIcon()
1277
+ };
1278
+ };
1279
+ const result = [];
1280
+ let i = 0;
1281
+ for (const rawDirent of rawDirents) {
1282
+ if (excluded.includes(rawDirent.name)) {
1283
+ continue;
1284
+ }
1285
+ result.push(toDisplayDirent(rawDirent, i));
1286
+ i++;
1287
+ }
1288
+ return result;
1289
+ };
1290
+
1291
+ const hasSymbolicLinks = rawDirents => {
1292
+ return rawDirents.some(isSymbolicLink);
1293
+ };
1273
1294
  const getChildDirentsRaw = async uri => {
1274
1295
  const rawDirents = await readDirWithFileTypes();
1275
1296
  array(rawDirents);
@@ -1347,12 +1368,91 @@ const expandAll = async state => {
1347
1368
  };
1348
1369
  };
1349
1370
 
1350
- const focusIndex = (state, index) => {
1351
- const {
1352
- minLineY,
1353
- maxLineY
1354
- } = state;
1355
- if (index < minLineY) {
1371
+ const getParentEndIndex = (dirents, index) => {
1372
+ const dirent = dirents[index];
1373
+ let endIndex = index + 1;
1374
+ while (endIndex < dirents.length && dirents[endIndex].depth > dirent.depth) {
1375
+ endIndex++;
1376
+ }
1377
+ return endIndex;
1378
+ };
1379
+
1380
+ const makeExpanded = dirent => {
1381
+ if (dirent.type === Directory) {
1382
+ return {
1383
+ ...dirent,
1384
+ type: DirectoryExpanded
1385
+ };
1386
+ }
1387
+ return dirent;
1388
+ };
1389
+ const expandRecursively = async state => {
1390
+ const {
1391
+ items,
1392
+ focusedIndex,
1393
+ pathSeparator,
1394
+ root,
1395
+ height,
1396
+ itemHeight,
1397
+ minLineY
1398
+ } = state;
1399
+ const dirent = focusedIndex < 0 ? {
1400
+ type: Directory,
1401
+ path: root,
1402
+ depth: 0
1403
+ } : items[focusedIndex];
1404
+ if (dirent.type !== Directory && dirent.type !== DirectoryExpanding && dirent.type !== DirectoryExpanded) {
1405
+ return state;
1406
+ }
1407
+ // TODO this is very inefficient
1408
+ const getChildDirentsRecursively = async dirent => {
1409
+ switch (dirent.type) {
1410
+ case File:
1411
+ return [dirent];
1412
+ case Directory:
1413
+ case DirectoryExpanding:
1414
+ case DirectoryExpanded:
1415
+ const childDirents = await getChildDirents(pathSeparator, dirent);
1416
+ const all = [makeExpanded(dirent)];
1417
+ for (const childDirent of childDirents) {
1418
+ const childAll = await getChildDirentsRecursively(childDirent);
1419
+ all.push(...childAll);
1420
+ }
1421
+ return all;
1422
+ default:
1423
+ return [];
1424
+ }
1425
+ };
1426
+ // TODO race condition: what if folder is being collapse while it is recursively expanding?
1427
+ // TODO race condition: what if folder is being deleted while it is recursively expanding?
1428
+ // TODO race condition: what if a new file/folder is created while the folder is recursively expanding?
1429
+ const childDirents = await getChildDirentsRecursively(dirent);
1430
+ const startIndex = focusedIndex;
1431
+ if (focusedIndex >= 0) {
1432
+ const endIndex = getParentEndIndex(items, focusedIndex);
1433
+ const newDirents = [...items.slice(0, startIndex), ...childDirents, ...items.slice(endIndex)];
1434
+ const maxLineY = getExplorerMaxLineY(minLineY, height, itemHeight, newDirents.length);
1435
+ return {
1436
+ ...state,
1437
+ items: newDirents,
1438
+ maxLineY
1439
+ };
1440
+ }
1441
+ const newDirents = childDirents.slice(1);
1442
+ const maxLineY = getExplorerMaxLineY(minLineY, height, itemHeight, newDirents.length);
1443
+ return {
1444
+ ...state,
1445
+ items: newDirents,
1446
+ maxLineY
1447
+ };
1448
+ };
1449
+
1450
+ const focusIndex = (state, index) => {
1451
+ const {
1452
+ minLineY,
1453
+ maxLineY
1454
+ } = state;
1455
+ if (index < minLineY) {
1356
1456
  if (index < 0) {
1357
1457
  return {
1358
1458
  ...state,
@@ -1442,7 +1542,7 @@ const focusPrevious = state => {
1442
1542
  }
1443
1543
  };
1444
1544
 
1445
- const None = 'none';
1545
+ const None$2 = 'none';
1446
1546
  const Tree = 'tree';
1447
1547
  const TreeItem$1 = 'treeitem';
1448
1548
 
@@ -1501,6 +1601,39 @@ const UiStrings = {
1501
1601
  OpenFolder: 'Open folder',
1502
1602
  NoFolderOpen: 'No Folder Open'
1503
1603
  };
1604
+ const newFile = () => {
1605
+ return i18nString(UiStrings.NewFile);
1606
+ };
1607
+ const newFolder = () => {
1608
+ return i18nString(UiStrings.NewFolder);
1609
+ };
1610
+ const openContainingFolder$1 = () => {
1611
+ return i18nString(UiStrings.OpenContainingFolder);
1612
+ };
1613
+ const openInIntegratedTerminal = () => {
1614
+ return i18nString(UiStrings.OpenInIntegratedTerminal);
1615
+ };
1616
+ const cut = () => {
1617
+ return i18nString(UiStrings.Cut);
1618
+ };
1619
+ const copy = () => {
1620
+ return i18nString(UiStrings.Copy);
1621
+ };
1622
+ const paste = () => {
1623
+ return i18nString(UiStrings.Paste);
1624
+ };
1625
+ const copyPath = () => {
1626
+ return i18nString(UiStrings.CopyPath);
1627
+ };
1628
+ const copyRelativePath = () => {
1629
+ return i18nString(UiStrings.CopyRelativePath);
1630
+ };
1631
+ const rename = () => {
1632
+ return i18nString(UiStrings.Rename);
1633
+ };
1634
+ const deleteItem = () => {
1635
+ return i18nString(UiStrings.Delete);
1636
+ };
1504
1637
  const filesExplorer = () => {
1505
1638
  return i18nString(UiStrings.FilesExplorer);
1506
1639
  };
@@ -1523,7 +1656,7 @@ const getFileIconVirtualDom = icon => {
1523
1656
  type: Img,
1524
1657
  className: FileIcon,
1525
1658
  src: icon,
1526
- role: None,
1659
+ role: None$2,
1527
1660
  childCount: 0
1528
1661
  };
1529
1662
  };
@@ -1805,6 +1938,121 @@ const getKeyBindings = () => {
1805
1938
  }];
1806
1939
  };
1807
1940
 
1941
+ const Separator = 1;
1942
+ const None$1 = 0;
1943
+ const RestoreFocus = 6;
1944
+
1945
+ const menuEntrySeparator = {
1946
+ id: 'separator',
1947
+ label: '',
1948
+ flags: Separator,
1949
+ command: ''
1950
+ };
1951
+
1952
+ const menuEntryNewFile = {
1953
+ id: 'newFile',
1954
+ label: newFile(),
1955
+ flags: None$1,
1956
+ command: 'Explorer.newFile'
1957
+ };
1958
+ const menuEntryNewFolder = {
1959
+ id: 'newFolder',
1960
+ label: newFolder(),
1961
+ flags: None$1,
1962
+ command: 'Explorer.newFolder'
1963
+ };
1964
+ const menuEntryOpenContainingFolder = {
1965
+ id: 'openContainingFolder',
1966
+ label: openContainingFolder$1(),
1967
+ flags: RestoreFocus,
1968
+ command: 'Explorer.openContainingFolder'
1969
+ };
1970
+ const menuEntryOpenInIntegratedTerminal = {
1971
+ id: 'openInIntegratedTerminal',
1972
+ label: openInIntegratedTerminal(),
1973
+ flags: None$1,
1974
+ command: /* TODO */-1
1975
+ };
1976
+ const menuEntryCut = {
1977
+ id: 'cut',
1978
+ label: cut(),
1979
+ flags: RestoreFocus,
1980
+ command: /* TODO */-1
1981
+ };
1982
+ const menuEntryCopy = {
1983
+ id: 'copy',
1984
+ label: copy(),
1985
+ flags: RestoreFocus,
1986
+ command: 'Explorer.handleCopy'
1987
+ };
1988
+ const menuEntryPaste = {
1989
+ id: 'paste',
1990
+ label: paste(),
1991
+ flags: None$1,
1992
+ command: 'Explorer.handlePaste'
1993
+ };
1994
+ const menuEntryCopyPath = {
1995
+ id: 'copyPath',
1996
+ label: copyPath(),
1997
+ flags: RestoreFocus,
1998
+ command: 'Explorer.copyPath'
1999
+ };
2000
+ const menuEntryCopyRelativePath = {
2001
+ id: 'copyRelativePath',
2002
+ label: copyRelativePath(),
2003
+ flags: RestoreFocus,
2004
+ command: 'Explorer.copyRelativePath'
2005
+ };
2006
+ const menuEntryRename = {
2007
+ id: 'rename',
2008
+ label: rename(),
2009
+ flags: None$1,
2010
+ command: 'Explorer.renameDirent'
2011
+ };
2012
+ const menuEntryDelete = {
2013
+ id: 'delete',
2014
+ label: deleteItem(),
2015
+ flags: None$1,
2016
+ command: 'Explorer.removeDirent'
2017
+ };
2018
+ const ALL_ENTRIES = [menuEntryNewFile, menuEntryNewFolder, menuEntryOpenContainingFolder, menuEntryOpenInIntegratedTerminal, menuEntrySeparator, menuEntryCut, menuEntryCopy, menuEntryPaste, menuEntrySeparator, menuEntryCopyPath, menuEntryCopyRelativePath, menuEntrySeparator, menuEntryRename, menuEntryDelete];
2019
+
2020
+ // TODO there are two possible ways of getting the focused dirent of explorer
2021
+ // 1. directly access state of explorer (bad because it directly accesses state of another component)
2022
+ // 2. expose getFocusedDirent method in explorer (bad because explorer code should not know about for menuEntriesExplorer, which needs that method)
2023
+ const getFocusedDirent = explorerState => {
2024
+ if (!explorerState || explorerState.focusedIndex < 0) {
2025
+ return undefined;
2026
+ }
2027
+ return explorerState.items[explorerState.focusedIndex];
2028
+ };
2029
+ const getMenuEntriesDirectory = () => {
2030
+ return ALL_ENTRIES;
2031
+ };
2032
+ const getMenuEntriesFile = () => {
2033
+ return [menuEntryOpenContainingFolder, menuEntryOpenInIntegratedTerminal, menuEntrySeparator, menuEntryCut, menuEntryCopy, menuEntryPaste, menuEntrySeparator, menuEntryCopyPath, menuEntryCopyRelativePath, menuEntrySeparator, menuEntryRename, menuEntryDelete];
2034
+ };
2035
+ const getMenuEntriesDefault = () => {
2036
+ return ALL_ENTRIES;
2037
+ };
2038
+ const getMenuEntriesRoot = () => {
2039
+ return [menuEntryNewFile, menuEntryNewFolder, menuEntryOpenContainingFolder, menuEntryOpenInIntegratedTerminal, menuEntrySeparator, menuEntryPaste, menuEntrySeparator, menuEntryCopyPath, menuEntryCopyRelativePath];
2040
+ };
2041
+ const getMenuEntries = state => {
2042
+ const focusedDirent = getFocusedDirent(state);
2043
+ if (!focusedDirent) {
2044
+ return getMenuEntriesRoot();
2045
+ }
2046
+ switch (focusedDirent.type) {
2047
+ case Directory:
2048
+ return getMenuEntriesDirectory();
2049
+ case File:
2050
+ return getMenuEntriesFile();
2051
+ default:
2052
+ return getMenuEntriesDefault();
2053
+ }
2054
+ };
2055
+
1808
2056
  const getVisibleExplorerItems = (items, minLineY, maxLineY, focusedIndex, editingIndex, editingType, editingValue) => {
1809
2057
  const visible = [];
1810
2058
  for (let i = minLineY; i < Math.min(maxLineY, items.length); i++) {
@@ -1825,7 +2073,7 @@ const getVisibleExplorerItems = (items, minLineY, maxLineY, focusedIndex, editin
1825
2073
  });
1826
2074
  }
1827
2075
  }
1828
- if (editingType !== None$1 && editingIndex === -1) {
2076
+ if (editingType !== None$3 && editingIndex === -1) {
1829
2077
  visible.push({
1830
2078
  depth: 3,
1831
2079
  posInSet: 1,
@@ -1841,14 +2089,182 @@ const getVisibleExplorerItems = (items, minLineY, maxLineY, focusedIndex, editin
1841
2089
  return visible;
1842
2090
  };
1843
2091
 
1844
- const getFocusedDirent = state => {
2092
+ const handleBlur = state => {
2093
+ // TODO when blur event occurs because of context menu, focused index should stay the same
2094
+ // but focus outline should be removed
2095
+ const {
2096
+ editingType
2097
+ } = state;
2098
+ if (editingType !== None$3) {
2099
+ return state;
2100
+ }
2101
+ return {
2102
+ ...state,
2103
+ focused: false
2104
+ };
2105
+ };
2106
+
2107
+ // TODO viewlet should only have create and refresh functions
2108
+ // every thing else can be in a separate module <viewlet>.lazy.js
2109
+ // and <viewlet>.ipc.js
2110
+
2111
+ // viewlet: creating | refreshing | done | disposed
2112
+ // TODO recycle viewlets (maybe)
2113
+
2114
+ // TODO instead of root string, there should be a root dirent
2115
+
2116
+ // TODO rename dirents to items, then can use virtual list component directly
2117
+
2118
+ // TODO support multiselection and removing multiple dirents
2119
+
2120
+ // TODO use posInSet and setSize properties to compute more effectively
2121
+
2122
+ // TODO much shared logic with newFolder
2123
+
2124
+ const handleClickFile$1 = async (state, dirent, index, keepFocus = false) => {
2125
+ // await Command.execute(/* Main.openAbsolutePath */ 'Main.openUri', /* absolutePath */ dirent.path, /* focus */ !keepFocus)
2126
+ return {
2127
+ ...state,
2128
+ focusedIndex: index,
2129
+ focused: keepFocus
2130
+ };
2131
+ };
2132
+ const handleClickDirectory$1 = async (state, dirent, index, keepFocus) => {
2133
+ dirent.type = DirectoryExpanding;
2134
+ // TODO handle error
2135
+ const dirents = await getChildDirents(state.pathSeparator, dirent);
2136
+ const state2 = {};
2137
+ if (!state2) {
2138
+ return state;
2139
+ }
2140
+ // TODO use Viewlet.getState here and check if it exists
2141
+ const newIndex = state2.items.indexOf(dirent);
2142
+ // TODO if viewlet is disposed or root has changed, return
2143
+ if (newIndex === -1) {
2144
+ return state;
2145
+ }
2146
+ const newDirents = [...state2.items];
2147
+ newDirents.splice(newIndex + 1, 0, ...dirents);
2148
+ dirent.type = DirectoryExpanded;
2149
+ dirent.icon = getIcon();
2150
+ const {
2151
+ height,
2152
+ itemHeight,
2153
+ minLineY
2154
+ } = state2;
2155
+ // TODO when focused index has changed while expanding, don't update it
2156
+ const maxLineY = getExplorerMaxLineY(minLineY, height, itemHeight, newDirents.length);
2157
+ return {
2158
+ ...state,
2159
+ items: newDirents,
2160
+ focusedIndex: newIndex,
2161
+ focused: keepFocus,
2162
+ maxLineY
2163
+ };
2164
+ };
2165
+ const handleClickDirectoryExpanding = (state, dirent, index, keepFocus) => {
2166
+ dirent.type = Directory;
2167
+ dirent.icon = getIcon();
2168
+ return {
2169
+ ...state,
2170
+ focusedIndex: index,
2171
+ focused: keepFocus
2172
+ };
2173
+ };
2174
+ const handleClickDirectoryExpanded$1 = (state, dirent, index, keepFocus) => {
1845
2175
  const {
1846
- focusedIndex,
1847
2176
  minLineY,
2177
+ maxLineY,
2178
+ itemHeight
2179
+ } = state;
2180
+ dirent.type = Directory;
2181
+ dirent.icon = getIcon();
2182
+ const endIndex = getParentEndIndex(state.items, index);
2183
+ const removeCount = endIndex - index - 1;
2184
+ // TODO race conditions and side effects are everywhere
2185
+ const newDirents = [...state.items];
2186
+ newDirents.splice(index + 1, removeCount);
2187
+ const newTotal = newDirents.length;
2188
+ if (newTotal < maxLineY) {
2189
+ const visibleItems = Math.min(maxLineY - minLineY, newTotal);
2190
+ const newMaxLineY = Math.min(maxLineY, newTotal);
2191
+ const newMinLineY = newMaxLineY - visibleItems;
2192
+ const deltaY = newMinLineY * itemHeight;
2193
+ return {
2194
+ ...state,
2195
+ items: newDirents,
2196
+ focusedIndex: index,
2197
+ focused: keepFocus,
2198
+ minLineY: newMinLineY,
2199
+ maxLineY: newMaxLineY,
2200
+ deltaY
2201
+ };
2202
+ }
2203
+ return {
2204
+ ...state,
2205
+ items: newDirents,
2206
+ focusedIndex: index,
2207
+ focused: keepFocus
2208
+ };
2209
+ };
2210
+ const handleClickSymLink$1 = async (state, dirent, index) => {
2211
+ await getRealPath();
2212
+ const type = await stat();
2213
+ switch (type) {
2214
+ case File:
2215
+ return handleClickFile$1(state, dirent, index);
2216
+ default:
2217
+ throw new Error(`unsupported file type ${type}`);
2218
+ }
2219
+ };
2220
+ const getClickFn = direntType => {
2221
+ switch (direntType) {
2222
+ case File:
2223
+ case SymLinkFile:
2224
+ return handleClickFile$1;
2225
+ case Directory:
2226
+ case SymLinkFolder:
2227
+ return handleClickDirectory$1;
2228
+ case DirectoryExpanding:
2229
+ return handleClickDirectoryExpanding;
2230
+ case DirectoryExpanded:
2231
+ return handleClickDirectoryExpanded$1;
2232
+ case Symlink:
2233
+ return handleClickSymLink$1;
2234
+ case CharacterDevice:
2235
+ throw new Error('Cannot open character device files');
2236
+ case BlockDevice:
2237
+ throw new Error('Cannot open block device files');
2238
+ case Socket:
2239
+ throw new Error('Cannot open socket files');
2240
+ default:
2241
+ throw new Error(`unsupported dirent type ${direntType}`);
2242
+ }
2243
+ };
2244
+
2245
+ const getIndexFromPosition = (state, eventX, eventY) => {
2246
+ const {
2247
+ y,
2248
+ itemHeight,
1848
2249
  items
1849
2250
  } = state;
1850
- const dirent = items[focusedIndex + minLineY];
1851
- return dirent;
2251
+ const index = Math.floor((eventY - y) / itemHeight);
2252
+ if (index < 0) {
2253
+ return 0;
2254
+ }
2255
+ if (index >= items.length) {
2256
+ return -1;
2257
+ }
2258
+ return index;
2259
+ };
2260
+
2261
+ const getParentStartIndex = (dirents, index) => {
2262
+ const dirent = dirents[index];
2263
+ let startIndex = index - 1;
2264
+ while (startIndex >= 0 && dirents[startIndex].depth >= dirent.depth) {
2265
+ startIndex--;
2266
+ }
2267
+ return startIndex;
1852
2268
  };
1853
2269
 
1854
2270
  const Keyboard = -1;
@@ -1917,7 +2333,7 @@ const removeDirent = async state => {
1917
2333
  if (state.focusedIndex < 0) {
1918
2334
  return state;
1919
2335
  }
1920
- const dirent = getFocusedDirent(state);
2336
+ const dirent = getFocusedDirent$1(state);
1921
2337
  const absolutePath = dirent.path;
1922
2338
  try {
1923
2339
  // TODO handle error
@@ -1992,7 +2408,7 @@ const cancelEdit = state => {
1992
2408
  focused: true,
1993
2409
  editingIndex: -1,
1994
2410
  editingValue: '',
1995
- editingType: None$1
2411
+ editingType: None$3
1996
2412
  };
1997
2413
  };
1998
2414
 
@@ -2039,15 +2455,6 @@ const handleClickDirectory = async (state, dirent, index, keepFocus) => {
2039
2455
  maxLineY
2040
2456
  };
2041
2457
  };
2042
- const handleClickDirectoryExpanding = (state, dirent, index, keepFocus) => {
2043
- dirent.type = Directory;
2044
- dirent.icon = getIcon();
2045
- return {
2046
- ...state,
2047
- focusedIndex: index,
2048
- focused: keepFocus
2049
- };
2050
- };
2051
2458
  const handleClickDirectoryExpanded = (state, dirent, index, keepFocus) => {
2052
2459
  const {
2053
2460
  minLineY,
@@ -2084,30 +2491,6 @@ const handleClickDirectoryExpanded = (state, dirent, index, keepFocus) => {
2084
2491
  focused: keepFocus
2085
2492
  };
2086
2493
  };
2087
- const getClickFn = direntType => {
2088
- switch (direntType) {
2089
- case File:
2090
- case SymLinkFile:
2091
- return handleClickFile;
2092
- case Directory:
2093
- case SymLinkFolder:
2094
- return handleClickDirectory;
2095
- case DirectoryExpanding:
2096
- return handleClickDirectoryExpanding;
2097
- case DirectoryExpanded:
2098
- return handleClickDirectoryExpanded;
2099
- case Symlink:
2100
- return handleClickSymLink;
2101
- case CharacterDevice:
2102
- throw new Error('Cannot open character device files');
2103
- case BlockDevice:
2104
- throw new Error('Cannot open block device files');
2105
- case Socket:
2106
- throw new Error('Cannot open socket files');
2107
- default:
2108
- throw new Error(`unsupported dirent type ${direntType}`);
2109
- }
2110
- };
2111
2494
  const handleClick = (state, index, keepFocus = false) => {
2112
2495
  const {
2113
2496
  items,
@@ -2219,21 +2602,6 @@ const handleArrowLeft = state => {
2219
2602
  // TODO what happens when mouse leave and anther mouse enter event occur?
2220
2603
  // should update preview instead of closing and reopening
2221
2604
 
2222
- const handleBlur = state => {
2223
- // TODO when blur event occurs because of context menu, focused index should stay the same
2224
- // but focus outline should be removed
2225
- const {
2226
- editingType
2227
- } = state;
2228
- if (editingType !== None$1) {
2229
- return state;
2230
- }
2231
- return {
2232
- ...state,
2233
- focused: false
2234
- };
2235
- };
2236
-
2237
2605
  // TODO maybe just insert items into explorer and refresh whole explorer
2238
2606
 
2239
2607
  const handleClickOpenFolder = async state => {
@@ -2241,18 +2609,6 @@ const handleClickOpenFolder = async state => {
2241
2609
  return state;
2242
2610
  };
2243
2611
 
2244
- const state = {
2245
- rpc: undefined
2246
- };
2247
- const invoke = (method, ...params) => {
2248
- const rpc = state.rpc;
2249
- // @ts-ignore
2250
- return rpc.invoke(method, ...params);
2251
- };
2252
- const setRpc = rpc => {
2253
- state.rpc = rpc;
2254
- };
2255
-
2256
2612
  const show = async (x, y, id, ...args) => {
2257
2613
  return invoke('ContextMenu.show', x, y, id, ...args);
2258
2614
  };
@@ -2294,18 +2650,321 @@ const handleContextMenu = (state, button, x, y) => {
2294
2650
  }
2295
2651
  };
2296
2652
 
2297
- const writeText = async text => {
2298
- await invoke('ClipBoard.writeText', /* text */text);
2653
+ const getFilePathElectron = async file => {
2654
+ return invoke('GetFilePathElectron.getFilePathElectron', file);
2299
2655
  };
2300
2656
 
2301
- const copyRelativePath = async state => {
2302
- const dirent = getFocusedDirent(state);
2303
- const relativePath = dirent.path.slice(1);
2304
- // TODO handle error
2305
- await writeText(relativePath);
2657
+ const mergeDirents$2 = (oldDirents, newDirents) => {
2658
+ return newDirents;
2659
+ };
2660
+
2661
+ // TODO copy files in parallel
2662
+ const copyFilesElectron = async (root, pathSeparator, files) => {
2663
+ for (const file of files) {
2664
+ await getFilePathElectron(file);
2665
+ // const from = file.path
2666
+ join(pathSeparator, root, file.name);
2667
+ await copy$1();
2668
+ }
2669
+ };
2670
+ const getMergedDirents$2 = async (root, pathSeparator, dirents) => {
2671
+ const childDirents = await getChildDirents(pathSeparator, {
2672
+ path: root,
2673
+ depth: 0
2674
+ });
2675
+ const mergedDirents = mergeDirents$2(dirents, childDirents);
2676
+ return mergedDirents;
2677
+ };
2678
+ const handleDrop$2 = async (state, files) => {
2679
+ const {
2680
+ root,
2681
+ pathSeparator,
2682
+ items
2683
+ } = state;
2684
+ await copyFilesElectron(root, pathSeparator, files);
2685
+ const mergedDirents = await getMergedDirents$2(root, pathSeparator, items);
2686
+ return {
2687
+ ...state,
2688
+ items: mergedDirents,
2689
+ dropTargets: []
2690
+ };
2691
+ };
2692
+
2693
+ const uploadFileSystemHandles = async (root, pathSeparator, fileSystemHandles) => {
2694
+ // TODO send to renderer worker
2695
+ };
2696
+
2697
+ const mergeDirents$1 = (oldDirents, newDirents) => {
2698
+ return newDirents;
2699
+ };
2700
+ const getMergedDirents$1 = async (root, pathSeparator, dirents) => {
2701
+ const childDirents = await getChildDirents(pathSeparator, {
2702
+ path: root,
2703
+ depth: 0
2704
+ });
2705
+ const mergedDirents = mergeDirents$1(dirents, childDirents);
2706
+ return mergedDirents;
2707
+ };
2708
+ const handleDrop$1 = async (state, files) => {
2709
+ const {
2710
+ root,
2711
+ pathSeparator,
2712
+ items
2713
+ } = state;
2714
+ const handled = await uploadFileSystemHandles();
2715
+ if (handled) {
2716
+ return state;
2717
+ }
2718
+ const mergedDirents = await getMergedDirents$1(root, pathSeparator, items);
2719
+ return {
2720
+ ...state,
2721
+ items: mergedDirents,
2722
+ dropTargets: []
2723
+ };
2724
+ };
2725
+
2726
+ const getModule = isElectron => {
2727
+ if (isElectron) {
2728
+ return handleDrop$2;
2729
+ }
2730
+ return handleDrop$1;
2731
+ };
2732
+ const handleDropRoot = async (state, files) => {
2733
+ const fn = await getModule(state.isElectron);
2734
+ return fn(state, files);
2735
+ };
2736
+
2737
+ const getEndIndex = (items, index, dirent) => {
2738
+ for (let i = index + 1; i < items.length; i++) {
2739
+ if (items[i].depth === dirent.depth) {
2740
+ return i;
2741
+ }
2742
+ }
2743
+ return items.length;
2744
+ };
2745
+ const getMergedDirents = (items, index, dirent, childDirents) => {
2746
+ const startIndex = index;
2747
+ const endIndex = getEndIndex(items, index, dirent);
2748
+ const mergedDirents = [...items.slice(0, startIndex), {
2749
+ ...dirent,
2750
+ type: DirectoryExpanded
2751
+ }, ...childDirents, ...items.slice(endIndex)];
2752
+ return mergedDirents;
2753
+ };
2754
+ const handleDropIntoFolder = async (state, dirent, index, files) => {
2755
+ const {
2756
+ pathSeparator,
2757
+ items
2758
+ } = state;
2759
+ for (const file of files) {
2760
+ await copy$1();
2761
+ }
2762
+ const childDirents = await getChildDirents(pathSeparator, dirent);
2763
+ const mergedDirents = getMergedDirents(items, index, dirent, childDirents);
2764
+ // TODO update maxlineY
2765
+ return {
2766
+ ...state,
2767
+ items: mergedDirents,
2768
+ dropTargets: []
2769
+ };
2770
+ };
2771
+ const handleDropIntoFile = (state, dirent, index, files) => {
2772
+ const {
2773
+ items
2774
+ } = state;
2775
+ const parentIndex = getParentStartIndex(items, index);
2776
+ if (parentIndex === -1) {
2777
+ return handleDropRoot(state, files);
2778
+ }
2779
+ // @ts-ignore
2780
+ return handleDropIndex(parentIndex);
2781
+ };
2782
+ const handleDropIndex = (state, index, files) => {
2783
+ const {
2784
+ items
2785
+ } = state;
2786
+ const dirent = items[index];
2787
+ // TODO if it is a file, drop into the folder of the file
2788
+ // TODO if it is a folder, drop into the folder
2789
+ // TODO if it is a symlink, read symlink and determine if file can be dropped
2790
+ switch (dirent.type) {
2791
+ case Directory:
2792
+ case DirectoryExpanded:
2793
+ return handleDropIntoFolder(state, dirent, index, files);
2794
+ case File:
2795
+ return handleDropIntoFile(state, dirent, index, files);
2796
+ default:
2797
+ return state;
2798
+ }
2799
+ };
2800
+
2801
+ const normalizeLine = line => {
2802
+ if (line.startsWith('Error: ')) {
2803
+ return line.slice('Error: '.length);
2804
+ }
2805
+ if (line.startsWith('VError: ')) {
2806
+ return line.slice('VError: '.length);
2807
+ }
2808
+ return line;
2809
+ };
2810
+ const getCombinedMessage = (error, message) => {
2811
+ const stringifiedError = normalizeLine(`${error}`);
2812
+ if (message) {
2813
+ return `${message}: ${stringifiedError}`;
2814
+ }
2815
+ return stringifiedError;
2816
+ };
2817
+ const NewLine = '\n';
2818
+ const getNewLineIndex = (string, startIndex = undefined) => {
2819
+ return string.indexOf(NewLine, startIndex);
2820
+ };
2821
+ const mergeStacks = (parent, child) => {
2822
+ if (!child) {
2823
+ return parent;
2824
+ }
2825
+ const parentNewLineIndex = getNewLineIndex(parent);
2826
+ const childNewLineIndex = getNewLineIndex(child);
2827
+ if (childNewLineIndex === -1) {
2828
+ return parent;
2829
+ }
2830
+ const parentFirstLine = parent.slice(0, parentNewLineIndex);
2831
+ const childRest = child.slice(childNewLineIndex);
2832
+ const childFirstLine = normalizeLine(child.slice(0, childNewLineIndex));
2833
+ if (parentFirstLine.includes(childFirstLine)) {
2834
+ return parentFirstLine + childRest;
2835
+ }
2836
+ return child;
2837
+ };
2838
+ class VError extends Error {
2839
+ constructor(error, message) {
2840
+ const combinedMessage = getCombinedMessage(error, message);
2841
+ super(combinedMessage);
2842
+ this.name = 'VError';
2843
+ if (error instanceof Error) {
2844
+ this.stack = mergeStacks(this.stack, error.stack);
2845
+ }
2846
+ if (error.codeFrame) {
2847
+ // @ts-ignore
2848
+ this.codeFrame = error.codeFrame;
2849
+ }
2850
+ if (error.code) {
2851
+ // @ts-ignore
2852
+ this.code = error.code;
2853
+ }
2854
+ }
2855
+ }
2856
+
2857
+ const handleDrop = async (state, x, y, files) => {
2858
+ try {
2859
+ const index = getIndexFromPosition(state, x, y);
2860
+ switch (index) {
2861
+ case -1:
2862
+ return await handleDropRoot(state, files);
2863
+ default:
2864
+ return await handleDropIndex(state, index, files);
2865
+ }
2866
+ } catch (error) {
2867
+ throw new VError(error, 'Failed to drop files');
2868
+ }
2869
+ };
2870
+
2871
+ const getTopLevelDirents = (root, pathSeparator, excluded) => {
2872
+ if (!root) {
2873
+ return [];
2874
+ }
2875
+ return getChildDirents(pathSeparator, {
2876
+ depth: 0,
2877
+ path: root,
2878
+ type: Directory
2879
+ }, excluded);
2880
+ };
2881
+
2882
+ const mergeDirents = (oldDirents, newDirents) => {
2883
+ const merged = [];
2884
+ for (const newDirent of newDirents) {
2885
+ merged.push(newDirent);
2886
+ }
2887
+ return merged;
2888
+ };
2889
+
2890
+ // TODO add lots of tests for this
2891
+ const updateRoot = async state1 => {
2892
+ if (state1.disposed) {
2893
+ return state1;
2894
+ }
2895
+ // const file = nativeFiles.files[0]
2896
+ // @ts-ignore
2897
+ const topLevelDirents = await getTopLevelDirents(state1.root, state1.pathSeparator);
2898
+ // const state2 = Viewlet.getState('Explorer')
2899
+ // // TODO what if root changes while reading directories?
2900
+ // if (state2.disposed || state2.root !== state1.root) {
2901
+ // return state2
2902
+ // }
2903
+ const newDirents = mergeDirents(state1.items, topLevelDirents);
2904
+ const state3 = {
2905
+ ...state1,
2906
+ items: newDirents
2907
+ };
2908
+ return state3;
2909
+ };
2910
+
2911
+ const handlePasteCopy = async (state, nativeFiles) => {
2912
+ // TODO handle pasting files into nested folder
2913
+ // TODO handle pasting files into symlink
2914
+ // TODO handle pasting files into broken symlink
2915
+ // TODO handle pasting files into hardlink
2916
+ // TODO what if folder is big and it takes a long time
2917
+ for (const source of nativeFiles.files) {
2918
+ join(state.pathSeperator, state.root, getBaseName(state.pathSeparator, source));
2919
+ await copy$1();
2920
+ }
2921
+ // TODO only update folder at which level it changed
2922
+ return updateRoot(state);
2923
+ };
2924
+
2925
+ const handlePasteCut = async (state, nativeFiles) => {
2926
+ for (const source of nativeFiles.files) {
2927
+ `${state.root}${state.pathSeparator}${getBaseName(state.pathSeparator, source)}`;
2928
+ await rename$1();
2929
+ }
2930
+ return state;
2931
+ };
2932
+
2933
+ const handlePasteNone = (state, nativeFiles) => {
2934
+ console.info('[ViewletExplorer/handlePaste] no paths detected');
2306
2935
  return state;
2307
2936
  };
2308
2937
 
2938
+ const None = 'none';
2939
+ const Copy = 'copy';
2940
+ const Cut = 'cut';
2941
+
2942
+ const handlePaste = async state => {
2943
+ const nativeFiles = await readNativeFiles();
2944
+ // TODO detect cut/paste event, not sure if that is possible
2945
+ // TODO check that pasted folder is not a parent folder of opened folder
2946
+ // TODO support pasting multiple paths
2947
+ // TODO what happens when pasting multiple paths, but some of them error?
2948
+ // how many error messages should be shown? Should the operation be undone?
2949
+ // TODO what if it is a large folder and takes a long time to copy? Should show progress
2950
+ // TODO what if there is a permission error? Probably should show a modal to ask for permission
2951
+ // TODO if error is EEXISTS, just rename the copy (e.g. file-copy-1.txt, file-copy-2.txt)
2952
+ // TODO actual target should be selected folder
2953
+ // TODO but what if a file is currently selected? Then maybe the parent folder
2954
+ // TODO but will it work if the folder is a symlink?
2955
+ // TODO handle error gracefully when copy fails
2956
+ switch (nativeFiles.type) {
2957
+ case None:
2958
+ return handlePasteNone(state);
2959
+ case Copy:
2960
+ return handlePasteCopy(state, nativeFiles);
2961
+ case Cut:
2962
+ return handlePasteCut(state, nativeFiles);
2963
+ default:
2964
+ throw new Error(`unexpected native paste type: ${nativeFiles.type}`);
2965
+ }
2966
+ };
2967
+
2309
2968
  const handlePointerDown = (state, button, x, y) => {
2310
2969
  const index = getIndexFromPosition(state, x, y);
2311
2970
  if (button === LeftClick && index === -1) {
@@ -2428,9 +3087,226 @@ const restoreState = savedState => {
2428
3087
  };
2429
3088
  };
2430
3089
 
2431
- const copyPath = async state => {
2432
- // await Command.execute(RendererWorkerCommandType.ClipBoardWriteText, /* text */ path)
2433
- return state;
3090
+ const getIndex = (dirents, uri) => {
3091
+ for (let i = 0; i < dirents.length; i++) {
3092
+ const dirent = dirents[i];
3093
+ if (dirent.path === uri) {
3094
+ return i;
3095
+ }
3096
+ }
3097
+ return -1;
3098
+ };
3099
+
3100
+ const getPathParts = (root, uri, pathSeparator) => {
3101
+ const parts = [];
3102
+ let index = root.length - 1;
3103
+ let depth = 0;
3104
+ while ((index = uri.indexOf('/', index + 1)) !== -1) {
3105
+ const partUri = uri.slice(0, index);
3106
+ parts.push({
3107
+ path: partUri,
3108
+ depth: depth++,
3109
+ root,
3110
+ pathSeparator
3111
+ });
3112
+ }
3113
+ return parts;
3114
+ };
3115
+
3116
+ const isTopLevel = dirent => {
3117
+ return dirent.depth === 1;
3118
+ };
3119
+ const orderDirents = dirents => {
3120
+ if (dirents.length === 0) {
3121
+ return dirents;
3122
+ }
3123
+ // const parentMap = Object.create(null)
3124
+ // for(const dirent of dirents){
3125
+ // const parentPath = dirent.slice(0, dirent.lastIndexOf('/'))
3126
+ // parentMap[parentPath]||=[]
3127
+ // parentMap[parentPath].push(dirent)
3128
+ // }
3129
+ const withDeepChildren = parent => {
3130
+ const children = [];
3131
+ for (const dirent of dirents) {
3132
+ if (dirent.depth === parent.depth + 1 && dirent.path.startsWith(parent.path)) {
3133
+ children.push(dirent, ...withDeepChildren(dirent));
3134
+ }
3135
+ }
3136
+ return [parent, ...children];
3137
+ };
3138
+ const topLevelDirents = dirents.filter(isTopLevel);
3139
+ const ordered = topLevelDirents.flatMap(withDeepChildren);
3140
+ return ordered;
3141
+ };
3142
+
3143
+ const scrollInto = (index, minLineY, maxLineY) => {
3144
+ const diff = maxLineY - minLineY;
3145
+ const smallerHalf = Math.floor(diff / 2);
3146
+ const largerHalf = diff - smallerHalf;
3147
+ if (index < minLineY) {
3148
+ return {
3149
+ newMinLineY: index - smallerHalf,
3150
+ newMaxLineY: index + largerHalf
3151
+ };
3152
+ }
3153
+ if (index >= maxLineY) {
3154
+ return {
3155
+ newMinLineY: index - smallerHalf,
3156
+ newMaxLineY: index + largerHalf
3157
+ };
3158
+ }
3159
+ return {
3160
+ newMinLineY: minLineY,
3161
+ newMaxLineY: maxLineY
3162
+ };
3163
+ };
3164
+
3165
+ const getPathPartsToReveal = (root, pathParts, dirents) => {
3166
+ for (let i = 0; i < pathParts.length; i++) {
3167
+ const pathPart = pathParts[i];
3168
+ const index = getIndex(dirents, pathPart.uri);
3169
+ if (index === -1) {
3170
+ continue;
3171
+ }
3172
+ return pathParts.slice(i);
3173
+ }
3174
+ return pathParts;
3175
+ };
3176
+ const getPathPartChildren = pathPart => {
3177
+ const children = getChildDirents(pathPart.pathSeparator, pathPart);
3178
+ return children;
3179
+ };
3180
+ const mergeVisibleWithHiddenItems = (visibleItems, hiddenItems) => {
3181
+ const merged = [...hiddenItems];
3182
+ const seen = Object.create(null);
3183
+ const unique = [];
3184
+ for (const item of merged) {
3185
+ if (seen[item.path]) {
3186
+ continue;
3187
+ }
3188
+ seen[item.path] = true;
3189
+ unique.push(item);
3190
+ }
3191
+
3192
+ // depth one
3193
+ // let depth=1
3194
+ // while(true){
3195
+ // for(const item of unique){
3196
+ // if(item.depth===depth){
3197
+ // ordered.push(item)
3198
+ // }
3199
+ // }
3200
+ // break
3201
+ // }
3202
+ // const getChildren = (path) => {
3203
+ // const children = []
3204
+ // for (const item of unique) {
3205
+ // if (item.path.startsWith(path) && item.path !== path) {
3206
+ // ordered.push(item)
3207
+ // }
3208
+ // }
3209
+ // return children
3210
+ // }
3211
+ // for (const item of unique) {
3212
+ // for (const potentialChild of unique) {
3213
+ // if (
3214
+ // potentialChild.path.startsWith(item.path) &&
3215
+ // potentialChild.path !== item.path
3216
+ // ) {
3217
+ // ordered.push(potentialChild)
3218
+ // }
3219
+ // }
3220
+ // }
3221
+ // const refreshedRoots = Object.create(null)
3222
+ // for (const hiddenItem of hiddenItems) {
3223
+ // const parent = hiddenItem.path.slice(0, hiddenItem.path.lastIndexOf('/'))
3224
+ // refreshedRoots[parent] = true
3225
+ // }
3226
+ // const mergedDirents = []
3227
+ // for(const v)
3228
+ // for (const visibleItem of visibleItems) {
3229
+ // if (visibleItem.path === hiddenItemRoot) {
3230
+ // // TODO update aria posinset and aria setsize
3231
+ // mergedDirents.push(...hiddenItems)
3232
+ // } else {
3233
+ // for (const hiddenItem of hiddenItems) {
3234
+ // if (hiddenItem.path === visibleItem.path) {
3235
+ // continue
3236
+ // }
3237
+ // }
3238
+ // mergedDirents.push(visibleItem)
3239
+ // }
3240
+ // }
3241
+
3242
+ return unique;
3243
+ };
3244
+
3245
+ // TODO maybe just insert items into explorer and refresh whole explorer
3246
+ const revealItemHidden = async (state, uri) => {
3247
+ const {
3248
+ root,
3249
+ pathSeparator,
3250
+ items,
3251
+ minLineY,
3252
+ maxLineY
3253
+ } = state;
3254
+ const pathParts = getPathParts(root, uri, pathSeparator);
3255
+ if (pathParts.length === 0) {
3256
+ return state;
3257
+ }
3258
+ const pathPartsToReveal = getPathPartsToReveal(root, pathParts, items);
3259
+ const pathPartsChildren = await Promise.all(pathPartsToReveal.map(getPathPartChildren));
3260
+ const pathPartsChildrenFlat = pathPartsChildren.flat(1);
3261
+ const orderedPathParts = orderDirents(pathPartsChildrenFlat);
3262
+ const mergedDirents = mergeVisibleWithHiddenItems(items, orderedPathParts);
3263
+ const index = getIndex(mergedDirents, uri);
3264
+ if (index === -1) {
3265
+ throw new Error(`File not found in explorer ${uri}`);
3266
+ }
3267
+ const {
3268
+ newMinLineY,
3269
+ newMaxLineY
3270
+ } = scrollInto(index, minLineY, maxLineY);
3271
+ return {
3272
+ ...state,
3273
+ items: mergedDirents,
3274
+ focused: true,
3275
+ focusedIndex: index,
3276
+ minLineY: newMinLineY,
3277
+ maxLineY: newMaxLineY
3278
+ };
3279
+ };
3280
+
3281
+ const revealItemVisible = (state, index) => {
3282
+ const {
3283
+ minLineY,
3284
+ maxLineY
3285
+ } = state;
3286
+ const {
3287
+ newMinLineY,
3288
+ newMaxLineY
3289
+ } = scrollInto(index, minLineY, maxLineY);
3290
+ return {
3291
+ ...state,
3292
+ focused: true,
3293
+ focusedIndex: index,
3294
+ minLineY: newMinLineY,
3295
+ maxLineY: newMaxLineY
3296
+ };
3297
+ };
3298
+
3299
+ const revealItem = async (state, uri) => {
3300
+ object(state);
3301
+ string(uri);
3302
+ const {
3303
+ items
3304
+ } = state;
3305
+ const index = getIndex(items, uri);
3306
+ if (index === -1) {
3307
+ return revealItemHidden(state, uri);
3308
+ }
3309
+ return revealItemVisible(state, index);
2434
3310
  };
2435
3311
 
2436
3312
  const isExpandedDirectory = dirent => {
@@ -2460,15 +3336,17 @@ const saveState = state => {
2460
3336
  const commandMap = {
2461
3337
  'Explorer.acceptEdit': acceptEdit,
2462
3338
  'Explorer.cancelEdit': cancelEdit,
2463
- 'Explorer.copyPath': copyPath,
2464
- 'Explorer.copyRelativePath': copyRelativePath,
3339
+ 'Explorer.copyPath': copyPath$1,
3340
+ 'Explorer.copyRelativePath': copyRelativePath$1,
2465
3341
  'Explorer.expandAll': expandAll,
3342
+ 'Explorer.expandRecursively': expandRecursively,
2466
3343
  'Explorer.focusFirst': focusFirst,
2467
3344
  'Explorer.focusIndex': focusIndex,
2468
3345
  'Explorer.focusLast': focusLast,
2469
3346
  'Explorer.focusNext': focusNext,
2470
3347
  'Explorer.focusPrevious': focusPrevious,
2471
3348
  'Explorer.getKeyBindings': getKeyBindings,
3349
+ 'Explorer.getMenuEntries': getMenuEntries,
2472
3350
  'Explorer.getVirtualDom': getExplorerVirtualDom,
2473
3351
  'Explorer.getVisibleItems': getVisibleExplorerItems,
2474
3352
  'Explorer.handleArrowLeft': handleArrowLeft,
@@ -2479,7 +3357,9 @@ const commandMap = {
2479
3357
  'Explorer.handleClickCurrentButKeepFocus': handleClickCurrentButKeepFocus,
2480
3358
  'Explorer.handleClickOpenFolder': handleClickOpenFolder,
2481
3359
  'Explorer.handleContextMenu': handleContextMenu,
3360
+ 'Explorer.handleDrop': handleDrop,
2482
3361
  'Explorer.handleIconThemeChange': handleIconThemeChange,
3362
+ 'Explorer.handlePaste': handlePaste,
2483
3363
  'Explorer.handlePointerDown': handlePointerDown,
2484
3364
  'Explorer.handleWheel': handleWheel,
2485
3365
  'Explorer.loadContent': loadContent,
@@ -2487,6 +3367,7 @@ const commandMap = {
2487
3367
  'Explorer.removeDirent': removeDirent,
2488
3368
  'Explorer.renameDirent': renameDirent,
2489
3369
  'Explorer.restoreState': restoreState,
3370
+ 'Explorer.revealItem': revealItem,
2490
3371
  'Explorer.saveState': saveState
2491
3372
  };
2492
3373