aboutsummaryrefslogtreecommitdiff
path: root/duet.el
blob: 9ab858bc2c6e95a1b1badf7884877d8236a2ce4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
;;; duet.el --- Dual-pane file commander over dirvish/dired -*- lexical-binding: t -*-

;; Author: Craig Jennings <c@cjennings.net>
;; URL: https://github.com/cjennings/duet
;; Version: 0.1.0
;; Package-Requires: ((emacs "29.1"))
;; Keywords: files, tools, convenience

;; This file is not part of GNU Emacs.

;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;;; Commentary:

;; DUET — "DUET Unifies Endpoint Trees" — is a two-pane orthodox file
;; manager (Midnight Commander / FileZilla style) built on dirvish/dired.
;; Two dired panes show any location, local or remote; single-key actions
;; on the file under point use the opposite pane as the implied target.
;;
;; Transfers route through a pluggable backend registry: rsync for
;; Unix-to-Unix (delta-transfer, faithful metadata, zero remote install),
;; rclone for cloud and the long protocol tail, lftp for FTP/FTPS/HTTP, and
;; unison for bidirectional sync.  TRAMP is the universal substrate.
;;
;; dirvish is recommended as the renderer but not required; DUET degrades to
;; plain dired.
;;
;; STATUS: pre-alpha skeleton.  See the design document for the full plan and
;; the staged roadmap.

;;; Code:

(require 'tramp)

(defgroup duet nil
  "Dual-pane file commander over dirvish/dired."
  :group 'files
  :prefix "duet-")

;;; Path classification

(defun duet--classify-path (path)
  "Classify PATH into a plist describing its locality and components.

The returned plist has these keys:

  :locality   `local' or `remote'
  :method     TRAMP method (e.g. \"ssh\"), or nil when local
  :user       remote user, or nil
  :host       remote host (the final hop for a multi-hop path), or nil
  :port       remote port as a string, or nil
  :localname  the path on the target filesystem
  :hop        the leading-hops string for a multi-hop path, or nil

TRAMP performs the dissection, so any path `file-remote-p' recognizes is
remote and everything else is local.  A local PATH has its name expanded
\(so a leading ~ resolves to the home directory); a remote localname is kept
verbatim because a ~ there is the remote home, not this machine's.

Classification is total: a TRAMP-looking string TRAMP does not accept as a
remote name (an incomplete \"/ssh:host\") is treated as a local path rather
than signaling.  Validating raw TRAMP input is the connection reader's job."
  (if (file-remote-p path)
      (let ((v (tramp-dissect-file-name path)))
        (list :locality 'remote
              :method (tramp-file-name-method v)
              :user (tramp-file-name-user v)
              :host (tramp-file-name-host v)
              :port (tramp-file-name-port v)
              :localname (tramp-file-name-localname v)
              :hop (tramp-file-name-hop v)))
    (list :locality 'local
          :method nil
          :user nil
          :host nil
          :port nil
          :localname (expand-file-name path)
          :hop nil)))

;;;###autoload
(defun duet ()
  "Launch the DUET dual-pane file commander."
  (interactive)
  (user-error "DUET is not yet implemented — see the design document"))

(provide 'duet)
;;; duet.el ends here