From eac1165a1ab716a065424a031de97f707ee48d6b Mon Sep 17 00:00:00 2001 From: Akib Azmain Turja Date: Thu, 1 Dec 2022 01:40:17 +0600 Subject: [PATCH] Support directory tracking with OSC 7 * eat.el: Require 'url'. * eat.el (eat-enable-directory-tracking): New user option. * eat.el (eat--t-term): New slots: 'cwd' and 'set-cwd-fn'. * eat.el (eat--t-set-cwd, eat-term-cwd): New function. * eat.el (eat-term-set-cwd-function): New function and generalized variable. * eat.el (eat--t-handle-output): Handle OSC 7. * eat.el (eat--set-cwd): New function. * eat.el (eat-exec): Use 'eat--set-cwd' to change current working directory. * eat.el (eat--eshell-invocation-directory): New variable, local in Eshell buffers when 'eat-eshell-mode' is enabled. * eat.el (eat--eshell-setup-proc-and-term): Save the invocation directory of process. * eat.el (eat--eshell-cleanup): Revert working directory to invocation directory. * eat.el (eat--eshell-local-mode): When enabling, make 'eat--eshell-invocation-directory' local variable and kill when disabling. * eat-tests.el (eat-test-set-cwd): New test. * eat.texi (Directory Tracking): New chapter. --- eat-tests.el | 24 +++++++++++++++ eat.el | 85 ++++++++++++++++++++++++++++++++++++++++++++++++---- eat.texi | 33 ++++++++++++++++++++ 3 files changed, 136 insertions(+), 6 deletions(-) diff --git a/eat-tests.el b/eat-tests.el index 396504d..4149297 100644 --- a/eat-tests.el +++ b/eat-tests.el @@ -5748,6 +5748,30 @@ Write plain text and newline to move cursor." "frob") :cursor '(1 . 4)))) +(ert-deftest eat-test-set-cwd () + "Test setting current working directory." + (eat--tests-with-term '() + (let ((cwd default-directory)) + (should (string= (eat-term-cwd (terminal)) default-directory)) + (setf (eat-term-set-cwd-function (terminal)) + (lambda (term dir) + (should (eq term (terminal))) + (setq cwd dir))) + ;; file://HOST/PATH/. + (output (format "\e]7;file://%s/foo/bar/\e\\" (system-name))) + (should (string= cwd "/foo/bar/")) + (should (string= (eat-term-cwd (terminal)) "/foo/bar/")) + ;; file://HOST/PATH (note the missing trailing slash). + (output (format "\e]7;file://%s/bar/baz\a" (system-name))) + (should (string= cwd "/bar/baz/")) + (should (string= (eat-term-cwd (terminal)) "/bar/baz/")) + ;; file://SOME-OTHER-HOST/PATH/ + (output (format "\e]7;file://%s/baz/foo/\e\\" + (if (string= (system-name) "foo") "bar" "foo"))) + (should (string= cwd "/bar/baz/")) + (should (string= (eat-term-cwd (terminal)) "/bar/baz/")) + (should-term :cursor '(1 . 1))))) + ;;;;; Input Event Tests. diff --git a/eat.el b/eat.el index 56c511d..d3252e4 100644 --- a/eat.el +++ b/eat.el @@ -81,6 +81,7 @@ (require 'cl-lib) (require 'ansi-color) (require 'shell) +(require 'url) ;;;; User Options. @@ -139,6 +140,16 @@ This is left disabled for security reasons." :group 'eat-ui :group 'eat-eshell) +(defcustom eat-enable-directory-tracking t + "Non-nil means do directory tracking. + +When non-nil, Eat will track the working directory of program. You +need to configure the program to send current working directory +information." + :type 'boolean + :group 'eat-ui + :group 'eat-eshell) + (defconst eat--cursor-type-value-type (let ((cur-type '(choice @@ -647,6 +658,9 @@ For example: when THRESHOLD is 3, \"*foobarbaz\" is converted to (begin nil :documentation "Beginning of terminal.") (end nil :documentation "End of terminal area.") (title "" :documentation "The title of the terminal.") + (cwd + default-directory + :documentation "The working directory of the terminal.") (bell-fn (1value #'ignore) :documentation "Function to ring the bell.") @@ -662,6 +676,9 @@ For example: when THRESHOLD is 3, \"*foobarbaz\" is converted to (set-title-fn (1value #'ignore) :documentation "Function to set title.") + (set-cwd-fn + (1value #'ignore) + :documentation "Function to set the current working directory.") (grab-mouse-fn (1value #'ignore) :documentation "Function to grab mouse.") @@ -2189,6 +2206,25 @@ MODE should be one of nil and `x10', `normal', `button-event', ;; Inform the UI. (funcall (eat--t-term-set-title-fn eat--t-term) eat--t-term title)) +(defun eat--t-set-cwd (url) + "Set the working directory of terminal to URL. + +URL should be a URL in the format \"file://HOST/CWD/\"; HOST can be +empty." + (message "%S" url) + (let ((obj (url-generic-parse-url url))) + (when (and (string= (url-type obj) "file") + (or (null (url-host obj)) + (string= (url-host obj) (system-name)))) + (let ((dir (expand-file-name + (file-name-as-directory + (url-unhex-string (url-filename obj)))))) + ;; Update working directory. + (setf (eat--t-term-cwd eat--t-term) dir) + ;; Inform the UI. + (funcall (eat--t-term-set-cwd-fn eat--t-term) + eat--t-term dir))))) + (defun eat--t-send-device-attrs (params format) "Return device attributes. @@ -2701,6 +2737,11 @@ DATA is the selection data encoded in base64." (let title (zero-or-more anything)) string-end) (eat--t-set-title title)) + ;; OSC 7 ; ST. + ((rx string-start ?7 ?\; + (let url (zero-or-more anything)) + string-end) + (eat--t-set-cwd url)) ;; OSC 10 ; ? ST. ("10;?" (eat--t-report-foreground-color)) @@ -2971,6 +3012,24 @@ FUNCTION), where FUNCTION is the function to set title." (gv-define-setter eat-term-set-title-function (function terminal) `(setf (eat--t-term-set-title-fn ,terminal) ,function)) +(defun eat-term-cwd (terminal) + "Return the current working directory of TERMINAL." + (eat--t-term-cwd terminal)) + +(defun eat-term-set-cwd-function (terminal) + "Return the function used to set the working directory of TERMINAL. + +The function is called with two arguments, TERMINAL and the new +\(current) working directory of TERMINAL. The function should not +change point and buffer restriction. + +To set it, use (`setf' (`eat-term-set-cwd-function' TERMINAL) +FUNCTION), where FUNCTION is the function to set title." + (eat--t-term-set-cwd-fn terminal)) + +(gv-define-setter eat-term-set-cwd-function (function terminal) + `(setf (eat--t-term-set-cwd-fn ,terminal) ,function)) + (defun eat-term-grab-mouse-function (terminal) "Return the function used to grab the mouse. @@ -4185,6 +4244,12 @@ selection, or nil if none." "Ring the bell." (ding t)) +(defun eat--set-cwd (_ cwd) + "Set CWD as the current working directory (`default-directory')." + (when eat-enable-directory-tracking + (ignore-errors + (cd-absolute cwd)))) + ;;;;; Major Mode. @@ -4488,7 +4553,8 @@ same Eat buffer. The hook `eat-exec-hook' is run after each exec." #'eat--grab-mouse (eat-term-manipulate-selection-function eat--terminal) #'eat--manipulate-kill-ring - (eat-term-ring-bell-function eat--terminal) #'eat--bell) + (eat-term-ring-bell-function eat--terminal) #'eat--bell + (eat-term-set-cwd-function eat--terminal) #'eat--set-cwd) ;; Crank up a new process. (let* ((size (eat-term-size eat--terminal)) (process-environment @@ -4702,6 +4768,9 @@ PROGRAM can be a shell command." ;;;;; Process Handling. +(defvar eat--eshell-invocation-directory nil + "The directory from where the current process was started.") + (defvar eshell-last-output-start) ; In `esh-mode'. (defvar eshell-last-output-end) ; In `esh-mode'. @@ -4732,7 +4801,8 @@ PROGRAM can be a shell command." (defun eat--eshell-setup-proc-and-term (proc) "Setup process PROC and a new terminal for it." (unless (or eat--terminal eat--process) - (setq eat--process proc) + (setq eat--process proc + eat--eshell-invocation-directory default-directory) (process-put proc 'adjust-window-size-function #'eat--adjust-process-window-size) (setq eat--terminal (eat-term-make (current-buffer) @@ -4745,20 +4815,22 @@ PROGRAM can be a shell command." #'eat--grab-mouse (eat-term-manipulate-selection-function eat--terminal) #'eat--manipulate-kill-ring - (eat-term-ring-bell-function eat--terminal) #'eat--bell) + (eat-term-ring-bell-function eat--terminal) #'eat--bell + (eat-term-set-cwd-function eat--terminal) #'eat--set-cwd) (when-let* ((window (get-buffer-window nil t))) (with-selected-window window (eat-term-resize eat--terminal (window-max-chars-per-line) (window-text-height)))) (eat-term-redisplay eat--terminal) - (make-local-variable 'eshell-output-filter-functions) - (setq eshell-output-filter-functions '(eat--eshell-output-filter)) + (setq-local eshell-output-filter-functions + '(eat--eshell-output-filter)) (eat-eshell-semi-char-mode))) (defun eat--eshell-cleanup () "Cleanup everything." (when eat--terminal (let ((inhibit-read-only t)) + (cd-absolute eat--eshell-invocation-directory) (goto-char (eat-term-end eat--terminal)) (unless (or (= (point) (point-min)) (= (char-before) ?\n)) @@ -4923,7 +4995,8 @@ sane 2>%s ; if [ $1 = .. ]; then shift; fi; exec \"$@\"" eat--mouse-grabbing-type eat--pending-output-chunks eat--output-queue-first-chunk-time - eat--process-output-queue-timer))) + eat--process-output-queue-timer + eat--eshell-invocation-directory))) (cond (eat--eshell-local-mode (mapc #'make-local-variable locals) diff --git a/eat.texi b/eat.texi index 1b7f4b0..3dbe058 100644 --- a/eat.texi +++ b/eat.texi @@ -70,6 +70,7 @@ Advanced Customizations * Cursor Types:: Cursor can displayed in many forms. * Mouse Tracking:: Eat tracks mouse, but this can be changed. * Clipboard:: Integrating kill ring with terminal. +* Directory Tracking:: Tracking the working directory of program. * Colors:: Eat can show more than sixteen million colors. * Fonts:: Eat can show up to sixty font different fonts. * Blinking Text:: Annoying blinking texts. @@ -470,6 +471,38 @@ non-@code{nil}, programs can receive the kill ring contents. This is disabled by default for security reasons. @end defopt +@node Directory Tracking +@cindex directory tracking +@cindex tracking directory +@cindex working directory tracking +@cindex tracking working directory +@cindex cwd tracking +@cindex tracking cwd +@chapter Directory Tracking + +Eat can track the current working directory of the program. This also +works in Eshell, but after the program exits, the current working +directory is changed back to the directory from where the program was +invoked. + +To track, Eat needs the program to send this information. So, to +enable directory tracking, you'll need to setup your shell. You'll +need to arrange that your shell sends appropriate escape sequence at +each prompt, for example with the command: + +@example +printf "\\e]7;file://%s%s\\e\\\\" "$HOSTNAME" "$PWD" +@end example + +If you don't want Eat to track your program's working directory, you +can set the following to nil: + +@vindex eat-enable-directory-tracking +@defopt eat-enable-directory-tracking +This controls directory tracking. When set to non-@code{nil}, Eat +tracks the current working directory of programs. +@end defopt + @node Colors @cindex colors @cindex customizing colors