Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Folding code: more advanced commands #2920

Open
ghost opened this issue Oct 8, 2021 · 15 comments
Open

Folding code: more advanced commands #2920

ghost opened this issue Oct 8, 2021 · 15 comments

Comments

@ghost
Copy link

ghost commented Oct 8, 2021

EDIT: updated some of suggested commands: fold/ unfold of descendant nodes of given node.

Currently, the only ways to change the folding is:

  • toggle current fold
  • fold all
  • unfold all

For files with many nodes and deep levels of children nodes, it would be great to have commands for efficient navigation and (controlled) fold/unfold of the hierarchical structure:

  • CO1 Move from current node into the parent node (if it has one) and fold that
  • CO2 the opposite: Unfold current node (if parent) and move to 1st child node.

When applied repeatedly, will allows to browse and fold/unfold a full branch / path of nodes. Similar to navigation with arrows in many file managers' tree view of folders (right/left to move across levels and collapse/expand, and up/down to jump through sibling nodes)

  • C3 Folding of all parent nodes in the file at same level as current node's level descendant nodes of current node.
  • C4 Unfolding of all parent nodes in the file at same level as current node's level descendant nodes of current node.

This will fill the big control gap between what's available currently: folding at current node and at whole document. They are analogous to how current "fold all" and "unfold all" work, if think of the file as the "root node" of all other nodes/lines.
These would be more useful, IMO, than the fold/unfold of all nodes of same fixed-level that some other editors provide (for ex , Notepad++ ). First, it allows local fold-region control (so that does change folding in distand sibling nodes/branches, and don't load your visual memory with too much stuff). Second, it's relative to current node, so you don't not need to know which level exactly you're currently on.

By node I don't mean only something that can be folded, but any "logical line" (non-blank line; with one or more programming statements on it; perhaps comment lines too, depending on language ).
A node is a parent node if it contains a folding point (and the nodes inside the fold - children nodes); otherwise is a leaf node. It's a child node if it's inside the fold of another node.

# comment                 # level 0, leaf
print(9)                  # level 0, leaf node
    # comment             # level 0  leaf node (b/c previous line is not a parent node)
def f(x):                 # level 0, parent node
    """ doc string        # level 1, parent node
    lalala                # level 2, leaf node
    """
    y=x+1                 # level 1 , leaf node
    for i in L:           # level 1, parent
        do(x)             # level 2, leaf
    print(x-y)            # level 1, leaf node
    # comment             # level 1, leaf
# comment                 # level 0, leaf node
print(3)                  # level 0, leaf node

In my own use case, in addition to coding, I use Geany for organizing notes in .txt files, with folding by indentation. With ability to navigate fast, and control the hierarchy display, I will not need to use a separate "outliner".

@elextr
Copy link
Member

elextr commented Oct 8, 2021

I don't use folding much myself, but this looks reasonable, perhaps fold users could comment.

But a couple of points, the keybindings should have menu equivalents so they are more discoverable or the actions usable if the keys are not bound. I don't like using up additional keys for default bindings, its already getting so there are not many left, so they would be unbound by default and the menu would allow occasional use of the functionality without binding them. Users with a high usage would of course bind keys to the actions.

I would suggest that in the Document menu Fold All and Unfold All with a single Fold... and a submenu with all the 9 commands.

@elextr
Copy link
Member

elextr commented Oct 8, 2021

To address the last edit about applying to any line, fold points are defined by the lexers in the Scintilla editing widget, they can't be randomly placed. If the lexer isn't defining the fold points you need, you need to address that there.

@ghost
Copy link
Author

ghost commented Oct 8, 2021

Thanks for commenting; I understand.
My last edit about meaning of "node" was not to suggest placing "fold points" at other places than defined by lexers, but to clarify to which node to jump when jumping from current node to "parent node".

@elextr
Copy link
Member

elextr commented Oct 8, 2021

Since you said in C1 that you wanted to fold at a "node" it will have to be a fold point, thats where folding happens.

Scintilla allows fold points to be set randomly by the application, and by the lexers, but since lexers will overwrite random fold points each time they run it is probably impossible to have both at the same time. I'm not sure Geany allows lexer folding to be turned off and anyway it hasn't anything to define fold points in other places.

@ghost
Copy link
Author

ghost commented Oct 8, 2021

I've edited my original post, and removed some of the originally suggested commands, (moving between siblings) can be already done with existing commands.

@ghost ghost changed the title Folding code: more advanced ways to do it Folding code: 2 more commands Oct 8, 2021
@intact
Copy link

intact commented Oct 8, 2021

I always wanted (un)fold recursively in Geany and after checking Geany's source code i just discovered, that Geany already supports that folding actions.

So probably it would be nice to have menu items for Fold, Unfold, Toggle Fold, Fold Recursively, Unfold Recursively, Fold All and Unfold All, because all actions are currently supported (but eg toggle only using keybinding and recursive (un)fold only using mouse + configuration +/- shift key).

@ghost
Copy link
Author

ghost commented Oct 8, 2021

I just read a couple articles about folding in https://www.scintilla.org/ScintillaDoc.html, in particular https://www.scintilla.org/Lexer.txt, and I see that my notion of "level" in the 1st comment agrees with "fold level" scintilla and lexers uses internally to give each line.
So then the command to move to parent of comment node could be stated as:

@intact In the initial version of the post I suggested:
(EDIT: to which I reversed now!)
CO1: a command that combined moving to parent node and folding that, and
CO2: an opposite command that unfolds a parent node and moves to Ist child node.
But for modularity, later I thought perhaps simpler to implement just the moving to parent command, and then the combined ones could be made from C1, existing "toggle current fold" command and moving with keys.
Also, maybe some users would want to move to parent node, repeatedly, but without simultaneously folding it ?

EDIT: Probably not worth providing a separate command just for moving to parent (can be done with toggle + arrow up/down). The combined versions I believe will ultimately be more useful.

@ghost ghost changed the title Folding code: 2 more commands Folding code: more advanced commands Oct 9, 2021
@ghost
Copy link
Author

ghost commented Oct 9, 2021

A few more thoughts:

CO1 and CO2, (moving to parent/child + fold/unfold) , to be better suited for repeated manual invocation, probably need to be based not on "toggle current fold" but "fold current node (line)" and "unfold current node (line)", each with no effect if line is not a folding point. And moving to parent node, or child node , should ideally place the cursor at a regular position in the line ( I guess Ist non-white space char).

C3, C4 ( folding, and unfolding, of all nodes in the file, of same level as current node all descendants of current node ) should also be based on "fold current node (line)" and "unfold current node (line)" , instead of "toggle current fold", to ensure that after the invocation, each node will be folded or unfolded to the same depth, regardless of the initial state (folded or not).
These C3 and C4 could be stanalone, or (perhaps even better ) be based on CO1 and CO2, via an added switch: move to parent node (to Ist child for C4) and fold (unfold for C4) that and also all other nodes in the file at the same level. Will allow to quickly expand/collapse all trees, progressively, by level, relative to level of current node(line).

@ghost
Copy link
Author

ghost commented Oct 9, 2021

Here's a first draft for CO1, and CO2, based on https://www.scintilla.org/ScintillaDoc.html, especially https://www.scintilla.org/ScintillaDoc.html#Folding.
But I've never contributed code to online projects before, and I dont' know where and how this would be integrated in Geany.
So any advice/info is welcome to make this more fun :)

#include "Scintilla.h"			//for flags
#include "ScintillaTypes.h"		// for 'line' and 'position' types
#include "ScintillaMessages.h"

/* if current line has a parent line, then move cursor to it and fold it (hide its children lines).
 * otherwise (no parents), then effect will be folding current line
 */
void moveToParentAndFold()
{
	line currentLine = SCI_LINEFROMPOSITION(SCI_GETCURRENTPOS);

	line parentLine= SCI_GETFOLDPARENT(currentLine);
	// if there is no parent, then will fold current line
	if (parentLine == -1)
		parentLine=currentLine;

	//set caret at first non-whitespace char in parentLine
	SCI_GOTOPOS(SCI_GETLINEINDENTPOSITION(parentLine) );

	SCI_FOLDLINE(parentLine, SC_FOLDACTION_CONTRACT);
}

/* if current line is a parent, unfold it and move cursor to Ist child line.
 */
void unfoldAndMoveToChild()
{
	line currentLine = SCI_LINEFROMPOSITION(SCI_GETCURRENTPOS);

	// if it's parent line (fold point)
	if( SCI_GETFOLDLEVEL(currentLine) & SC_FOLDLEVELHEADERFLAG )
	{
		SCI_FOLDLINE(currentLine, SC_FOLDACTION_EXPAND );

		// find Ist child line, taken to be Ist non-blank line after current line
		while( SCI_GETFOLDLEVEL(currentLine + 1) & SC_FOLDLEVELWHITEFLAG )
			currentLine++ ;

		SCI_GOTOPOS(SCI_GETLINEINDENTPOSITION( currentLine+1 ) );
	}
}

EDIT: it looks like folding happens here: https://github.com/geany/geany/blob/master/src/editor.c#L4360 . So I guess I can continue preparing such drafts for C3, C4, and then, perhaps with someone's help, change them to be similar to those in editor.c.

@ghost
Copy link
Author

ghost commented Oct 9, 2021

Since the primary motivation for folding is to prevent visual information overload, I think there's not much value in expanding nodes, at same level, across _all trees _ in the entire file. (By the way, for the same reason, I'd choose to have all my files open by default with all nodes folded. )
So that's why I made the changes above to C3 and C4, to be able to fold/unfold all nodes locally, belonging to same parent node/subtree (As complement to fold/unfold at whole file level, and individual node level).

@intact , what did you have in mind with Fold Recursively, Unfold Recursively ?

@intact
Copy link

intact commented Oct 9, 2021

Fold All folds all nodes and subnodes in document. I can click on folding icon and unpack that node and subnodes stay folded. If i use Shift + click, i can unfold subnodes too (or Shift key may unfold only one level, depends on "Editor / Features / Fold/unfold all children of a fold point" configuration).

So (Un)Fold is for (un)folding one level and (Un)Fold Recursively is for (un)folding subnodes too.

Btw looks like "Toggle current fold" use that "Fold/unfold all children of a fold point" configuration too.

@ghost
Copy link
Author

ghost commented Oct 10, 2021

Btw looks like "Toggle current fold" use that "Fold/unfold all children of a fold point" configuration too.

Right; except the Shift switch doesn't work to change the behavior of the "Toggle current fold" command.

So then your (un)fold recursive is almost the same as what I got to in C3/C4. With just one difference: to apply to descendants (= to children, recursively) only, not to parent node.
Here's why it may be better:
when you come to a parent node:

  1. if it's initially closed, your first action will probably be just to toggle it open. You wouldn't want to open it and its children recursively, as that will reveal too much infor at once.
  2. Now once you opened it, if children happen to be closed, that's fine, you can select one of them to explore (in particular with CO1/CO2 if want). If you really want to pop them all open at once, then the Unfold All descendants I suggest still does the job.
    But if all/most chilren are already opened (let alone if their descendants too), you'll want to close them (too much info at once!). So here the Fold All Descendants will come in very handy: will fold the children of children, but you'll still see curent (parent) and its children.
  3. If instead the node is initially open... see 2.

(This formulation also logically agrees with what (Un)Fold All does, if imagine that the ancestor node of all nodes in file is the file itself..)

So we have 3 existing, + 4 new suggested.
In addition, we could also include a command to open/unfold All nodes of level 0, and another to close/fold them; then another to (un)fold all of level 1... - just b/c we're at it...
Though I'm personally not convinced these fixed-level of all nodes are that useful; or at least not useful beyond Ist level (b/c usually you dont' want to see too much at once, and you'll work in a local region anyway, where toggle current and/or (Un)Fold Descendants and CO1/CO2 will do a better job ).

EDIT: an alternative command, that generalizes (and replaces) existing (un)fold All could remove the need for (un)fold all lines of fixed level last mentioned, and for C3/C4 , and also be used for similar purpose as the (un)fold Recursive is:

  • Fold/Unfold all sibling lines, at same level as the current line, and their descendants.

When cursor is on a 0-level line, it coincides with what (un)fold All does. But if cursor on a 1-level line, it (un)folds only the sibling lines (and their descendants), inside current fold region, not whole file.
This also works better with the CO1/CO2 (unfold & move in / fold & move out), as after CO1, if you see too much, you apply this Fold all Siblings Recursively, and then continue to explore.

@ghost
Copy link
Author

ghost commented Oct 11, 2021

I'll have to leave others to take it from here, as I found out windows32 bit OS (which unfortunately I have) won't be supported any more.
Maybe I'll come back to this after I install Linux.

@PthDE
Copy link

PthDE commented Nov 11, 2023

I'm a simple hobbyist, enjoying Geany as my main editor when coding for use under the Arduino IDE.
When both applications run simultaneously, Geany correctly detects an opened file being changed by another application, for example due to edits made and saved via the Arduino IDE.
Re-loading the file in Geany causes all code-folding that was previously set, to be lost. It's a small irritation given the many features Geany that offers, but of course "another nice to have".
Many thanks

@elextr
Copy link
Member

elextr commented Nov 11, 2023

@PthDE IIRC one of the plugins might remember folding, but how good (or not) it is if the folds have changed I don't know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants