ZeroBraneStudio icon indicating copy to clipboard operation
ZeroBraneStudio copied to clipboard

Suggestion. Keep stack frame fold state after step.

Open robertlzj opened this issue 5 years ago • 3 comments

Step in debug will update stack frame, and unfold all of them. Which is not so convenient, if I fold them to just watch function invoke relations. Also, lower frame and its variable isn't change much. So, can stack frame maintain its fold state, or default to folded.

robertlzj avatar Jul 20 '18 02:07 robertlzj

I thought about it, but it's problematic, because un/fold state has to be associated with the function itself, rather than the frame number/position.

It may be much easier to provide an event callback that is called after the stack view is generated and you can write your own plugin that will do what you want to do in that case.

pkulchenko avatar Jul 20 '18 02:07 pkulchenko

There is a very similar problem with watches. Whenever a watched value changes, it folds/unfolds to exactly one level. If it was folded, or any of its sub-elements were expanded, that "configuration" is lost.

Actually, it's exactly the same behavior as with stack trace, but it's not quite as bad since every element of the trace is "updated" after every step.

I have an idea of how folding persistence could be implemented in (IMO) convenient manner and without making the user write any scripts for it, you can see it in the end of this reply. However, I realized that it's probably going to be somewhat difficult to implement, and scriptable folding would be more useful anyway if it's actually used.

My 2¢ on the folding script design:

  • The script must be persisted per project. Re-typing it each time or shuffling plugins around all the time isn't fun.
  • Changes in the script must be applied immediately, without even killing active debug session, for obvious reasons.
  • The callback needs to receive some form of "path" to the item, e.g. {"watches", {"file.lua", "function", "nested function", "lambda#2"}, {"table", index, "nested key"}}, and return if it should be folded or expanded. It should only be called whenever item is "added", letting the user fold/expand it manually.
  • Maybe also add a callback that lists what expressions to watch for each function?
  • Being able to easily add a couple buttons that toggle "watch profiles" to the watches window would be nice.
[click me] My idea of nice point&click implementation of folding with persistence: I actually have a suggestion on how it could be implemented in an easy to use manner (I think so, at least) without making users write any code. I might even get to implementing it myself soon, but I won't trust me on that. (I'm both sort-of new to Lua and this IDE, and I only really use them "for fun".)

The idea is as follows:

  • Folding information is stored in a separate table tree. It's cleared whenever watches are cleared.
  • For the roots, children are indexed by watched expressions and function locations for watches and stack respectively. For nested tables, the indices of children are tostring'ed if they aren't strings or integers. Function locations can be file:line of its definition, but then they'd probably have to be cleared between runs. They can also be lists of parent scopes, starting with file scope, but that would be tricky. (Maybe store just the first line for each block, apply diff to resulting text, and only keep unchanged lines?)
  • Each table has 2 special values stored: __watch_expanded and __watch_expand_depth.
  • Watch_expanded is true/false if that specific value was manually expanded/folded, and nil if it was not.
  • Watch_expand_depth is the number of levels this value should have expanded by default, i.e. for items that have watch_expanded set to nil. Allowed values are non-negative and nil, the latter meaning "inherit", i.e. parent's minus 1.
  • If an item isn't currently visible, due to folding or being inaccessible, its configuration is kept but ignored.
  • If an item has no children and both watch_expanded and watch_expand_depth are nil, it can be deleted.
  • For roots, watch_expand_depth are at least 1 (you can't really fold the whole list) and are set to 1 by default (don't expand any elements).
  • The context menu for each element and root should have "Reset all folding", "Reset manual folding", "Reset all folding for children", "Reset manual folding for children", "Don't expand by default" and "Expand up to N level(s) by default" for each N from 1 up to some small value, plus one "big" value not big enough to crash/hang the IDE. The "default" entries set watch_expand_depth, and the value is incremented by 1 for roots. (Because I'd expect "Expand up to 1 level" on root to mean "Expand each watch up to 1 level".)
  • Deleting a watch should purge its configuration, editing it should update the key in the root table.

Usage scenarios I came up with:

  • Initially, nothing is unfolded, stack doesn't show local vars.
  • Choose "Expand up to 1 level" from stack's context menu, now local vars are shown.
  • "Expand up to levels" on some var/watch. Now it's fully expanded.
  • One of its (possibly not direct) children is a huge table consisting of huge tables. I'd rather keep it all folded, so I choose "Don't expand by default" on it. It's now completely folded, but its siblings still remain expanded.
  • I still can manually expand that huge table, and none of its huge sub-tables are expanded by default, even if they were before the previous step. I can expand/fold each of them manually if I want to.
  • I can fold/expand that huge table however many times I want, and all its children will keep their folded/expanded state each time
  • I can fold/expand any of its parents, or change their default folding depths. Whenever that huge table is visible, its folding and folding of its children stay the same and never reset themselves.
  • Now I entered a function, and that watch is no longer available. It just shows nil. Upon return from this function, however, its value is back, and the folding is the same as it was!

P.S. While writing the last point, I noticed that tying watches to functions might be required for this to work really well. That's not going to be too easy. P.P.S. OR you could just throw all watches for a function into a table expression, and fold/expand them whenever needed.

In any case, persistent configuration through a special per-project script is going to be much more useful if you take time to actually use it.

evg-zhabotinsky avatar Sep 23 '18 23:09 evg-zhabotinsky

In any case, persistent configuration through a special per-project script is going to be much more useful if you take time to actually use it.

@evg-zhabotinsky, love the suggestions; I think it will make more sense if it's implemented consistently across all trees, including saving the state of the project tree. I have to think about the mechanism, but it may be doable.

I can fold/expand that huge table however many times I want, and all its children will keep their folded/expanded state each time

This is exactly how it's done in the tree controls right now, but it may be difficult to re-apply the same configuration after the tree is refreshed. The project tree is refreshed in a special way that only updates the items that have changed, which allows it to keep the items that haven't been modified along with their expand/collapse status (including their children). Stack and watch window content is refreshed every time, as it's more difficult to detect what has changed there to only refresh modified items.

Keeping track of items that have expanded status in a separate table may be doable. The tree elements also have special data area that may be used for this purpose, assuming that the items are preserved upon refresh.

pkulchenko avatar Oct 15 '18 05:10 pkulchenko