;;; mistty-osc7.el --- Add support for OSC 7 to term-mode  -*- lexical-binding: t -*-

;; 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.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see
;; `http://www.gnu.org/licenses/'.

;;; Commentary:
;;
;; This file defines a handler for OSC 7 code sequences that can be
;; registered in `mistty-osc-handlers' to allow shells to report
;; current directories to Emacs (dirtrack).
;;

(require 'url-util)

;;; Code:

(defcustom mistty-allow-tramp-paths t
  "If true, allow generating TRAMP paths as shell-specified directory.

This affects directories set using OSC 7, which can then build
TRAMP remote paths based on the hostname specified in the file://
URL, using the default method.

This option doesn't affect hosts configured using
`mistty-host-to-tramp-path-alist'."

  :group 'mistty
  :type '(boolean))

(defcustom mistty-host-to-tramp-path-alist nil
  "Maps hostnames to TRAMP paths.

This alist maps hosts as self-declared using OSC 7 to the TRAMP
path MisTTY should use for directory tracking.

This is useful for hosts that require a specific method to
connect to.

The values should be of the same form as the remote identifiers
returned by `file-remote-p', with method, user and host, for
example \"/ssh:example.com:\". Invalid values are ignored.

The values can also be nil to disable directory tracking for that
specific host.

Note that for the ssh method, it's often easier to add the
configuration option you need to a host to ~/.ssh/config."
  :group 'mistty
  :type '(alist :key-type (string :tag "Host")
                :value-type (choice (string :tag "TRAMP Path")
                                    (const nil))))

(defun mistty-osc7 (_ osc-seq)
  "Store OSC-SEQ as path to the current directory.

OSC-SEQ must be a file:// URL pointing to a directory to be used
as the current directory for the current shell.

By default, only local paths are taken into account. To allow
remote paths, configure `mistty-allow-tramp-paths.

The remote paths that are generated by default use the default
TRAMP method, which can be configured using
`tramp-default-method' and `tramp-default-method-alist'. If that
doesn't work for some hosts, add them to
`mistty-host-to-tramp-path-alist'.

Such sequences are typically printed out by shells just before
printing a prompt by a command such as:

  printf \"\\e]7;file://%s%s\\e\\\\\" \"$HOSTNAME\" \"$PWD\"

This can be used as a drop-in replacement for
`ansi-osc-directory-tracker', with the following tweaks:

 - it decodes percent-encoded non-ASCII characters in the paths,
 so such paths look better

 - it has optional supports for remote paths."
  (when (string-match "file://\\([^/]*\\)\\(/.*\\)" osc-seq)
    (let ((hostname (url-unhex-string (match-string 1 osc-seq)))
          (path (file-name-as-directory
                 (decode-coding-string
                  (url-unhex-string (match-string 2 osc-seq))
                  'utf-8
                  'nocopy)))
          host-config
          host-remote-path)
      (when (or (string= hostname "")
                (string= hostname "localhost"))
        (setq hostname (system-name)))
      (cond
       ;; Interpret path as being in the same TRAMP connection as
       ;; default-directory if the host names match. This is useful
       ;; for things like the sudo method, where the hostname is the
       ;; same, but accessed differently.
       ((equal (file-remote-p default-directory 'host) hostname)
        (setq default-directory
              (concat (file-remote-p default-directory) path)))

       ;; Interpret path as a straightforward local path.
       ((string= hostname (system-name))
        (ignore-errors (cd-absolute path)))

       ;; Host disabled in mistty-host-to-tramp-path-alist. Do
       ;; nothing.
       ((and (setq host-config
                   (assoc hostname mistty-host-to-tramp-path-alist))
             (null (cdr host-config))))

       ;; Using remote path from mistty-host-to-tramp-path-alist, if
       ;; it's valid.
       ((and (cdr host-config)
             (setq host-remote-path (file-remote-p (cdr host-config))))
        (setq default-directory (concat host-remote-path path)))

       ;; Generate default paths for a host, if enabled.
       (mistty-allow-tramp-paths
        (setq default-directory
              (concat (file-remote-p (concat "/-:" hostname ":"))
                      path)))))))

(provide 'mistty-osc7)

;;; mistty-osc7.el ends here
