Alexander-Miller / treemacs

GNU General Public License v3.0
2.07k stars 151 forks source link

Open org-mode heading from treemacs broken (Something went wrong when finding tag) #998

Open darim1 opened 1 year ago

darim1 commented 1 year ago

I encounter problems, when trying to open a heading from treemacs. Wehen double-clicking, it would report:

[Treemacs] Something went wrong when finding tag 'xxxx': (wrong-type-argument integer-or-marker-p nil)

Moreover it would close the buffer.

My setup:

Message window is not really very talkative. What can I do to track this down or fix?

Alexander-Miller commented 1 year ago

Eval this code.

(defun treemacs--imenu-tag-noselect (file tag-path)
  (let ((tag (-last-item tag-path))
        (path (-butlast tag-path)))
    (progn
      (find-file-noselect file)
      (let ((index (treemacs--get-imenu-index file)))
        (dolist (path-item path)
          (setq index (cdr (assoc path-item index))))
        (-let [(buf . pos)
               (treemacs--extract-position (cdr (--first (equal (car it) tag) index)) path)]
          ;; some imenu implementations, like markdown, will only provide
          ;; a raw buffer position (an int) to move to
          (list (or buf (get-file-buffer file)) pos))))))

It's the function where the error happens, without the error cordon. That way you'll see a full stack trace when you enable debug on error.

Based on a quick test tag navigation does work on my end, so other than the stack trace it'd be useful if I had an example org file where the error happens for you.

Finally I also need to know your org-version.

darim1 commented 1 year ago

I use org-version 9.6

I don't receive a stack-trace with debug-on-error, loading the function provided above. This the message window output:

treemacs--imenu-tag-noselect
Debug on Error enabled globally
c:/dummy.org and s:/Projekt/dummy.org are the same file
[Treemacs] Something went wrong when finding tag 'Head 1': (wrong-type-argument integer-or-marker-p nil)
treemacs--imenu-tag-noselect
c:/dummy.org and s:/Projekt/dummy.org are the same file
[Treemacs] Something went wrong when finding tag 'Head 1': (wrong-type-argument integer-or-marker-p nil)

Please find attached the very simple example file:

dummy.zip

Thanks!

Alexander-Miller commented 1 year ago

I accidentally gave you the mouse interface version of the tag code. Here's the correct defun:

(defun treemacs--call-imenu-and-goto-tag (tag-path &optional org?)
  (let* ((file (car tag-path))
         (path (-butlast (cdr tag-path)))
         (tag (-last-item tag-path)))
    (progn
      (find-file-noselect file)
      (let ((index (treemacs--get-imenu-index file)))
        (dolist (path-item path)
          (setq index (cdr (assoc path-item index))))
        (-let [(buf . pos) (treemacs--extract-position
                            (-let [item (--first
                                         (equal (car it) tag)
                                         index)]
                              (if org? item (cdr item)))
                            (car tag-path))]
          ;; some imenu implementations, like markdown, will only provide
          ;; a raw buffer position (an int) to move to
          (switch-to-buffer (or buf (get-file-buffer file)))
          (if (functionp pos)
              (funcall pos)
            (goto-char pos))
          ;; a little bit of convenience - reveal those nested headlines
          (when (and (eq major-mode 'org-mode)
                     (fboundp 'org-reveal))
            (org-reveal)))))))

I've upgraded to org 9.6, but couldn't reproduce the error with that either, so we have to debug on your side. Other than the stack trace what does calling this on your dummy file output for you?

(treemacs--get-imenu-index "..../Dummy.org")

darim1 commented 1 year ago

Thanks for the update, this time it has worked:

Debugger entered--Lisp error: (wrong-type-argument integer-or-marker-p nil)
  goto-char(nil)
  (if (functionp pos) (funcall pos) (goto-char pos))
  (let* ((--dash-source-47-- (treemacs--extract-position (let* ((item (let ... ... needle))) (if org\? item (cdr item))) (car tag-path))) (buf (car-safe (prog1 --dash-source-47-- (setq --dash-source-47-- (cdr --dash-source-47--))))) (pos --dash-source-47--)) (switch-to-buffer (or buf (get-file-buffer file))) (if (functionp pos) (funcall pos) (goto-char pos)) (if (and (eq major-mode 'org-mode) (fboundp 'org-reveal)) (progn (org-reveal))))
  (let ((index (treemacs--get-imenu-index file))) (let ((--dolist-tail-- path) path-item) (while --dolist-tail-- (setq path-item (car --dolist-tail--)) (setq index (cdr (assoc path-item index))) (setq --dolist-tail-- (cdr --dolist-tail--)))) (let* ((--dash-source-47-- (treemacs--extract-position (let* ((item ...)) (if org\? item (cdr item))) (car tag-path))) (buf (car-safe (prog1 --dash-source-47-- (setq --dash-source-47-- (cdr --dash-source-47--))))) (pos --dash-source-47--)) (switch-to-buffer (or buf (get-file-buffer file))) (if (functionp pos) (funcall pos) (goto-char pos)) (if (and (eq major-mode 'org-mode) (fboundp 'org-reveal)) (progn (org-reveal)))))
  (progn (find-file-noselect file) (let ((index (treemacs--get-imenu-index file))) (let ((--dolist-tail-- path) path-item) (while --dolist-tail-- (setq path-item (car --dolist-tail--)) (setq index (cdr (assoc path-item index))) (setq --dolist-tail-- (cdr --dolist-tail--)))) (let* ((--dash-source-47-- (treemacs--extract-position (let* (...) (if org\? item ...)) (car tag-path))) (buf (car-safe (prog1 --dash-source-47-- (setq --dash-source-47-- ...)))) (pos --dash-source-47--)) (switch-to-buffer (or buf (get-file-buffer file))) (if (functionp pos) (funcall pos) (goto-char pos)) (if (and (eq major-mode 'org-mode) (fboundp 'org-reveal)) (progn (org-reveal))))))
  (let* ((file (car tag-path)) (path (-butlast (cdr tag-path))) (tag (-last-item tag-path))) (progn (find-file-noselect file) (let ((index (treemacs--get-imenu-index file))) (let ((--dolist-tail-- path) path-item) (while --dolist-tail-- (setq path-item (car --dolist-tail--)) (setq index (cdr (assoc path-item index))) (setq --dolist-tail-- (cdr --dolist-tail--)))) (let* ((--dash-source-47-- (treemacs--extract-position (let* ... ...) (car tag-path))) (buf (car-safe (prog1 --dash-source-47-- ...))) (pos --dash-source-47--)) (switch-to-buffer (or buf (get-file-buffer file))) (if (functionp pos) (funcall pos) (goto-char pos)) (if (and (eq major-mode 'org-mode) (fboundp 'org-reveal)) (progn (org-reveal)))))))
  treemacs--call-imenu-and-goto-tag(("c:/mnt/iav/Projekt/KOV2/Fremdvergabe/project.org" #("Planung" 0 7 (org-imenu-marker #<marker in no buffer> org-imenu t)) #("KW17" 0 4 (org-imenu-marker #<marker in no buffer> org-imenu t))))
  treemacs--goto-tag(#<marker (moves after insertion) at 338 in  *Treemacs-Scoped-Buffer-#<frame GNU Emacs at 20IAV502790N-0 0000027eea6ad3c0>*>)
  treemacs-visit-node-in-most-recently-used-window()
  treemacs-doubleclick-action((double-mouse-1 (#<window 10 on  *Treemacs-Scoped-Buffer-#<frame GNU Emacs at 20IAV502790N-0 0000027eea6ad3c0>*> 339 (90 . 649) 7247312 nil 339 (9 . 26) nil (4 . 11) (8 . 16)) 2))
  funcall-interactively(treemacs-doubleclick-action (double-mouse-1 (#<window 10 on  *Treemacs-Scoped-Buffer-#<frame GNU Emacs at 20IAV502790N-0 0000027eea6ad3c0>*> 339 (90 . 649) 7247312 nil 339 (9 . 26) nil (4 . 11) (8 . 16)) 2))
  command-execute(treemacs-doubleclick-action)

Please also find the requested output for the imenu index:

((#("Head 1" 0 6 (org-imenu t org-imenu-marker #<marker in no buffer>)) . #<marker in no buffer>) 
(#("Head 2" 0 6 (org-imenu t org-imenu-marker #<marker in no buffer>)) . #<marker in no buffer>))

Thanks!

Alexander-Miller commented 1 year ago

Still nothing suspicious. Let's produce some debug output next. Eval this and try again:

(defun treemacs--call-imenu-and-goto-tag (tag-path &optional org?)
  "Call the imenu index of the tag at TAG-PATH and go to its position.
ORG? should be t when this function is called for an org buffer and index since
org requires a slightly different position extraction because the position of a
headline with sub-elements is saved in an `org-imenu-marker' text property."
  (treemacs-log "Goto-Tag Path %s Org? %s" tag-path org?)
  (let* ((file (car tag-path))
         (path (-butlast (cdr tag-path)))
         (tag (-last-item tag-path)))
    (treemacs-log "File %s" file)
    (treemacs-log "Path %s" path)
    (treemacs-log "Tag %s" tag)
    (condition-case e
        (progn
          (find-file-noselect file)
          (let ((index (treemacs--get-imenu-index file)))
            (treemacs-log "Index %s" index)
            (dolist (path-item path)
              (setq index (cdr (assoc path-item index))))
            (treemacs-log "Postprocess Index %s" index)
            (-let [(buf . pos) (treemacs--extract-position
                                (-let [item (--first
                                             (equal (car it) tag)
                                             index)]
                                  (if org? item (cdr item)))
                                (car tag-path))]
              (treemacs-log "Extracted Position %s %s" buf pos)
              ;; some imenu implementations, like markdown, will only provide
              ;; a raw buffer position (an int) to move to
              (switch-to-buffer (or buf (get-file-buffer file)))
              (treemacs-log "Switched to %s" (current-buffer))
              (if (functionp pos)
                  (funcall pos)
                (goto-char pos))
              ;; a little bit of convenience - reveal those nested headlines
              (when (and (eq major-mode 'org-mode)
                         (fboundp 'org-reveal))
                (org-reveal)))))
      (error
       (treemacs-log-err "Something went wrong when finding tag '%s': %s"
         (propertize tag 'face 'treemacs-tags-face)
         e)))))

The output should look like this:

[Treemacs] Goto-Tag Path (/home/am/Downloads/dummy.org Head 2) Org? nil
[Treemacs] File /home/am/Downloads/dummy.org
[Treemacs] Path nil
[Treemacs] Tag Head 2
[Treemacs] Index ((Head 1 . #<marker at 1 in dummy.org>) (Head 2 . #<marker at 10 in dummy.org>))
[Treemacs] Postprocess Index ((Head 1 . #<marker at 1 in dummy.org>) (Head 2 . #<marker at 10 in dummy.org>))
[Treemacs] Extracted Position dummy.org 10
[Treemacs] Switched to dummy.org
darim1 commented 1 year ago
[Treemacs] Goto-Tag Path (.../dummy.org Head 1) Org? nil
[Treemacs] File .../dummy.org
[Treemacs] Path nil
[Treemacs] Tag Head 1
.../dummy.org and mnt/dummy.org are the same file
[Treemacs] Index ((Head 1 . #<marker in no buffer>) (Head 2 . #<marker in no buffer>))
[Treemacs] Postprocess Index ((Head 1 . #<marker in no buffer>) (Head 2 . #<marker in no buffer>))
[Treemacs] Extracted Position nil nil
[Treemacs] Switched to project.org
[Treemacs] Something went wrong when finding tag 'Head 1': (wrong-type-argument integer-or-marker-p nil)

Apparently emacs claims that the same file exists on two locations. This is intentionally by using symbolic link. I checked with a file not having symbolic link and here treemacs perfectly works. I am sure that this setup has worked before, but this could have been also with older emacs version (i.e. 27). As workaround I can simply re-configure my treemacs workspaces by avoiding the symbolic link. Anyway, I know that this is off-topic from treemacs perspective now, but do you have an idea why this happens?

Thanks!

Alexander-Miller commented 1 year ago

Are both the original file and the symlink together in the same workspace? If yes then that could be the cause of the problem. Long story short treemacs treats a file's absolute path as basically a unique primary key, and getting rid of that restriction would be a Long and Unpleasant undertaking. A symlink is not quite the same thing, but I think it's close enough to potentially cause some confusion.

I am not sure however, and couldn't reproduce the problem with symlinks of my own, so let's take a look why we can't extract that headline position. Try this:

(defun treemacs--extract-position (item file)
  (declare (side-effect-free t))
  (treemacs-log "Extract Item %s %s File %s" (type-of item) item file)
  (pcase (type-of item)
    ('marker
     (cons (marker-buffer item) (marker-position item)))
    ('overlay
     (cons (overlay-buffer item) (overlay-start item)))
    ('integer
     (cons nil item))
    ('cons
     (cond
      ((eq 'pdf-outline-imenu-activate-link (cadr item))
       (with-no-warnings
         (cons (find-buffer-visiting file) (lambda () (apply #'pdf-outline-imenu-activate-link item)))))
      ((get-text-property 0 'org-imenu-marker (car item))
       (-let [org-marker (get-text-property 0 'org-imenu-marker (car item))]
         (cons (marker-buffer org-marker) (marker-position org-marker))))))))
darim1 commented 1 year ago
treemacs--extract-position
[Treemacs] Extract Item marker #<marker in no buffer> File /mnt/dummy.org
/mnt/dummy.org and /dummy.org are the same file
[Treemacs] Extract Item marker #<marker in no buffer> File /mnt/dummy.org
[Treemacs] Something went wrong when finding tag 'Head 1': (wrong-type-argument integer-or-marker-p nil)

I configured my workspace by using the sym-linked location. There is no overlap with actual file location from workspace perspective.

Thanks!

Alexander-Miller commented 1 year ago

Ok, I think I know when the problem happens - you need to have the real file already opened, and then to try and get the tag in the linked file. Try this version, it'll resolve the link before looking for tags and looked like it fixed the issue on my end:

(defun treemacs--call-imenu-and-goto-tag (tag-path &optional org?)
  (let* ((file (car tag-path))
         (path (-butlast (cdr tag-path)))
         (tag (-last-item tag-path)))
    (condition-case e
        (progn
          (-when-let (link-target (file-symlink-p file))
            (setf file link-target))
          (find-file-noselect file)
          (let ((index (treemacs--get-imenu-index file)))
            (dolist (path-item path)
              (setq index (cdr (assoc path-item index))))
            (-let [(buf . pos) (treemacs--extract-position
                                (-let [item (--first
                                             (equal (car it) tag)
                                             index)]
                                  (if org? item (cdr item)))
                                (car tag-path))]
              ;; some imenu implementations, like markdown, will only provide
              ;; a raw buffer position (an int) to move to
              (switch-to-buffer (or buf (get-file-buffer file)))
              (if (functionp pos)
                  (funcall pos)
                (goto-char pos))
              ;; a little bit of convenience - reveal those nested headlines
              (when (and (eq major-mode 'org-mode)
                         (fboundp 'org-reveal))
                (org-reveal)))))
      (error
       (treemacs-log-err "Something went wrong when finding tag '%s': %s"
         (propertize tag 'face 'treemacs-tags-face)
         e)))))
darim1 commented 1 year ago

Thanks, but I think that did't work:

treemacs--call-imenu-and-goto-tag
.../dummy.org and /mnt/dummy.org are the same file
[Treemacs] Something went wrong when finding tag 'Head 1': (wrong-type-argument integer-or-marker-p nil)
stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity.

Alexander-Miller commented 1 year ago

I've pushed a potential fix and can no longer reproduce the problem. What I tried is:

  1. Open the original file outside of treemacs with a simple find-file
  2. Have the linked file, and only the linked file, as part of a treemacs project
  3. Look up the tags of the symlink in treemacs

If you still have problems I'll need recipe like this to know exactly what to try.

darim1 commented 1 year ago

One additional remark: Since I am on windows I tried directory sym-links vs junctions. Apparently the latter performs better, i.e. I suspect the symbolic link (created with mklink /D <target> <source> vs. mklink /J <target> <source>.

stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity.