Alexander-Miller / treemacs

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

Extension API feedback/questions #331

Closed yyoncho closed 5 years ago

yyoncho commented 5 years ago

Hi, I will use this PR for feedback/question on Extension API. Please let me know whether this is the preffered format for you.

  1. How to define a function to return icon for treemacs-define-expandable-node like this:
(treemacs-define-expandable-node lsp-projects
  :icon-open (treemacs-icon-for-file (first item))
  :icon-closed (treemacs-icon-for-file (first item))
  :query-function (lsp-treemacs--get-files (treemacs-button-get btn :key))
  :render-action
  (treemacs-render-node ...)
  1. How to define multiple top level roots when using treemacs to create standalone control? (the roots should be dynamic)
Alexander-Miller commented 5 years ago

How to define a function to return icon for treemacs-define-expandable-node like this

Ran into the same thing when working on buffers. Not possible now, the plan is to extend the api to pass either an :icon-closed that gets defvard, or a dynamic :icon-closed-form.

How to define multiple top level roots when using treemacs to create standalone control?

Here I'll need to think of something.

yyoncho commented 5 years ago

Here I'll need to think of something.

Skipping the root node and adjusting the indentation will do the job for me.

yyoncho commented 5 years ago
  1. treemacs--evade-image moves the cursor in the begining of the line even if you have navigated to different buffer as a result of ret action.
yyoncho commented 5 years ago
  1. What is the proper way to refresh standalone treemacs extension? I tried:
(with-current-buffer (get-buffer-create "*Showcase Buffer List*")
  (treemacs-update-node (list 'Buffers)))

It seems like the code is not adjusted for that case.

Alexander-Miller commented 5 years ago

Dynamic icons have been pushed now. Variadic top-level extensions are next. I'll also investigate points 3 and 4.

Can you give me an example of what kind of data you'd use to initialize a variadic extension - is it just a list of strings or symbols or is there some additional data you want to make use of?

Skipping the root node and adjusting the indentation will do the job for me.

You need to explain what you mean here. Which root node is being skipped? And how should indentation be adjusted? We're talking about top-level nodes so the indentation is just zero, right?

By the way, I made some changes to nomenclature to help avoid misunderstandings (if you use the old terms the compiler will warn you about their obsolescence). treemacs-define-root-extension is replaced by treemacs-define-top-level-extension, :project-marker is replaced by :top-level-marker.

The previous situation was inconsistent, bascically it's like this: nodes (and extensions) that sit at the very top, with zero indentation, on the same level as projects, are top-level nodes, while root nodes are simply whatever sits at the top of your hierarchy of node types, but they'd be displayed as part of some project or directory.

yyoncho commented 5 years ago

Can you give me an example of what kind of data you'd use to initialize a variadic extension - is it just a list of strings or symbols or is there some additional data you want to make use of?

Files, I want to use treemacs-icon-for-file for rendering their icon. Here it is what I currently have, if you are lsp-mode user you may try it: https://github.com/emacs-lsp/lsp-treemacs .

You need to explain what you mean here. Which root node is being skipped? And how should indentation be adjusted? We're talking about top-level nodes so the indentation is just zero, right?

Please ignore my comment: I was referring the fact that if you have a way to set the indentation of the second level extensions to 0 then one can use narrow-to-region to have multiple top level extension.

Alexander-Miller commented 5 years ago

What is the proper way to refresh standalone treemacs extension?

The first element of a custom node's path is always the project it's situated under. I need this to for example disambiguate the same dependency explorer extension in 2 different java projects. For top-level extensions it's probably not needed, but should not be a problem either, top-level extensions have their projects stored buffer-locally in treemacs-${name}-extension-project.

Alexander-Miller commented 5 years ago

For top-level extensions it's probably not needed, but should not be a problem either,

But variadic extensions will make that a royal pain in the ass. I'll try to get rid of it.

Alexander-Miller commented 5 years ago

Pushed a first version of variadic extensions. Set :top-level-marker to 'variadic. Nothing's documented yet, I'll wait for feedback before getting into that. So far it'll only work if you call it yourself with the input you want, there's no way to give it to treemacs-define-top-level-extension (you can of course, but it'll just throw an error).

The extension will expect to be called with a list of cons cells, each mapping a label to a key, available as extension-label and extension-key. The keys should be unique, so we can forego the whole project requirement and just use them for a path.

Ugly quick example code:

(treemacs-define-leaf-node "TESTLEAF" (treemacs-as-icon "x "))

(treemacs-define-expandable-node TESTROOT
  :icon-open (treemacs-as-icon "A ")
  :icon-closed (treemacs-as-icon "B ")
  :query-function '("X" "Y" "Z")
  :top-level-marker 'variadic
  :root-label "TESTROOT"
  :root-key-form extension-key
  :root-face font-lock-keyword-face
  :render-action
  (treemacs-render-node
   :icon "x "
   :label-form item
   :state treemacs-TESTLEAF-state
   :face 'font-lock-doc-face
   :key-form item))

(defun showcase-display-buffer-list ()
  (interactive)
  (let* ((buffer (get-buffer-create "*Showcase*"))
         (window (display-buffer-in-side-window buffer '((side . right)))))
    (select-window window)
    (treemacs-initialize)
    (treemacs-TESTROOT-extension '(("L1" . "FULLNAME1") ("L2" . "FULLNAME2") ("L3" . "FULLNAME3")))))

(showcase-display-buffer-list)

Btw updating won't work with this either. The goto-node code currently expects the first path item to be a project to detect it's a custom node we want to move to. I'll probably change the api to require the first element to be something like :custom to tell things apart.

:root-label "TESTROOT"

Now that I look this over I see that this isn't used anywhere. I suppose we can drop the alist approach and use this to pass a form that calculates the label based on the key. What do you think?

yyoncho commented 5 years ago

I will need updating - I will list only the project roots that have errors. Also, I would like to update the error list when the diagnostics are changed which will result in adding removing roots.

Here it is unpolished screenshot of what I have.

emacs myoncho_091

Alexander-Miller commented 5 years ago

Pushed update to variadic extensions: they're now identified by their key (so those keys must be unique). Updating them will work when the first element of the path is :custom. To get back to the previous example:

(with-current-buffer (get-buffer-create "*Showcase*")
  (treemacs-update-node '(:custom "FULLNAME1")))

will now do the trick.

Hopefully I didn't break anything in the process - my current priority is a fast feedback loop. Let me know if you see anything that looks like a bug.

Alexander-Miller commented 5 years ago

Pushed more stuff. Now all top-level extensions' paths start with their :root-key-form.

yyoncho commented 5 years ago

@Alexander-Miller

Here it is my code to render the roots:

(let* ((buffer (get-buffer-create "*LSP Errors*"))
       (window (display-buffer-in-side-window buffer '((side . bottom)))))
  (select-window window)
  (treemacs-initialize)
  (treemacs-LSP-ERROR-LIST-extension (lsp-treemacs--root-folders)))

What is the code needed to sync the new roots? If I have understood you correctly the code you have listed will update a single root node but I want to update all of the root nodes, e. g. something like treemacs-sync-and-update-root-nodes which will delete all root nodes that are no longer present, it will add the new one and it will update the existing one?

Alexander-Miller commented 5 years ago

You are asking for the big guns now. In fact I hadn't really been planning on adding something like this, I've always had the expectation of working with an event based update model with a mostly static number of roots, such as with file changes and buffer creation & deletion.

This kind of resync function will be a special version of treemacs--consolidate-projects - if you want to get an idea of what that the implementation would look like. So yeah, I'll need a bit of time to get this right.

yyoncho commented 5 years ago

Would't adding flag :hide, enforce node to be always open and do not increase :depth for that node be sufficient to cover that functionality?

yyoncho commented 5 years ago

Just noticed that most of the stuff I could do it with what I currently have(it pretty much works if there is at least one item initially)

(let* ((buffer (get-buffer-create "*Showcase Buffer List*"))
       (window (display-buffer-in-side-window buffer '((side . bottom)))))
  (select-window window)
  (treemacs-initialize)

  (treemacs-LSP-ERROR-LIST-extension)
  (goto-char (point-min))
  (treemacs-with-writable-buffer
   (treemacs-button-put (treemacs-node-at-point) :depth -1))

  (treemacs-expand-lsp-error-list)
  (goto-char (point-min))
  (next-line)
  (narrow-to-region (point) (point-max)))
Alexander-Miller commented 5 years ago

(treemacs-button-put (treemacs-node-at-point) :depth -1)

Interesting trick. But the whole approach seems all sorts of hacky. I have no idea how treemacs would deal with narrowing.

Would't adding flag :hide, enforce node to be always open and do not increase :depth for that node be sufficient to cover that functionality?

I think it really depends on what kind of update behavior you require here. The way you described things at first sounded like what's done in the consolidate-projects function. After an org-edit everything can have changed, new projects added, old ones deleted, their order can change as well. So I need to delete everything, render it all anew, with the addendum that whatever nodes had been expanded remain so. That's not exactly the most efficient approach (and I can probably make some improvements), so if you can work with updating less than the whole thing you should definitely do so. And let me know if there's any invariants on your end, for example the nodes cannot change order when updating, that kinda stuff should make the implementation easier and the code faster. If you think you need it I can push a treemacs-delete-single-node next, it's mostly just missing documentation here.

In the meantime you can call treemacs-initialize as a dumb update method. It'll kill the buffer and buffer-local variables, so you can just re-start your extension. Only downside is that it won't remember expanded nodes.

Btw this talk about depth has given me the idea to handle indentation like org-mode does, using a line-prefix property. In practical terms nothing should change, but it might have an impact on performance.

yyoncho commented 5 years ago

Interesting trick. But the whole approach seems all sorts of hacky. I have no idea how treemacs would deal with narrowing.

I just wanted to illustrate the idea - refreshing the roots seems to be no different from refreshing nested buttons (for my case) . It seems to me that most of the code to support that is already there. Note that I do not want to threat them as real roots from treemacs perspective but only visually. Treemacs does not handle

In the meantime you can call treemacs-initialize as a dumb update method. It'll kill the buffer and buffer-local variables, so you can just re-start your extension. Only downside is that it won't remember expanded nodes.

This is no go - the errors will be refreshed as you type so no rememering the expanded notes will hurt usability. I would prefer to have artificial root instead of loosing this functionality.

And let me know if there's any invariants on your end, for example the nodes cannot change order when updating,

The code is pretty simple: in lsp-mode I have set of projects I want to display the projects that have errors, similar to what Eclipse Problems list is doing. When you expand the project you will see the files contaiining the errors and when you expand the file you will see the errors in that file. When user fixes a particular error I want to remove that project if there are no errors in that project and vice versa. So I will have the same project order but a project might be inserted in the list. I could insert it in the end but it wont feel natural.

yyoncho commented 5 years ago

As a side note, would you accept a PR with icons for error/info/warning in treemacs?

Alexander-Miller commented 5 years ago

Note that I do not want to threat them as real roots from treemacs perspective but only visually.

That is the direction the current code is evolving as well. Top-level nodes in the main part of treemacs hold a special place as being your projects, but as you further describe your use-case I see that variadic extensions really don't need that kind of special treatment. Only updating them can be somewhat non-trivial, but in general the process really is no different than updating a hidden depth=-1 node.

Treating top-level extensions as project roots does have some internal advantages, it's less special-casing so I can, for example, reuse the code that deletes a project and makes sure that the right amount of lines is deleted based on treemacs-space-between-root-nodes. The current state of the extension api makes me think that I tried to force something akin to a static type system on your tree hierarchy, what with the precise specifications how Node Type A renders Node Type B renders Node Type C etc. I'm sure I can make the whole thing much less strict, dynamic icons were already a good start.

And just to establish a common understanding when talking about performance: when I talk performance and efficiency I don't mean spending less than a full second in your widget lib, my standard is to not even cause a perceptible stutter. A large part of the slowness of consolidating projects is re-opening everything by re-querying the file system. I assume your lsp integration would produce mostly static lists to render (as in 1 call to get the entire error info, at least per project), or at least be faster than caliing directory-files and filtering and then sorting its output? If so our performance budget situation just got a lot better.

As a side note, would you accept a PR with icons for error/info/warning in treemacs?

Sure. There's probably no use for them in vanilla treemacs, but it's still good for additional icons to be in the main repo in case another extension needs them.

yyoncho commented 5 years ago

And just to establish a common understanding when talking about performance: when I talk performance and efficiency I don't mean spending less than a full second in your widget lib, my standard is to not even cause a perceptible stutter.

I have no IO calls, only counting errors/warnings/info which I already have in memory. I can precompute some of the stuff but for now everything is calculated on demand. In the future, this could be optimized further or I could add debounce of error processing.

Alexander-Miller commented 5 years ago

I can precompute some of the stuff but for now everything is calculated on demand.

I think we're going to need this. There's an unpleasant issue with re-syncing something like your diagnostics buffer. Assume for example that on re-sync the "Errors" top-level node is still there, but the state has changed such that it should show errors of different projects than it did before, and the project nodes were also previously expanded to show errors in individual files.

After the sync treemacs will of course attempt to re-expand these projects that are no longer there, causing errors. Technically this also applies to my own dealings with the file system, but those edits are quite rare, helped by filewatch-mode, and I have the means to deal with them thanks to file-exists-p.

However short of some ugly try/catch mechanism I do not see how to make this work in your case, at least not with the info I'd have now. But a good solution is possible: are you familiar with treemacs' pseudo-dom? Long story short it's a doubly linked tree (mostly) representing the expanded nodes in a given buffer. All the dom-nodes are part of a hash table (mostly that's why I need unique keys) so I can quickly jump in and start searching at whatever point I need. So if you can provide me with something I can use to construct a new dom you'd make my life a lot easier. What I need is some kind of nested structure - only the expandable part, not the leaves - that I can use to cull the current dom.

Full node paths would be best, but I think collecting those places too great a burden on you as the consumer of the extension api, so just the nodes' individual keys will be fine. As an example I can image the input to a resync function to look something like this:

'((Errors
   ((Project1
     (File1
      File2))
    (Project2
     File3)))
  (Warnings
   ((Project3
     (File4
      File5
      File6)))))
yyoncho commented 5 years ago

The code is available at https://github.com/emacs-lsp/lsp-treemacs/blob/master/lsp-treemacs.el .

The model is project -> file -> error/warnings/infos -> error/warnings/infos. For the record, at some point the refresh was working fine, e. g. when fixed an error it was removed from the tree and the state of the rest node was persisted. It does not work in the latest code but I am not sure whether I have changed something.

Alexander-Miller commented 5 years ago

It does not work in the latest code but I am not sure whether I have changed something.

That's probably on me and the latest changes to top-level extensions. I'll try and investigate this based on your code now.

~So far I installed pyls, the lsp layer, and added my scripts directory as an lsp project. Most features work, but I'm not getting anything in flycheck. The lsp-ui checker is selected, flycheck's diagnostics say all is well, but bungling my scripts' syntax get me no reaction. Any idea what I'm missing?~ nvm it works after restrting emacs.

The code is available at https://github.com/emacs-lsp/lsp-treemacs/blob/master/lsp-treemacs.el

Sure, but I am also utterly unfamiliar with lsp's internals, that is why I'm asking how well we can transform lsp's data into something treemacs can digest properly.

But for now let's get the basics going. First I need to get your example going. Then I'll fix the current update problem. Then we'll on to making re-sync work.

Btw, treemacs-delete-single-node has now made it to master.

Alexander-Miller commented 5 years ago

Insta-update: your update code will work if you do it like this:

(defun lsp-treemacs--after-diagnostics ()
  (with-demoted-errors "%s"
    (with-current-buffer (get-buffer-create "*Showcase Buffer List*")
      (treemacs-update-node '(:custom Buffers)))))

I added that :custom part to help me disambiguate things. TL;DR version is that treemacs-goto-node which specialized goto-x function to call. It might not stick around in this form, so be ready so see it changed.

yyoncho commented 5 years ago

From treemacs point of view the models is just 3 functions returning (label . key) ((label . key)).

let me know if you need more info

Alexander-Miller commented 5 years ago

So far so good. I've pushed some cleanups to my fork and will try building the dom for the resync next.

yyoncho commented 5 years ago

So far so good. I've pushed some cleanups to my fork and will try building the dom for the resync next.

Do I need to retest something?

Alexander-Miller commented 5 years ago

Nope. I do think it'd be expedient for you to merge the changes I'm making - I have removed all compiler warnings and could turn the error view into something of a reference implementation - but that is up to you.

I'm also looking forward to your new icons, that ⚠ I'm using now isn't looking all that good.

Alexander-Miller commented 5 years ago

Got a working example of what a dom computation for treemacs could look like:

(defun treemacs-lsp--recompute-dom ()
  "TODO."
  (-let [new-dom nil]
    (treemacs--maphash (lsp-diagnostics) (file diag-entries)
      (-let [pairs nil]
        (dolist (diag diag-entries)
          (push (cons file diag) pairs))
        (push (cons file pairs) new-dom)))
    (-let [session-folders (lsp-session-folders (lsp-session))]
      (cons 'lsp-error-list
            (-group-by
             (lambda (dom-item)
               (-let [folder (--first (treemacs-is-path (car dom-item) :in it)
                                      session-folders)]
                 (treemacs--filename folder)))
             new-dom)))))

The output would look like this:

(lsp-error-list
 ("scripts"
  ("/home/a/Documents/git/treemacs/src/scripts/treemacs-dirs-to-collapse.py"
   ("/home/a/Documents/git/treemacs/src/scripts/treemacs-dirs-to-collapse.py"
    . #s(lsp-diagnostic  2 0 2 nil "pyflakes" "pyflakes: 'os.path.isfile' imported but unused"))
   ("/home/a/Documents/git/treemacs/src/scripts/treemacs-dirs-to-collapse.py"
    . #s(lsp-diagnostic  1 0 2 nil "pyflakes" "pyflakes: redefinition of unused 'listdir' from line 1")))
  ("/home/a/Documents/git/treemacs/src/scripts/treemacs-git-status.py"
   ("/home/a/Documents/git/treemacs/src/scripts/treemacs-git-status.py"
    . #s(lsp-diagnostic  57 47 1 nil "pyflakes" "pyflakes: invalid syntax")))))

I noticed that sorting diagnosis files to projects is a often used pattern, so if we get to optimizing that's where we should start.

yyoncho commented 5 years ago

@Alexander-Miller I am a bit concerned about the dom - will treemacs always require passing the whole tree on update? Also, having the dom tree upfront is not always an option since it might require remote calls. Another drawback is that it will force the clients to implement smart handling of the updates while in the current approach it is not needed.

Alexander-Miller commented 5 years ago

will treemacs always require passing the whole tree on update?

No, not always. I am preparing for the most complex of use cases: a total update of a variadic extension where I cannot be certain which nodes were deleted, which are new, and what their order is. Having a dom to go by in such a case is a great relief. Anything other than that remains the same old TAB-TAB approach with treemacs-update-node.

I hope I can make providing the dom optional, it depends on how well I can clean things up in the background on a resync (basically if I record node [A B C] as expanded with a bunch of child elements, but after an update it's deleted - how well can I find and removed that vestige, and how much trouble is it if it's left there).

We should go ahead here anyway, a proper reference implementation will be useful for both of us.

yyoncho commented 5 years ago

Thank you.

Do you have more comments on #354 ? Also, do you think that I should move lsp-treemacs forward using the artificial "Error" root and later when treemacs supports multiple dynamic roots to switch to that implementation or it would be better to wait for the full support?

Edit: It is not urgent on my side, I just want to plan my work.

Alexander-Miller commented 5 years ago

Also, do you think that I should move lsp-treemacs forward using the artificial "Error" roo

That should be fine. It'll give me enough breathing room to take care of all the other issues that have since appeared.

yyoncho commented 5 years ago

With the latest version of I am getting the following error when I delete an branch (e. g. fix an exception which results in removing few nodes) lsp-treemacs.el

Debugger entered--Lisp error: (wrong-type-argument integer-or-marker-p nil)
  get-text-property(nil :depth)
  treemacs-goto-node((:custom LSP-Errors "/home/kyoncho/Sources/lsp/dap-mode/features/fixtur..."))
  treemacs--reopen-at((:custom LSP-Errors))
  (progn (treemacs-on-expand (get-text-property btn :path) btn (let ((result (let ((result btn)) (if result (progn ...))))) (if result (progn (get-text-property result :path))))) (treemacs--reopen-at (get-text-property btn :path)))
  (let (buffer-read-only) (put-text-property (or (previous-single-property-change (1+ btn) 'button) (point-min)) (or (next-single-property-change btn 'button) (point-max)) :state 'treemacs-lsp-error-list-open-state) (beginning-of-line) (save-excursion (let ((len (length treemacs-icon-lsp-error-list-open))) (goto-char (- (button-start (next-button (point-at-bol) t)) len)) (insert treemacs-icon-lsp-error-list-open) (delete-char len))) (end-of-line) (progn (insert (apply #'concat (let* ((depth depth) (prefix (concat "\n" ...)) (item (cl-first items)) (strings)) (if item (progn (let ... ...))) (nreverse strings))))) (progn (treemacs-on-expand (get-text-property btn :path) btn (let ((result (let (...) (if result ...)))) (if result (progn (get-text-property result :path))))) (treemacs--reopen-at (get-text-property btn :path))))
  (save-excursion (let (buffer-read-only) (put-text-property (or (previous-single-property-change (1+ btn) 'button) (point-min)) (or (next-single-property-change btn 'button) (point-max)) :state 'treemacs-lsp-error-list-open-state) (beginning-of-line) (save-excursion (let ((len (length treemacs-icon-lsp-error-list-open))) (goto-char (- (button-start (next-button ... t)) len)) (insert treemacs-icon-lsp-error-list-open) (delete-char len))) (end-of-line) (progn (insert (apply #'concat (let* ((depth depth) (prefix ...) (item ...) (strings)) (if item (progn ...)) (nreverse strings))))) (progn (treemacs-on-expand (get-text-property btn :path) btn (let ((result (let ... ...))) (if result (progn (get-text-property result :path))))) (treemacs--reopen-at (get-text-property btn :path)))))
  (let ((items (lsp-treemacs--root-folders)) (depth (1+ (get-text-property btn :depth)))) (save-excursion (let (buffer-read-only) (put-text-property (or (previous-single-property-change (1+ btn) 'button) (point-min)) (or (next-single-property-change btn 'button) (point-max)) :state 'treemacs-lsp-error-list-open-state) (beginning-of-line) (save-excursion (let ((len (length treemacs-icon-lsp-error-list-open))) (goto-char (- (button-start ...) len)) (insert treemacs-icon-lsp-error-list-open) (delete-char len))) (end-of-line) (progn (insert (apply #'concat (let* (... ... ... ...) (if item ...) (nreverse strings))))) (progn (treemacs-on-expand (get-text-property btn :path) btn (let ((result ...)) (if result (progn ...)))) (treemacs--reopen-at (get-text-property btn :path))))))
  treemacs--do-expand-lsp-error-list(#<marker (moves after insertion) at 3 in *LSP Error List*>)
  (let* ((btn (let* ((point-at-bol (point-at-bol))) (if (get-text-property point-at-bol 'button) (copy-marker point-at-bol t) (let* ((p ...)) (if (get-char-property p ...) (progn ...))))))) (if (null btn) (progn (throw '--cl-block-__body__-- (treemacs-pulse-on-failure "There is nothing to do here.")))) (if (not (eq 'treemacs-lsp-error-list-closed-state (get-text-property btn :state))) (progn (throw '--cl-block-__body__-- (treemacs-pulse-on-failure "This function cannot expand a node of type '%s'." (propertize (format "%s" (get-text-property btn :state)) 'face 'font-lock-type-face))))) (treemacs--do-expand-lsp-error-list btn))
  (catch '--cl-block-__body__-- (let* ((btn (let* ((point-at-bol (point-at-bol))) (if (get-text-property point-at-bol 'button) (copy-marker point-at-bol t) (let* (...) (if ... ...)))))) (if (null btn) (progn (throw '--cl-block-__body__-- (treemacs-pulse-on-failure "There is nothing to do here.")))) (if (not (eq 'treemacs-lsp-error-list-closed-state (get-text-property btn :state))) (progn (throw '--cl-block-__body__-- (treemacs-pulse-on-failure "This function cannot expand a node of type '%s'." (propertize (format "%s" ...) 'face 'font-lock-type-face))))) (treemacs--do-expand-lsp-error-list btn)))
  treemacs-expand-lsp-error-list()
  treemacs-update-node((:custom LSP-Errors))
  (save-current-buffer (set-buffer (get-buffer-create "*LSP Error List*")) (treemacs-update-node '(:custom LSP-Errors)))
  (condition-case err (save-current-buffer (set-buffer (get-buffer-create "*LSP Error List*")) (treemacs-update-node '(:custom LSP-Errors))) ((debug error) (message "%s" err) nil))
  lsp-treemacs--after-diagnostics()
Alexander-Miller commented 5 years ago

Can you give me a rundown of what happened to the tree - what did it look like at the start, what got deleted, and which part did you update?

yyoncho commented 5 years ago
  1. There are errors in the file
  2. The errors were fixed(the file will be deleted).

emacs myoncho_109

emacs myoncho_110

Alexander-Miller commented 5 years ago

Ah, you found a pretty general bug, present in all parts of treemacs, that somehow managed to remain undiscovered till now. Basically treemacs remember the node to be expanded, and tries to re-expand it when you update, but it's deleted, so it fails. With a bit of luck it should be a quick enough fix.

BTW, some of the icons in company-box could be useful for us as well.

yyoncho commented 5 years ago

Yeah. I am going to implement treemacs versions of type hierarchy/call hierarchy when they are present in the java server and then I will add the icons in treemacs.

  1. treemacs--evade-image moves the cursor in the begining of the line even if you have navigated to different buffer as a result of ret action.

This is still reproducible.

Alexander-Miller commented 5 years ago

This is still reproducible.

Should be fixed now.

Alexander-Miller commented 5 years ago

Minor update: we are actually quite lucky that reopening works recursively by moving to a node's immediate children, otherwise this whole thing would've gotten really tricky. Without the lean, event-based interface of filenotify.el I have no means to determine that the node is gone, except for trial-and-error. Luckily this is basically what's supposed to happen anyway - we try to move to a node and expand it, and when it's not there the move should fail gracefully - no error, and we move back to where we started. It's only the latter part that is failing (I don't think I ever tested the goto-custom-node code quite so thoroughly). I'll make fixing that a priority now, so we should have a solution within the week.

Alexander-Miller commented 5 years ago

Found an issue in lsp-treemacs: lsp returns directories with a final slash, but treemacs is completely geared towards paths without final slashes. As a start you can call treemacs--unslash in the final step in lsp-treemacs--root-folders, but it might be troublesome in the future.

Alexander-Miller commented 5 years ago

Never mind, custom nodes' keys can look however you like, the final slash restriction only applies to treemacs' own file nodes.

Alexander-Miller commented 5 years ago

Based on some cursory testing the update seems to be working now. At least for the most part, the hl-line overlay isn't moving quite right.

yyoncho commented 5 years ago

Left click yields(let me know if it is something that I had to fix):

Debugger entered--Lisp error: (wrong-type-argument window-live-p nil)
  #<subr select-window>(nil nil)
  ad-Advice-select-window(#<subr select-window> nil)
  apply(ad-Advice-select-window #<subr select-window> nil)
  select-window(nil)
  #f(compiled-function (event) "Move focus to the clicked line.\nMust be bound to a mouse click, or EVENT will not be supplied." (interactive "e") #<bytecode 0x155950e1d199>)((mouse-1 (#<window 85 on *LSP Error List*> 48 (154 . 74) 483937292 nil 48 (38 . 1) nil (-73 . 48) (7 . 26))))
  apply(#f(compiled-function (event) "Move focus to the clicked line.\nMust be bound to a mouse click, or EVENT will not be supplied." (interactive "e") #<bytecode 0x155950e1d199>) (mouse-1 (#<window 85 on *LSP Error List*> 48 (154 . 74) 483937292 nil 48 (38 . 1) nil (-73 . 48) (7 . 26))))
  treemacs-leftclick-action((mouse-1 (#<window 85 on *LSP Error List*> 48 (154 . 74) 483937292 nil 48 (38 . 1) nil (-73 . 48) (7 . 26))))
  funcall-interactively(treemacs-leftclick-action (mouse-1 (#<window 85 on *LSP Error List*> 48 (154 . 74) 483937292 nil 48 (38 . 1) nil (-73 . 48) (7 . 26))))
  call-interactively(treemacs-leftclick-action nil nil)
  command-execute(treemacs-leftclick-action)

Also, I am getting the following error when I have the whole tree open and I fix all of errors:

Debugger entered--Lisp error: (wrong-type-argument integer-or-marker-p nil)
  get-text-property(nil :parent)
  treemacs-goto-node((:custom LSP-Errors "/home/kyoncho/Sources/rust/hello/"))
  treemacs--reopen-at((:custom LSP-Errors))
  (progn (treemacs-on-expand (get-text-property btn :path) btn (let ((result (let ((result btn)) (if result (progn ...))))) (if result (progn (get-text-property result :path))))) (treemacs--reopen-at (get-text-property btn :path)))
  (let (buffer-read-only) (put-text-property (or (previous-single-property-change (1+ btn) 'button) (point-min)) (or (next-single-property-change btn 'button) (point-max)) :state 'treemacs-lsp-error-list-open-state) (beginning-of-line) (save-excursion (let ((len (length treemacs-icon-lsp-error-list-open))) (goto-char (- (button-start (next-button (point-at-bol) t)) len)) (insert treemacs-icon-lsp-error-list-open) (delete-char len))) (end-of-line) (progn (insert (apply #'concat (let* ((depth depth) (prefix (concat "\n" ...)) (item (cl-first items)) (strings)) (if item (progn (let ... ...))) (nreverse strings))))) (progn (treemacs-on-expand (get-text-property btn :path) btn (let ((result (let (...) (if result ...)))) (if result (progn (get-text-property result :path))))) (treemacs--reopen-at (get-text-property btn :path))))
  (let* ((p (point))) (let (buffer-read-only) (put-text-property (or (previous-single-property-change (1+ btn) 'button) (point-min)) (or (next-single-property-change btn 'button) (point-max)) :state 'treemacs-lsp-error-list-open-state) (beginning-of-line) (save-excursion (let ((len (length treemacs-icon-lsp-error-list-open))) (goto-char (- (button-start (next-button ... t)) len)) (insert treemacs-icon-lsp-error-list-open) (delete-char len))) (end-of-line) (progn (insert (apply #'concat (let* ((depth depth) (prefix ...) (item ...) (strings)) (if item (progn ...)) (nreverse strings))))) (progn (treemacs-on-expand (get-text-property btn :path) btn (let ((result (let ... ...))) (if result (progn (get-text-property result :path))))) (treemacs--reopen-at (get-text-property btn :path)))) (count-lines p (point)))
  (save-excursion (let* ((p (point))) (let (buffer-read-only) (put-text-property (or (previous-single-property-change (1+ btn) 'button) (point-min)) (or (next-single-property-change btn 'button) (point-max)) :state 'treemacs-lsp-error-list-open-state) (beginning-of-line) (save-excursion (let ((len (length treemacs-icon-lsp-error-list-open))) (goto-char (- (button-start ...) len)) (insert treemacs-icon-lsp-error-list-open) (delete-char len))) (end-of-line) (progn (insert (apply #'concat (let* (... ... ... ...) (if item ...) (nreverse strings))))) (progn (treemacs-on-expand (get-text-property btn :path) btn (let ((result ...)) (if result (progn ...)))) (treemacs--reopen-at (get-text-property btn :path)))) (count-lines p (point))))
  (let ((items (lsp-treemacs--root-folders)) (depth (1+ (get-text-property btn :depth)))) (save-excursion (let* ((p (point))) (let (buffer-read-only) (put-text-property (or (previous-single-property-change (1+ btn) 'button) (point-min)) (or (next-single-property-change btn 'button) (point-max)) :state 'treemacs-lsp-error-list-open-state) (beginning-of-line) (save-excursion (let ((len ...)) (goto-char (- ... len)) (insert treemacs-icon-lsp-error-list-open) (delete-char len))) (end-of-line) (progn (insert (apply #'concat (let* ... ... ...)))) (progn (treemacs-on-expand (get-text-property btn :path) btn (let (...) (if result ...))) (treemacs--reopen-at (get-text-property btn :path)))) (count-lines p (point)))))
  treemacs--do-expand-lsp-error-list(#<marker (moves after insertion) at 3 in *LSP Error List*>)
  (let* ((btn (let* ((point-at-bol (point-at-bol))) (if (get-text-property point-at-bol 'button) (copy-marker point-at-bol t) (let* ((p ...)) (if (get-char-property p ...) (progn ...))))))) (if (null btn) (progn (throw '--cl-block-__body__-- (treemacs-pulse-on-failure "There is nothing to do here.")))) (if (not (eq 'treemacs-lsp-error-list-closed-state (get-text-property btn :state))) (progn (throw '--cl-block-__body__-- (treemacs-pulse-on-failure "This function cannot expand a node of type '%s'." (propertize (format "%s" (get-text-property btn :state)) 'face 'font-lock-type-face))))) (treemacs--do-expand-lsp-error-list btn))
  (catch '--cl-block-__body__-- (let* ((btn (let* ((point-at-bol (point-at-bol))) (if (get-text-property point-at-bol 'button) (copy-marker point-at-bol t) (let* (...) (if ... ...)))))) (if (null btn) (progn (throw '--cl-block-__body__-- (treemacs-pulse-on-failure "There is nothing to do here.")))) (if (not (eq 'treemacs-lsp-error-list-closed-state (get-text-property btn :state))) (progn (throw '--cl-block-__body__-- (treemacs-pulse-on-failure "This function cannot expand a node of type '%s'." (propertize (format "%s" ...) 'face 'font-lock-type-face))))) (treemacs--do-expand-lsp-error-list btn)))
  treemacs-expand-lsp-error-list()
  treemacs-update-node((:custom LSP-Errors))

-> treemacs-goto-node((:custom LSP-Errors "/home/kyoncho/Sources/rust/hello/"))

This node is not present.

Alexander-Miller commented 5 years ago

Overlay issue is that treemacs-update-node does not save position, but does call hl-line-highlight. I'll probably split it up like delete-node into update and do-update, where the former saves position and re-highlights and the latter does not, so one version is faster and can easier be used in bulk.

The save-position macro is not behaving quite right for an all-custom-node setup either, so I'll need to look at that as well.

Alexander-Miller commented 5 years ago

treemacs-goto-node((:custom LSP-Errors "/home/kyoncho/Sources/rust/hello/")) treemacs--reopen-at((:custom LSP-Errors))

Chunks of the re-open logic still aren't quite right it seems.

Left click yields(let me know if it is something that I had to fix):

That's a bug in the mouseclick action, it is trying to select (treemacs-get-local-window), so I will need to fix that.

Alexander-Miller commented 5 years ago

Both bugs should be fixed now.

yyoncho commented 5 years ago

Thank you, I retest and let you know. I have started the melpa onboarding for lsp-treemacs.

yyoncho commented 5 years ago

I am getting this error with the latest lsp-treemacs from time to time.

Debugger entered--Lisp error: (error "Node at path (:custom LSP-Errors) cannot be found")
  signal(error ("Node at path (:custom LSP-Errors) cannot be found"))
  error("Node at path %s cannot be found" (:custom LSP-Errors))
  (if btn (progn (if (memq (get-text-property btn :state) treemacs--open-node-states) (progn (let* ((close-func (alist-get ... treemacs-TAB-actions-config))) (funcall close-func) (if (= 1 (funcall ...)) (progn (funcall close-func))))))) (error "Node at path %s cannot be found" path))
  (let ((btn (treemacs-goto-node path))) (if btn (progn (if (memq (get-text-property btn :state) treemacs--open-node-states) (progn (let* ((close-func ...)) (funcall close-func) (if (= 1 ...) (progn ...)))))) (error "Node at path %s cannot be found" path)))
  (let* ((curr-btn (let* ((point-at-bol (point-at-bol))) (if (get-text-property point-at-bol 'button) (copy-marker point-at-bol t) (let* ((p ...)) (if (get-char-property p ...) (progn ...)))))) (next-path (let ((result (let (...) (if result ...)))) (if result (progn (button-get result :path))))) (prev-path (let ((result (let (...) (if result ...)))) (if result (progn (button-get result :path))))) (curr-node-path (let ((result curr-btn)) (if result (progn (get-text-property result :path))))) (curr-state (let ((result curr-btn)) (if result (progn (get-text-property result :state))))) (curr-file (let ((result curr-btn)) (if result (progn (treemacs--nearest-path result))))) (curr-tagpath (let ((result curr-btn)) (if result (progn (treemacs--tags-path-of result))))) (curr-window (treemacs-get-local-window)) (curr-win-line (if curr-window (progn (let ((save-selected-window--state ...)) (save-current-buffer (unwind-protect ... ...))))))) (let ((btn (treemacs-goto-node path))) (if btn (progn (if (memq (get-text-property btn :state) treemacs--open-node-states) (progn (let* (...) (funcall close-func) (if ... ...))))) (error "Node at path %s cannot be found" path))) (cond ((memq curr-state '(file-node-closed file-node-open dir-node-closed dir-node-open root-node-closed root-node-open)) (if (and (file-exists-p curr-file) (or treemacs-show-hidden-files (not (s-matches\? treemacs-dotfiles-regex ...)))) (treemacs-goto-file-node curr-file) (let (--cl-can-move-to--) (setq --cl-can-move-to-- #'(lambda ... ...)) (cond ((and next-path ...) (treemacs-goto-file-node next-path)) ((and prev-path ...) (treemacs-goto-file-node prev-path)) (t (let* ... ... ...)))))) ((memq curr-state '(tag-node tag-node-closed tag-node-open)) (treemacs--goto-tag-button-at curr-tagpath)) ((null curr-state) (goto-char (point-min))) (t (condition-case _ (treemacs-goto-node curr-node-path) (error (ignore))))) (treemacs--evade-image) (if curr-win-line (progn (let ((save-selected-window--state (internal--before-with-selected-window curr-window))) (save-current-buffer (unwind-protect (progn (select-window ... ...) (recenter ...)) (internal--after-with-selected-window save-selected-window--state)))))))
  (progn (let* ((curr-btn (let* ((point-at-bol (point-at-bol))) (if (get-text-property point-at-bol 'button) (copy-marker point-at-bol t) (let* (...) (if ... ...))))) (next-path (let ((result (let ... ...))) (if result (progn (button-get result :path))))) (prev-path (let ((result (let ... ...))) (if result (progn (button-get result :path))))) (curr-node-path (let ((result curr-btn)) (if result (progn (get-text-property result :path))))) (curr-state (let ((result curr-btn)) (if result (progn (get-text-property result :state))))) (curr-file (let ((result curr-btn)) (if result (progn (treemacs--nearest-path result))))) (curr-tagpath (let ((result curr-btn)) (if result (progn (treemacs--tags-path-of result))))) (curr-window (treemacs-get-local-window)) (curr-win-line (if curr-window (progn (let (...) (save-current-buffer ...)))))) (let ((btn (treemacs-goto-node path))) (if btn (progn (if (memq (get-text-property btn :state) treemacs--open-node-states) (progn (let* ... ... ...)))) (error "Node at path %s cannot be found" path))) (cond ((memq curr-state '(file-node-closed file-node-open dir-node-closed dir-node-open root-node-closed root-node-open)) (if (and (file-exists-p curr-file) (or treemacs-show-hidden-files (not ...))) (treemacs-goto-file-node curr-file) (let (--cl-can-move-to--) (setq --cl-can-move-to-- #'...) (cond (... ...) (... ...) (t ...))))) ((memq curr-state '(tag-node tag-node-closed tag-node-open)) (treemacs--goto-tag-button-at curr-tagpath)) ((null curr-state) (goto-char (point-min))) (t (condition-case _ (treemacs-goto-node curr-node-path) (error (ignore))))) (treemacs--evade-image) (if curr-win-line (progn (let ((save-selected-window--state (internal--before-with-selected-window curr-window))) (save-current-buffer (unwind-protect (progn ... ...) (internal--after-with-selected-window save-selected-window--state))))))))
  (unwind-protect (progn (let* ((curr-btn (let* ((point-at-bol ...)) (if (get-text-property point-at-bol ...) (copy-marker point-at-bol t) (let* ... ...)))) (next-path (let ((result ...)) (if result (progn ...)))) (prev-path (let ((result ...)) (if result (progn ...)))) (curr-node-path (let ((result curr-btn)) (if result (progn ...)))) (curr-state (let ((result curr-btn)) (if result (progn ...)))) (curr-file (let ((result curr-btn)) (if result (progn ...)))) (curr-tagpath (let ((result curr-btn)) (if result (progn ...)))) (curr-window (treemacs-get-local-window)) (curr-win-line (if curr-window (progn (let ... ...))))) (let ((btn (treemacs-goto-node path))) (if btn (progn (if (memq ... treemacs--open-node-states) (progn ...))) (error "Node at path %s cannot be found" path))) (cond ((memq curr-state '(file-node-closed file-node-open dir-node-closed dir-node-open root-node-closed root-node-open)) (if (and (file-exists-p curr-file) (or treemacs-show-hidden-files ...)) (treemacs-goto-file-node curr-file) (let (--cl-can-move-to--) (setq --cl-can-move-to-- ...) (cond ... ... ...)))) ((memq curr-state '(tag-node tag-node-closed tag-node-open)) (treemacs--goto-tag-button-at curr-tagpath)) ((null curr-state) (goto-char (point-min))) (t (condition-case _ (treemacs-goto-node curr-node-path) (error (ignore))))) (treemacs--evade-image) (if curr-win-line (progn (let ((save-selected-window--state ...)) (save-current-buffer (unwind-protect ... ...))))))) (with-no-warnings (setq treemacs--ready-to-follow o)))
  (let ((o (with-no-warnings treemacs--ready-to-follow))) (with-no-warnings (setq treemacs--ready-to-follow nil)) (unwind-protect (progn (let* ((curr-btn (let* (...) (if ... ... ...))) (next-path (let (...) (if result ...))) (prev-path (let (...) (if result ...))) (curr-node-path (let (...) (if result ...))) (curr-state (let (...) (if result ...))) (curr-file (let (...) (if result ...))) (curr-tagpath (let (...) (if result ...))) (curr-window (treemacs-get-local-window)) (curr-win-line (if curr-window (progn ...)))) (let ((btn (treemacs-goto-node path))) (if btn (progn (if ... ...)) (error "Node at path %s cannot be found" path))) (cond ((memq curr-state '...) (if (and ... ...) (treemacs-goto-file-node curr-file) (let ... ... ...))) ((memq curr-state '...) (treemacs--goto-tag-button-at curr-tagpath)) ((null curr-state) (goto-char (point-min))) (t (condition-case _ (treemacs-goto-node curr-node-path) (error ...)))) (treemacs--evade-image) (if curr-win-line (progn (let (...) (save-current-buffer ...)))))) (with-no-warnings (setq treemacs--ready-to-follow o))))
  treemacs-update-node((:custom LSP-Errors))
  (save-excursion (treemacs-update-node '(:custom LSP-Errors)))
  (save-current-buffer (set-buffer (get-buffer-create "*LSP Error List*")) (save-excursion (treemacs-update-node '(:custom LSP-Errors))))
  (condition-case err (save-current-buffer (set-buffer (get-buffer-create "*LSP Error List*")) (save-excursion (treemacs-update-node '(:custom LSP-Errors)))) ((debug error) (message "%s" err) nil))
  lsp-treemacs--after-diagnostics()
  run-hooks(lsp-after-diagnostics-hook)