Closed deadtrickster closed 5 years ago
This is how I hacked it for now:
(defun vterm--rename-buffer-as-title (title)
(let ((dir (string-trim-left (concat (nth 1 (split-string title ":")) "/"))))
(cd-absolute dir)
(rename-buffer (format "term %s" title)))
(add-hook 'vterm-set-title-functions 'vterm--rename-buffer-as-title)
title is <user>@<host>:path
I saw a PR and a few commits about that recently, they called it "directory tracking". I think you can find it in the recently closed PRs. If I recall correctly you have to setup some stuff in your shell's config files as well for it to work.
@deadtrickster Thanks for this! Now C-x C-f
works the way it should. 😃
For me, removing the buffer renaming and just updating the directory allows for multiple vterm buffers to have the same current directory.
(defun vterm--cd-to-directory-in-title (title)
"Change into directory extracted from path info in TITLE.
The format of TITLE is expected to be '<user>@<host>:path'
TITLE is based on the xterm style window title, set via the shell. See sections
3 and 4 on this page for how to set the TITLE via the shell:
http://www.faqs.org/docs/Linux-mini/Xterm-Title.html"
(let ((dir (concat (nth 1 (split-string title ":")) "/")))
(cd dir)))
(add-hook 'vterm-set-title-functions #'vterm--cd-to-directory-in-title)
I think leaving cwd in the buffer name helps a lot in finding the right vterm.
For me, removing the buffer renaming and just updating the directory allows for multiple vterm buffers to have the same current directory.
(rename-buffer NEWNAME &optional UNIQUE)
:around advice: ‘uniquify--rename-buffer-advice’
Change current buffer’s name to NEWNAME (a string). If second arg UNIQUE is nil or omitted, it is an error if a buffer named NEWNAME already exists. If UNIQUE is non-nil, come up with a new name using ‘generate-new-buffer-name’. Interactively, you can set UNIQUE with a prefix argument. We return the name we actually gave the buffer. This does not change the name of the visited file (if any).
(defun vterm-buffer-name(prefix cmd default-directory) (let* ((cmd (car (split-string cmd "[ |\t]" t " "))) (pwd (abbreviate-file-name default-directory)) (dir-tokens (split-string pwd "[/|\]" t " "))) (when (> (length dir-tokens) 2) (setq pwd (mapconcat 'identity (last dir-tokens 2) "/"))) (format "%s%s(%s)" prefix (or cmd "") pwd)))
(rename-buffer (vterm-buffer-name "vterm " "pwd" "~/a/b/c/d/e") t)
result:
"vterm pwd(d/e)"
Thanks @jixiuf. Changing
(rename-buffer (format "term %s" title)))
to
(rename-buffer (format "term %s" title) t))
solves the multiple vterms with same directory problem. 👍
I also have this snippet:
(make-local-variable 'vterm-is-for-project)
(setq-default vterm-is-for-project nil)
(defun vterm--rename-buffer-as-title (title)
(let ((dir (string-trim-left (concat (nth 1 (split-string title ":")) "/"))))
(cd-absolute dir)
(if (and vterm-is-for-project (not (projectile-project-p)))
;; we left project directory
(setq-local vterm-is-for-project nil))
(unless vterm-is-for-project
(rename-buffer (vterm--buffer-title) t))))
(add-hook 'vterm-set-title-functions 'vterm--rename-buffer-as-title)
(defun vterm--buffer-title ()
(format "term %s@%s:%s" user-login-name (system-name) default-directory))
(defun vterm--project-buffer-title ()
(format "term %s" (projectile-project-name)))
(defun vt ()
"Create a new vterm."
(interactive)
(let* ((buffer-title (vterm--buffer-title))
(existing-buffer (get-buffer buffer-title)))
(if existing-buffer
(switch-to-buffer existing-buffer)
(let ((buffer (generate-new-buffer buffer-title)))
(with-current-buffer buffer
(vterm-mode))
(switch-to-buffer buffer)))))
(defun vtp ()
"Create a new vterm."
(interactive)
(let* ((buffer-title (vterm--project-buffer-title))
(existing-buffer (get-buffer buffer-title)))
(if existing-buffer
(switch-to-buffer existing-buffer)
(let ((buffer (generate-new-buffer buffer-title)))
(with-current-buffer buffer
(cd (projectile-project-root))
(vterm-mode)
(setq-local vterm-is-for-project t))
(switch-to-buffer buffer)))))
Ugly and buggy as all my elisp (sigh) but vtp (re)opens the terminal for the current project, which I've found very handy.
Since this is a very important feature (at least, in my humble opinion), wouldn't be useful to add one of the suggested solutions to the readme?
Updated code to fix https://github.com/akermu/emacs-libvterm/issues/60
Thanks for the update, @deadtrickster, works for me. 👍 I suggest calling rename-buffer
with t
in addition to the new buffer name, to avoid a naming error when you have two vterms with the same directory. Also, there should be an extra paren at the end of the function definition:
(defun vterm--rename-buffer-as-title (title)
(let ((dir (string-trim-left (concat (nth 1 (split-string title ":")) "/"))))
(cd-absolute dir)
(rename-buffer (format "term %s" title) t)))
(add-hook 'vterm-set-title-functions #'vterm--rename-buffer-as-title)
@jhacksworth yeah I actually added t
to my config :-)
Updated my vterm--rename-buffer-as-title
so it can escape project directories properly - basically falling back to regular terminals. Feels good.
I've tried every single one of your approaches and I still cannot get my current directory to update when using C-x C-f. Even using a completely clean emacs init. Is there something I'm missing that was left out in the conversation? The entire init file I'm testing it with is below. The use-package
portion couple other irrelevant items are there simply because I always keep a very basic init file as a backup, and I just dropped the vterm
stuff in there for testing.
(require 'package)
(setq package-enable-at-startup nil)
(package-initialize)
(custom-set-variables
;; custom-set-variables was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(package-archives
(quote
(("Marmalade" . "https://marmalade-repo.org/packages/")
("GNU" . "http://elpa.gnu.org/packages/")
("MELPA" . "http://melpa.milkbox.net/packages/")
("MELPA-Stable" . "https://stable.melpa.org/packages/")))))
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(setq tool-bar-mode nil)
(menu-bar-mode -1)
(scroll-bar-mode -1)
(tool-bar-mode -1)
(blink-cursor-mode -1)
(tooltip-mode 1)
(require 'use-package)
(setq use-package-always-ensure t)
;;(org-babel-load-file (expand-file-name "~/.emacs.d/tidy-init.org"))
(set-background-color "black")
(add-to-list 'load-path "~/.emacs.d/external-pkgs/emacs-libvterm")
(let (vterm-install)
(require 'vterm))
(add-hook 'vterm-mode-hook (lambda ()
(setq-local global-hl-line-mode nil)
(setq-local truncate-lines t)))
(defun vterm--rename-buffer-as-title (title)
(let ((dir (string-trim-left (concat (nth 1 (split-string title ":")) "/"))))
(cd-absolute dir)
(rename-buffer (format "term %s" title) t)))
(add-hook 'vterm-set-title-functions #'vterm--rename-buffer-as-title)
(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
)
@Bee-Mar Just to be clear (because I didn't realize this at first) there are two sides to this hack: Emacs and the shell. You need to configure your shell to set the xterm window title with information that Emacs (via this hack) uses to figure out what the current directory is.
How you set the xterm title depends on your shell. For example, in Bash, you might set your PS1 prompt to
PS1='\[\033]0;\u@\h:\w\007\]\$ '
This would send the non-printing escape sequences necessary to set the xterm
title, in the format that this hack expects: <user>@<host>:path
.
For Bash, the non-printing escape sequence is \[\033]0;\u@\h:\w\007\]
.
For the specifics on how to set the xterm title, see:
@jhacksworth aaaaaaaahhhhhhhh this makes a whole lot more sense. I should have caught what the function was reading to parse the string, and produce the correct path. Well, this clears things up, and I was able to get it to work. At least this explanation is here now for others who may have had a similar question. Thanks!
Also, I just now realized, I had removed that non-printing escape sequence when I first started using multi-term because multi-term was printing that information to the screen, which was annoying. So, that also added another layer to my confusion lol.
Interestingly, I found an edge case for this function. When I ssh'd into one of my servers, I found that vterm froze up, and then I quickly realized it's because emacs was attempting to change into a directory that didn't exist on my local machine. I added in small check to ensure the host names match before this happens.
(setq user-host-name
(concat "@"
(substring
(shell-command-to-string "hostname")
0 -1)
":"))
;; user-host-name = "@[hostname]:"
(defun vterm--rename-buffer-as-title (title)
(if (string-match-p (regexp-quote user-host-name) title)
(let ((dir (string-trim-left (concat (nth 1 (split-string title ":")) "/"))))
(cd-absolute dir)
(rename-buffer (format "term %s" title) t))))
@Bee-Mar Thanks for reporting this.
I think a check to make sure the directory exists would make sense, even for people who aren't logging in to remote hosts. For example, with file-directory-p
:
(defun vterm--rename-buffer-as-title (title)
(let ((dir (string-trim-left (concat (nth 1 (split-string title ":")) "/"))))
(if (file-directory-p dir)
(cd-absolute dir))
(rename-buffer (format "term %s" title) t)))
@jhacksworth that's definitely a cleaner way of handling it
share my snippet
even in a ssh session, default-directory
is still updated when you cd to some directory ,
so you can call dired-jump
or find-file
directly.
the xterm title need in the format:user@host:path
or user@host@lastcmd:path
(defvar vterm-user "")
(make-variable-buffer-local 'vterm-user)
(defvar vterm-host "")
(make-variable-buffer-local 'vterm-host)
(defvar vterm-pwd "")
(make-variable-buffer-local 'vterm-pwd)
(defvar vterm-cmd "")
(make-variable-buffer-local 'vterm-cmd)
;; for Bash:
;;PS1='\[\033]0;\u@\H:\w\007\]\$ '
;; for zsh
;; # for emacs vterm.el
;; HOSTNAME=$(uname -n)
;; USER=$(whoami)
;; case $TERM in
;; (*xterm*|*rxvt*|(dt|k)term*))
;; # http://zsh.sourceforge.net/Doc/Release/Functions.html
;; # 刚打开shell时,也执行一次更新title
;; lastcmd=""
;; print -Pn "\e]2;${USER}@${HOSTNAME}@${lastcmd}:%~\a" #set title user@host@cmd:path
;; preexec () {
;; cmd="$1"
;; tokens=(${(s/ /)cmd}) # split by space
;; lastcmd=$tokens[1]
;; # # 标题栏、任务栏样式
;; # 在执行命令前执行,所以此时打印的pwd可能不准,故还需要在chpwd里,刚更新一次
;; print -Pn "\e]2;${USER}@${HOSTNAME}@${lastcmd}:%~\a" #set title user@host@cmd:path
;; }
;; chpwd() {
;; # ESC]0;stringBEL — Set icon name and window title to string
;; # ESC]1;stringBEL — Set icon name to string
;; # ESC]2;stringBEL — Set window title to string
;; print -Pn "\e]2;${USER}@${HOSTNAME}@${lastcmd}:%~\a" #set title user@host@cmd:path chpwd里取不到当前cmd
;; }
;; esac
(defun vterm-set-title-hook (title) ;title = user@host@lastcmd:path or user@host:path
(let* ((tokens (split-string title ":" ))
dir)
(when (equal 2 (length tokens))
(setq vterm-pwd (string-trim-left (nth 1 tokens)))
(setq tokens (split-string (nth 0 tokens) "@" ))
(when (> (length tokens) 1)
(setq vterm-user (nth 0 tokens))
(setq vterm-host (nth 1 tokens))
(when (and (nth 2 tokens)
(not (string-empty-p (or (nth 2 tokens) ""))))
(setq vterm-cmd (nth 2 tokens))))
(setq dir
(file-name-as-directory
(if (and (string= vterm-host (system-name))
(string= vterm-user (user-real-login-name)))
(expand-file-name vterm-pwd)
(concat "/-:" vterm-user "@" vterm-host ":"
vterm-pwd))))
(when (ignore-errors (file-directory-p dir))
(cd-absolute dir))
;; (message "pwd=%s,user=%s,host=%s,cmd=%s d=%s"
;; vterm-pwd vterm-user vterm-host vterm-cmd dir)
(rename-buffer (format "term %s %s@%s:%s" vterm-cmd vterm-user vterm-host vterm-pwd ) t)
)))
(add-hook 'vterm-set-title-functions 'vterm-set-title-hook)
@jixiuf does this code only work for zsh? Also, that functionality is awesome.
@Bee-Mar
not just for zsh
you just need set the xterm title, in the format : user@host:path
or user@host@lastcmd:path
in Bash, you might set your PS1 prompt to
PS1='\[\033]0;\u@\H:\w\007\]\$ '
https://github.com/akermu/emacs-libvterm/issues/55#issuecomment-471334398 mentioned
@Bee-Mar not just for zsh you just need set the xterm title, in the format :
user@host:path
oruser@host@lastcmd:path
in Bash, you might set your PS1 prompt to
PS1='\[\033]0;\u@\h:\w\007\]\$ '
#55 (comment) mentioned
My PS1 has been set correctly, but there's some other issue. When I one of my servers, the title doesn't update at all, and when I ssh into another, the tramp connection fails. I have everything setup using ssh keys, and when I remove the code you suggested everything works fine again.
you can add some debug code and see what's going on like
(message "title=%s\n pwd=%s\n user=%s\nhost=%s\ncmd=%s\n dir=%s\n"
title vterm-pwd vterm-user vterm-host vterm-cmd dir)
for me it looks like this:
title=root@BJ-DEV-GO: ~
pwd=~
user=root
host=BJ-DEV-GO
cmd=cd
dir=/-:root@BJ-DEV-GO:~/
then you can try to open /-:root@BJ-DEV-GO:~/
with find-file
and make sure you can ping the host
maybe you need add a line in your /etc/hosts
and I changed
(when (file-directory-p dir)
to
(when (ignore-errors (file-directory-p dir))
in case the remote host is not connectable
@jixiuf Thank you for sharing your snippet! This is awesome :)
@Bee-Mar I'm not sure what's causing your issues, but I can share some settings that helped me.
First, in my Bash shell config, I had to change the non-printing escape sequence from
\[\033]0;\u@\h:\w\007\]
to
\[\033]0;\u@\H:\w\007\]
Note the change to uppercase \H
. From the PROMPTING
section of the Bash manpage:
\h the hostname up to the first `.'
\H the hostname
This was due to the hostname part in the xterm title set by Bash not matching with the Emacs (system-name)
.
Next, I had some issues with Tramp that were fixed by the following settings:
(setq tramp-default-method "ssh")
(setq tramp-shell-prompt-pattern "\\(?:^\\|\r\\)[^]#$%>\n]*#?[]#$%>].* *\\(^[\\[[0-9;]*[a-zA-Z] *\\)*")
I had some non-standard prompts that were fixed by editing tramp-shell-prompt-pattern
.
I hope this helps.
@jhacksworth Thanks! The tramp-shell-prompt-pattern
did the trick. Did you come up with that regex? Cuz that's definitely a non-obvious pattern lol
@Bee-Mar Glad to hear it worked! I definitely did not come up with the regex :)
I got it from the EmacsWiki -Tramp Mode page, in the section titled Tramp hangs # 6: Not recognising the remote shell prompt
@jhacksworth aahhh, well, they certainly had a good bit of information lol
Since vterm-set-title-functions
is obsolete:
default-directory
to be the current vterm dir(defun sn/vterm--set-title-pre (title)
(let ((dir (string-trim-left (concat (nth 1 (split-string title ":")) "/"))))
(when (file-directory-p dir)
(cd-absolute dir))))
(advice-add 'vterm--set-title :before #'sn/vterm--set-title-pre)
;; or for Doom users
(defadvice! sn/vterm--set-title-pre (title)
:before #'vterm--set-title
(let ((dir (string-trim-left (concat (nth 1 (split-string title ":")) "/"))))
(when (file-directory-p dir)
(cd-absolute dir))))
Set vterm-buffer-name-string
with one format control string (as required).
The replaced value is the returned title from your terminal session.
;; (setq vterm-buffer-name "*vterm*") ;; you can use this custom property for convenience
(setq vterm-buffer-name-string "%s")
Once that's configured then
(defun sn/vterm--set-title-pre (fn title)
(let ((dir (string-trim-left (concat (nth 1 (split-string title ":")) "/"))))
(when (file-directory-p dir)
(cd-absolute dir))
(message (format "%s %s" vterm-buffer-name title))
(funcall fn (format "%s %s" vterm-buffer-name title))))
(advice-add 'vterm--set-title :around #'sn/vterm--set-title-pre)
;; or for Doom users
(defadvice! sn/vterm--set-title-pre (fn title)
:around #'vterm--set-title
(let ((dir (string-trim-left (concat (nth 1 (split-string title ":")) "/"))))
(when (file-directory-p dir)
(cd-absolute dir))
(funcall fn (format "%s %s" vterm-buffer-name title))))
EDIT: Just realised there's this: https://github.com/akermu/emacs-libvterm#directory-tracking-and-prompt-tracking 🤦 … anyway, this might still be an alternative
Is it possible to update
default-directory
after cd so I can easily find files? At least title updated somehow...