Alexander-Miller / treemacs

GNU General Public License v3.0
2.09k stars 152 forks source link

Support for tramp? #205

Closed blezek closed 4 years ago

blezek commented 6 years ago

Is it possible to add a tree for a tramp location, i.e /remote-server:path/to/directory

Didn't work for me, but may have done something incorrectly. Got [no match] when trying to use a tramp path.

Alexander-Miller commented 6 years ago

I don't use tramp so I cannot say what's missing. Can you try again with debug-on-error enabled? I need to know where that no match is coming from.

blezek commented 6 years ago

@Alexander-Miller thanks for the quick response. I did figure this out --

/scp:remote-server:path/to/directory

works great! (if a little slow...)

Alexander-Miller commented 6 years ago

Can you explain a bit what you got wrong first and what you had to change? If tramp is working out of the box I'd like to add a short explanation to the feature list.

blezek commented 6 years ago

Sure, I'd been using ido-mode which understands /remote-server:path/to/directory, but that didn't work in treemacs. When I used the standard tramp version, e.g. /scp:remote-server:path/to/directory, treemacs understood what I wanted.

treemacs works well across Tramp, though the scanning of directories and files takes some time over my slow-ish link. I also got an error about a timeout, but that's fairly common using tramp.

shelper commented 5 years ago

did the same thing but seems i got error saying no file directory found

Error running timer ‘treemacs--follow’: (file-missing "Setting current directory" "No such file or directory" "/scp:host:/home/username/workspace/") apply: Setting current directory: No such file or directory, /scp:host:/home/username/workspace [2 times]

did i do it wrongly? what i did is c-p a then add/scp:host:/home/username/workspace i am able to tramp to the folder by c-x c-f tho

Alexander-Miller commented 5 years ago

toggle-debug-on-error and try again, I'll need a stack trace for this.

shelper commented 5 years ago

well, i found actually some folder can be treemacsed, but some not... have no idea why is that. is there a way to reset the context of treemacs? wonder if that is because i wrongly used ssh for some folders and treemacs remembers that wrong configuration

anyway here is the error i have when i am able to add the folder and tab open it as an project to emacs

Debugger entered--Lisp error: (wrong-type-argument listp Traceback)
  treemacs--collapse-dirs(Traceback)
  treemacs--expand-dir-node(#<marker (moves after insertion) at 15 in  *Treemacs-Framebuffer-1*> :recursive nil)
  treemacs-toggle-node(nil)
  treemacs-TAB-action(nil)
  funcall-interactively(treemacs-TAB-action nil)
  call-interactively(treemacs-TAB-action nil nil)
  command-execute(treemacs-TAB-action)

and here is the error where i am not able to tab open a folder added to treemacs, i got an error message saying apply: Setting current directory: No such file or directory, /ssh:host_ip:/home/zhijia.yuan/workspace/av

Debugger entered--Lisp error: (file-missing "Setting current directory" "No such file or directory" "/ssh:host_ip:/home/zhijia.yuan/workspace/av")
  make-process(:name "Process Future" :buffer nil :command ("/usr/bin/python" "-O" "-S" "/Users/zhijia.yuan/.emacs.d/elpa/26.1/develop/treemacs-20190117.633/treemacs-git-status.py" "/ssh:host_ip:/home/zhijia.yuan/workspace/av/" "5000" ""))
  apply(make-process (:name "Process Future" :buffer nil :command ("/usr/bin/python" "-O" "-S" "/Users/zhijia.yuan/.emacs.d/elpa/26.1/develop/treemacs-20190117.633/treemacs-git-status.py" "/ssh:host_ip:/home/zhijia.yuan/workspace/av/" "5000" "")))
  start-process("Process Future" nil "/usr/bin/python" "-O" "-S" "/Users/zhijia.yuan/.emacs.d/elpa/26.1/develop/treemacs-20190117.633/treemacs-git-status.py" "/ssh:host_ip:/home/zhijia.yuan/workspace/av/" "5000" "")
  apply(start-process "Process Future" nil "/usr/bin/python" ("-O" "-S" "/Users/zhijia.yuan/.emacs.d/elpa/26.1/develop/treemacs-20190117.633/treemacs-git-status.py" "/ssh:host_ip:/home/zhijia.yuan/workspace/av/" "5000" ""))
  pfuture-new("/usr/bin/python" "-O" "-S" "/Users/zhijia.yuan/.emacs.d/elpa/26.1/develop/treemacs-20190117.633/treemacs-git-status.py" "/ssh:host_ip:/home/zhijia.yuan/workspace/av/" "5000" "")
  treemacs--git-status-process-function("/ssh:host_ip:/home/zhijia.yuan/workspace/av")
  treemacs--expand-dir-node(#<marker (moves after insertion) at 601 in  *Treemacs-Framebuffer-1*> :recursive nil)
  treemacs-toggle-node(nil)
  treemacs-TAB-action(nil)
  funcall-interactively(treemacs-TAB-action nil)
  call-interactively(treemacs-TAB-action nil nil)
  command-execute(treemacs-TAB-action)
Alexander-Miller commented 5 years ago

anyway here is the error i have when i am able to add the folder and tab open it as an project to emacs

Should be taken care of with https://github.com/Alexander-Miller/treemacs/commit/25d78c08c162a4c5899fdc92f7752e207a11b66c. The visible part that is. Internally the python process looking for directories to flatten breaks, and we need to look into what's happening on the inside. Maybe python cannot deal with tramp, maybe it's something else. What is the output of the following code?

(shell-command-to-string
 (format "%s -O -S %s %s 3 t"
         treemacs-python-executable
         treemacs--dirs-to-collpase.py
         "/your/path"))

and here is the error where i am not able to tab open a folder added to treemacs,

That's an error inside make-process, emacs thinks the directory does not exist. What does (file-exists-p "/ssh:host_ip:/home/zhijia.yuan/workspace/av") say?

is there a way to reset the context of treemacs?

You can edit your projects with treemacs-edit-workspaces. Deleting treemacs-persist-file on your hard drive and restarting emacs works too.

shelper commented 5 years ago

error for

(shell-command-to-string
 (format "%s -O -S %s %s 3 t"
         treemacs-python-executable
         treemacs--dirs-to-collpase.py
         "/your/path"))

elisp--eval-last-sexp(nil)

f(compiled-function (eval-last-sexp-arg-internal) "Evaluate sexp before point; print value in the echo area.\nInteractively, with a non -' prefix argument, print output into\ncurrent buffer.\n\nNormally, this function truncates long output according to the\nvalue of the variableseval-expression-print-length' and\neval-expression-print-level'. With a prefix argument of zero,\nhowever, there is no such truncation. Such a prefix argument\nalso causes integers to be printed in several additional formats\n(octal, hexadecimal, and character when the prefix argument is\n-1 or the integer iseval-expression-print-maximum-character' or\nless).\n\nIf `eval-expression-debug-on-error' is non-nil, which is the default,\nthis command arranges for all errors to enter the debugger." (interactive "P") #<bytecode 0x400d427b>)(nil)

f(compiled-function (&rest _it) #<bytecode 0x453652c9>)()

eval-sexp-fu-flash-doit-simple(#f(compiled-function (&rest _it) #<bytecode 0x453652c9>) #f(compiled-function (&rest args2) #<bytecode 0x44cfcac5>) #f(compiled-function (&rest args2) #<bytecode 0x44cfcae5>)) eval-sexp-fu-flash-doit(#f(compiled-function (&rest _it) #<bytecode 0x453652c9>) #f(compiled-function (&rest args2) #<bytecode 0x44cfcac5>) #f(compiled-function (&rest args2) #<bytecode 0x44cfcae5>)) esf-flash-doit(#f(compiled-function (&rest _it) #<bytecode 0x453652c9>) #f(compiled-function (&rest args2) #<bytecode 0x44cfcac5>) #f(compiled-function (&rest args2) #<bytecode 0x44cfcae5>) #f(compiled-function (&rest args2) #<bytecode 0x44cfcb05>)) ad-Advice-eval-last-sexp(#f(compiled-function (eval-last-sexp-arg-internal) "Evaluate sexp before point; print value in the echo area.\nInteractively, with a non -' prefix argument, print output into\ncurrent buffer.\n\nNormally, this function truncates long output according to the\nvalue of the variableseval-expression-print-length' and\neval-expression-print-level'. With a prefix argument of zero,\nhowever, there is no such truncation. Such a prefix argument\nalso causes integers to be printed in several additional formats\n(octal, hexadecimal, and character when the prefix argument is\n-1 or the integer iseval-expression-print-maximum-character' or\nless).\n\nIf eval-expression-debug-on-error' is non-nil, which is the default,\nthis command arranges for all errors to enter the debugger." (interactive "P") #<bytecode 0x400d427b>) nil) apply(ad-Advice-eval-last-sexp #f(compiled-function (eval-last-sexp-arg-internal) "Evaluate sexp before point; print value in the echo area.\nInteractively, with a non-' prefix argument, print output into\ncurrent buffer.\n\nNormally, this function truncates long output according to the\nvalue of the variables eval-expression-print-length' and\neval-expression-print-level'. With a prefix argument of zero,\nhowever, there is no such truncation. Such a prefix argument\nalso causes integers to be printed in several additional formats\n(octal, hexadecimal, and character when the prefix argument is\n-1 or the integer is eval-expression-print-maximum-character' or\nless).\n\nIfeval-expression-debug-on-error' is non-nil, which is the default,\nthis command arranges for all errors to enter the debugger." (interactive "P") #<bytecode 0x400d427b>) nil) eval-last-sexp(nil) lisp-state-eval-sexp-end-of-line() funcall-interactively(lisp-state-eval-sexp-end-of-line) call-interactively(lisp-state-eval-sexp-end-of-line nil nil) command-execute(lisp-state-eval-sexp-end-of-line)

(file-exists-p "/ssh:host_ip:/home/zhijia.yuan/workspace/av") say 't'

shelper commented 5 years ago

i am using mac and emacs 26.1 btw. i have python 3.7 installed but the system default is python2.7

Alexander-Miller commented 5 years ago

elisp--eval-last-sexp(nil)

I have no idea what's going on in there, but this is wrong. 1) That code is not supposed to cause an error in elisp. 2) That stack trace does not even look complete, all it shows is a bunch of eval-last-sexp functions.

(file-exists-p "/ssh:host_ip:/home/zhijia.yuan/workspace/av") say 't'

That error is thrown in the C core, so there is not much I can do here. I've only one final idea - evaluate the following code and see if it improves things:

(defun pfuture-new (cmd &rest cmd-args)
  (let* ((process-connection-type nil)
         (process (apply #'start-file-process "Process Future" nil cmd cmd-args)))
    (process-put process 'result "")
    (set-process-filter process #'pfuture--append-output)
    process))
jwatson0 commented 5 years ago

I'm having the same problem with opening remote files via tramp and ssh. Running:

(shell-command-to-string
 (format "%s -O -S %s %s 3 t"
         treemacs-python-executable
         treemacs--dirs-to-collpase.py
         "/ssh:myremotehostname:/home/myname/a/b/c/d/file.txt"))

Gives:

"Traceback (most recent call last):
  File \"/Users/myname/.emacs.d/elpa/treemacs-20190212.1922/treemacs-dirs-to-collapse.py\", line 48, in <module>
    main()
  File \"/Users/myname/.emacs.d/elpa/treemacs-20190212.1922/treemacs-dirs-to-collapse.py\", line 24, in main
    dirs = [d for d in dir_content(ROOT) if isdir(d)]
  File \"/Users/myname/.emacs.d/elpa/treemacs-20190212.1922/treemacs-dirs-to-collapse.py\", line 16, in dir_content
    for item in listdir(path):
OSError: [Errno 2] No such file or directory: '/ssh:myremotehostname:/home/myname/a/b/c/d/file.txt'
"

Evaluating the pfuture code and re-running gives the same error. Is that helpful?

Alexander-Miller commented 5 years ago

No such file or directory

It looks like python cannot deal with ssh. So unless someone can tell me how to call os.listir on a remote directory I'll have to disable this feature for any path starting with "/ssh:".

(You did use the code slightly wrong, the script accepts a directory not a file, but in that case the error should've been [Errno 20] Not a directory:, so I am fairly sure this is a remote dir issue)

Compro-Prasad commented 5 years ago

@Alexander-Miller How about converting the script from python to Emacs Lisp as Emacs knows how to list it?

I also wanted to ask the reason for choosing python instead of Emacs Lisp. Is there something missing in Elisp?

Alexander-Miller commented 5 years ago

It is all about performance. To find what I can flatten I need to recursively search the file system. That won't be fast in Elisp, especially on a potentially slow tramp connection. And such delays will become all the more annoying when they're caused by filewatch mode.

Python solves that nicely. While I doubt that it is much faster than Elisp (I've never checked), it does allow me do do all that work in parallel, and the only cost I really pay is feeding the output of the python process straight into the elisp interpreter.

And no, I haven't tried async.el - I think I didn't even know about it back when I first went for python. I should try it some time.

Compro-Prasad commented 5 years ago

I think you can disable some features while on a tramp connection. Something related to find-file-literally which is basically a light version of find-file.

Alexander-Miller commented 5 years ago

I'm not opening single files, but looking at the content of directories, so I need to call directory-files.

Compro-Prasad commented 5 years ago

Does tramp connection even work?

I did SPC p t from a remote directory and Treemacs successfully added the project but I am unable to expand the tree for that specific project. I got the following backtrace when expanding a remote project added to treemacs window:

Debugger entered--Lisp error: (file-missing "Setting current directory" "No such file or directory" "/ssh:user@xyz.com:/path/to/some/dir...")
  make-process(:name "Process Future" :buffer nil :command ("/usr/bin/python" "-O" "-S" "/home/compro/.emacs.d/elpa/27.0/develop/treemacs-2..." "/ssh:user@xyz.com:/path/to/some/dir..." "5000" ""))
  apply(make-process (:name "Process Future" :buffer nil :command ("/usr/bin/python" "-O" "-S" "/home/compro/.emacs.d/elpa/27.0/develop/treemacs-2..." "/ssh:user@xyz.com:/path/to/some/dir..." "5000" "")))
  start-process("Process Future" nil "/usr/bin/python" "-O" "-S" "/home/compro/.emacs.d/elpa/27.0/develop/treemacs-2..." "/ssh:user@xyz.com:/path/to/some/dir..." "5000" "")
  apply(start-process "Process Future" nil "/usr/bin/python" ("-O" "-S" "/home/compro/.emacs.d/elpa/27.0/develop/treemacs-2..." "/ssh:user@xyz.com:/path/to/some/dir..." "5000" ""))
  pfuture-new("/usr/bin/python" "-O" "-S" "/home/compro/.emacs.d/elpa/27.0/develop/treemacs-2..." "/ssh:user@xyz.com:/path/to/some/dir..." "5000" "")
  treemacs--git-status-process-function("/ssh:user@xyz.com:/path/to/some/dir...")
  treemacs--expand-root-node(#<marker (moves after insertion) at 3 in  *Treemacs-Framebuffer-1*>)
  treemacs-toggle-node()
  #f(compiled-function (event) "Run the appropriate doubeclick action for the current node.\nIn the default configuration this means to do the same as `treemacs-RET-action'.\n\nThis function's exact configuration is stored in\n`treemacs-doubleclick-actions-config'.\n\nMust be bound to a mouse click, or EVENT will not be supplied." (interactive "e") #<bytecode 0x155a236077e9>)((double-mouse-1 (#<window 28 on  *Treemacs-Framebuffer-1*> 7 (69 . 23) 78524881 nil 7 (6 . 0) nil (5 . 23) (9 . 19)) 2))
  apply(#f(compiled-function (event) "Run the appropriate doubeclick action for the current node.\nIn the default configuration this means to do the same as `treemacs-RET-action'.\n\nThis function's exact configuration is stored in\n`treemacs-doubleclick-actions-config'.\n\nMust be bound to a mouse click, or EVENT will not be supplied." (interactive "e") #<bytecode 0x155a236077e9>) (double-mouse-1 (#<window 28 on  *Treemacs-Framebuffer-1*> 7 (69 . 23) 78524881 nil 7 (6 . 0) nil (5 . 23) (9 . 19)) 2))
  treemacs-doubleclick-action((double-mouse-1 (#<window 28 on  *Treemacs-Framebuffer-1*> 7 (69 . 23) 78524881 nil 7 (6 . 0) nil (5 . 23) (9 . 19)) 2))
  funcall-interactively(treemacs-doubleclick-action (double-mouse-1 (#<window 28 on  *Treemacs-Framebuffer-1*> 7 (69 . 23) 78524881 nil 7 (6 . 0) nil (5 . 23) (9 . 19)) 2))
  call-interactively(treemacs-doubleclick-action nil nil)
  command-execute(treemacs-doubleclick-action)

I first opened the remote directory in dired mode then did SPC p t which added the project to the treemacs window then I double clicked the project to get the above backtrace.

Alexander-Miller commented 5 years ago

Does tramp connection even work?

You see that error because I disabled the flattening feature incorrectly. I check for remote directories from within the python process, but it's the creation of that process that's failing in the first place.

I've built an ugly prototype for async.el, does that work for you remote directory? The output should look like this:

(("/home/a/Documents/git/treemacs/test/testdir1"
  "/testdir2/testdir3"
  "/home/a/Documents/git/treemacs/test/testdir1/testdir2"
  "/home/a/Documents/git/treemacs/test/testdir1/testdir2/testdir3"))
(async-start 
 (lambda () 
   (let ((lexical-binding t))
     (require 'cl-lib)
     (with-temp-buffer
       (cl-labels
           ((dir-content
             (dir)
             (let* ((files (directory-files dir nil nil :no-sort))
                    (result))
               (dolist (file files)
                 (let ((full-path (concat dir "/" file)))
                   (unless (or (string-equal file ".")
                               (string-equal file "..")
                               (not (file-readable-p full-path)))
                     (push full-path result))))
               result)))
         (let* ((directory "/home/a/Documents/git/treemacs")
                (dirs (dir-content directory))
                (ret nil))
           (dolist (dir dirs)
             (when (file-directory-p dir)
               (let ((depth 0)
                     (collapsed dir)
                     (steps nil))
                 (cl-block loop
                   (let ((loop-dir dir))
                     (while t
                       (let ((children (dir-content loop-dir)))
                         (if (and (null (cdr children))
                                  (file-directory-p (car children)))
                             (let ((child (car children)))
                               (cl-incf depth)
                               (setf collapsed (expand-file-name child collapsed))
                               ;; (push (substring child (length dir)) steps)
                               (setf loop-dir child)
                               (push loop-dir steps)
                               (when (>= depth 3)
                                 (cl-return-from loop)))
                           (cl-return-from loop))))))
                 (when (> depth 0)
                   (let* ((steps (nreverse steps))
                          (final-dir (car (last steps))))
                     (push (cons dir (cons (substring final-dir (length dir)) steps)) ret))))))
           ret)))))
 (lambda (ret) (message "RET %s" ret)))
Compro-Prasad commented 5 years ago

Thanks for the code but currently I am a bit involved with some other projects. Ping me again after a month or so, if I have forgot to report back.

Alexander-Miller commented 5 years ago

I don't know what you think the code does, or how it's to be tested, but it's certainly not so involved to be a serious distraction. All you need to do is replace the path to treemacs with a remote directory and run it once and tell me what it outputs.

Compro-Prasad commented 5 years ago
RET ((/ssh:user@xyz.com:/project/path/.github /ISSUE_TEMPLATE /ssh:user@xyz.com:/project/path/.github/ISSUE_TEMPLATE))

Only username, host and absolute path have been changed.

The files in the remote project as shown in dired are:

.
..
backend/
doc/
frontend/
.git/
.gitattributes
.github/
.gitignore
README.md
scripts/
Alexander-Miller commented 5 years ago

/ssh:user@xyz.com:/project/path/.github/ISSUE_TEMPLATE

Well that looks about right, assuming, of course, that ISSUE_TEMPLATE is a directory. Either way the proof of concept works, so I can offer async.el as an alternative backend.

Should do some performance tests as well, for all I know it's faster than python.

Alexander-Miller commented 4 years ago

I ran the tests now, and async.el is about 6 times slower. At that speed it'd probably introduce a palpable delay every time you press TAB. Add in the rarity of the use-case of the non-trivial implementation and maintenance burden it would introduce and I'd rather just stick with python.