@statiolake/coc-explorer 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/lib/index.js +139 -0
  3. package/package.json +13 -1
package/README.md CHANGED
@@ -9,6 +9,6 @@ single click; right click opens the item's context action menu through
9
9
  files intentionally have no type-specific icons.
10
10
 
11
11
  View actions define both the right-click context menu and NvimTree-style local
12
- keys: `o`, `<C-x>`, `<C-v>`, `<C-t>`, `a`, `d`, `r`, `R`, `gy`, `.`, `s`, `-`,
13
- `+`, `<BS>`, and `P`. Coc's native `<CR>`, `<Tab>`, `f`, `t`, and `M` TreeView
14
- bindings remain available.
12
+ keys: `o`, `<C-x>`, `<C-v>`, `<C-t>`, `a`, `x` (cut), `y` (copy), `p` (paste),
13
+ `d` (delete), `r`, `R`, `gy`, `.`, `s`, `-`, `+`, `<BS>`, and `P`. Coc's native
14
+ `<CR>`, `<Tab>`, `f`, `t`, and `M` TreeView bindings remain available.
package/lib/index.js CHANGED
@@ -212,6 +212,18 @@ var Explorer = class {
212
212
  "coc-explorer.delete",
213
213
  (node) => this.delete(node)
214
214
  ),
215
+ import_coc.commands.registerCommand(
216
+ "coc-explorer.cut",
217
+ (node) => this.stage(node, "cut")
218
+ ),
219
+ import_coc.commands.registerCommand(
220
+ "coc-explorer.copy",
221
+ (node) => this.stage(node, "copy")
222
+ ),
223
+ import_coc.commands.registerCommand(
224
+ "coc-explorer.paste",
225
+ (node) => this.paste(node)
226
+ ),
215
227
  import_coc.commands.registerCommand(
216
228
  "coc-explorer.copyPath",
217
229
  (node) => this.copyPath(node)
@@ -222,6 +234,7 @@ var Explorer = class {
222
234
  }
223
235
  provider = new ExplorerProvider();
224
236
  tree;
237
+ clipboard;
225
238
  refreshTimer;
226
239
  async show() {
227
240
  await this.ui.showContainer("explorer");
@@ -314,6 +327,7 @@ var Explorer = class {
314
327
  const target = import_node_path.default.isAbsolute(name) ? name : import_node_path.default.join(import_node_path.default.dirname(node.path), name);
315
328
  if (target === node.path) return;
316
329
  await import_node_fs.promises.rename(node.path, target);
330
+ this.clearClipboardWithin(node.path);
317
331
  this.provider.refresh(node.parent);
318
332
  }
319
333
  async delete(node) {
@@ -324,8 +338,71 @@ var Explorer = class {
324
338
  );
325
339
  if (answer !== "Delete") return;
326
340
  await import_node_fs.promises.rm(node.path, { recursive: node.directory });
341
+ this.clearClipboardWithin(node.path);
327
342
  this.provider.refresh(node.parent);
328
343
  }
344
+ stage(node, operation) {
345
+ if (!node) return;
346
+ this.clipboard = {
347
+ operation,
348
+ source: node.path,
349
+ directory: node.directory
350
+ };
351
+ import_coc.window.showInformationMessage(
352
+ `${operation === "copy" ? "Copied" : "Cut"} ${node.path}`
353
+ );
354
+ }
355
+ async paste(node) {
356
+ if (!node) return;
357
+ const entry = this.clipboard;
358
+ if (!entry) {
359
+ import_coc.window.showWarningMessage("Explorer clipboard is empty");
360
+ return;
361
+ }
362
+ const destination = node.directory ? node.path : import_node_path.default.dirname(node.path);
363
+ const target = import_node_path.default.join(destination, import_node_path.default.basename(entry.source));
364
+ if (target === entry.source) {
365
+ import_coc.window.showWarningMessage("Source and destination are the same");
366
+ return;
367
+ }
368
+ if (entry.directory && isWithin(target, entry.source)) {
369
+ import_coc.window.showWarningMessage("Cannot paste a directory inside itself");
370
+ return;
371
+ }
372
+ try {
373
+ await import_node_fs.promises.lstat(entry.source);
374
+ } catch {
375
+ this.clipboard = void 0;
376
+ import_coc.window.showWarningMessage(`Source no longer exists: ${entry.source}`);
377
+ return;
378
+ }
379
+ const replace = await pathExists(target);
380
+ if (replace) {
381
+ const answer = await import_coc.window.showWarningMessage(
382
+ `Replace ${target}?`,
383
+ "Replace"
384
+ );
385
+ if (answer !== "Replace") return;
386
+ }
387
+ await writeReplacing(target, replace, async () => {
388
+ if (entry.operation === "copy") {
389
+ await import_node_fs.promises.cp(entry.source, target, {
390
+ recursive: entry.directory,
391
+ errorOnExist: true,
392
+ force: false
393
+ });
394
+ } else {
395
+ await move(entry.source, target, entry.directory);
396
+ }
397
+ });
398
+ if (entry.operation === "cut") this.clipboard = void 0;
399
+ this.provider.refresh();
400
+ }
401
+ clearClipboardWithin(parent) {
402
+ if (this.clipboard && isWithin(this.clipboard.source, parent)) {
403
+ this.clipboard = void 0;
404
+ }
405
+ }
329
406
  async copyPath(node) {
330
407
  if (!node) return;
331
408
  await import_coc.workspace.nvim.call("setreg", ["+", node.path]);
@@ -423,6 +500,24 @@ var Explorer = class {
423
500
  keys: ["r"],
424
501
  handler: (node) => this.rename(node)
425
502
  },
503
+ {
504
+ id: "coc-explorer.cut",
505
+ title: "Cut",
506
+ keys: ["x"],
507
+ handler: (node) => this.stage(node, "cut")
508
+ },
509
+ {
510
+ id: "coc-explorer.copy",
511
+ title: "Copy",
512
+ keys: ["y"],
513
+ handler: (node) => this.stage(node, "copy")
514
+ },
515
+ {
516
+ id: "coc-explorer.paste",
517
+ title: "Paste",
518
+ keys: ["p"],
519
+ handler: (node) => this.paste(node)
520
+ },
426
521
  {
427
522
  id: "coc-explorer.delete",
428
523
  title: "Delete",
@@ -466,6 +561,50 @@ var Explorer = class {
466
561
  function fnameescape(filename) {
467
562
  return filename.replace(/([\\\s|"'])/g, "\\$1");
468
563
  }
564
+ function isWithin(filename, parent) {
565
+ const relative = import_node_path.default.relative(parent, filename);
566
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
567
+ }
568
+ async function pathExists(filename) {
569
+ try {
570
+ await import_node_fs.promises.lstat(filename);
571
+ return true;
572
+ } catch {
573
+ return false;
574
+ }
575
+ }
576
+ async function writeReplacing(target, replace, write) {
577
+ if (!replace) {
578
+ await write();
579
+ return;
580
+ }
581
+ const backup = import_node_path.default.join(
582
+ import_node_path.default.dirname(target),
583
+ `.coc-explorer-${import_node_path.default.basename(target)}-${process.pid}-${Date.now()}`
584
+ );
585
+ await import_node_fs.promises.rename(target, backup);
586
+ try {
587
+ await write();
588
+ } catch (error) {
589
+ await import_node_fs.promises.rm(target, { recursive: true, force: true });
590
+ await import_node_fs.promises.rename(backup, target);
591
+ throw error;
592
+ }
593
+ await import_node_fs.promises.rm(backup, { recursive: true, force: true });
594
+ }
595
+ async function move(source, target, directory) {
596
+ try {
597
+ await import_node_fs.promises.rename(source, target);
598
+ } catch (error) {
599
+ if (error.code !== "EXDEV") throw error;
600
+ await import_node_fs.promises.cp(source, target, {
601
+ recursive: directory,
602
+ errorOnExist: true,
603
+ force: false
604
+ });
605
+ await import_node_fs.promises.rm(source, { recursive: directory });
606
+ }
607
+ }
469
608
  async function activate(context) {
470
609
  const extension = import_coc.extensions.getExtensionById("@statiolake/coc-ui");
471
610
  if (!extension?.exports) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statiolake/coc-explorer",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A lean filesystem explorer for coc.nvim view containers",
5
5
  "author": "statiolake",
6
6
  "license": "MIT",
@@ -85,6 +85,18 @@
85
85
  "command": "coc-explorer.delete",
86
86
  "title": "Delete Explorer Item"
87
87
  },
88
+ {
89
+ "command": "coc-explorer.cut",
90
+ "title": "Cut Explorer Item"
91
+ },
92
+ {
93
+ "command": "coc-explorer.copy",
94
+ "title": "Copy Explorer Item"
95
+ },
96
+ {
97
+ "command": "coc-explorer.paste",
98
+ "title": "Paste Explorer Item"
99
+ },
88
100
  {
89
101
  "command": "coc-explorer.copyPath",
90
102
  "title": "Copy Explorer Item Path"