Python Script - Fold levels/flags



  • I don’t understand what “editor.getFoldLevel(line)” with a line number as argument will return. With one line number it always returns 9218, with another line number it always changes between 9217<->1025 when I restart notepad++.
    I have searched about this, but I don’t understand it because:
    9218 (dec) = 0010 0100 0000 0010 (bin)
    9217 (dec) = 0010 0100 0000 0001 (bin)
    1025 (dec) = 0000 0100 0000 0001 (bin)
    and the table here only goes up to 128 power.
    I would like to know what those flags mean, how they are calculated/what defines them, specially about that only bit that changes between 9217 and 1025.

    Additionally, there is a FOLDLEVEL.HEADERFLAG that always seems to return “Npp.FOLDLEVEL.HEADERFLAG” when used directly in the console, or “HEADERFLAG” when used in the console as “str(FOLDLEVEL.HEADERFLAG)”.
    I also would like to know more about this function, what it is supposed to return, and what is a Headerflag. The only thing I found about it was this, which means nothing to me.

    Thank you anyone who would like to give a hand.



  • The folding of Scintilla can be a little difficult to understand at first, but luckily the documentation you pointed to on the Scintilla page is adequate.

    So some of this is pretty much repeating what the documentation says, but has some examples using LuaScript. I know you asked specifically about PythonScript but you seem to understand enough that translating between LuaScript and PythonScript shouldn’t be hard as most of the stuff is a one to one mapping. Plus the concepts are exactly the same.

    Fold Levels

    Let’s take for example this C++ code:

    int main(void) {
    	// comment
    
    	if (3 > 10) {
    		printf("nope")
    	}
    	return 0;
    }
    

    Within Notepad++ it looks like this as its fold levels.

    Using editor.getFoldLevel(line) provides information about the line’s folding which includes:

    • The fold level number (in the above example the printf() call is within the 2nd fold level)
    • The fold level header flag…which indicates if it is a fold point (i.e. the boxes that can be folded)
    • The fold level white flag…in all honesty I’m not sure what this does and think it is applicable to certain languages. For now this can be ignored.

    According to the documentation:

    The fold level is a number in the range 0 to SC_FOLDLEVELNUMBERMASK (0x0FFF).

    Which is easy to do, just take the returned value from editor.getFoldLevel(line) and bitwise AND it with SC_FOLDLEVELNUMBERMASK. In PythonScript it would look to be FOLDLEVEL.NUMBERMASK. The documentation also states:

    However, the initial fold level is set to SC_FOLDLEVELBASE (0x400) to allow unsigned arithmetic on folding levels.

    So you need to subtract SC_FOLDLEVELBASE from what you just “AND’ed” off. This will give you the fold number of the line.

    To get the header flag you need to take the fold level and bitwise AND it with SC_FOLDLEVELHEADERFLAG (which this is the highest bit you are seeing in your values you posted above).

    So…a bit of LuaScript to demonstrate what I’ve just described:

    -- Iterate over each line
    for i = 0, editor.LineCount - 1 do
    	-- Get the fold level of the line
    	local foldLevel = editor.FoldLevel[i]
    	
    	-- Calculate the fold number
    	local foldNumber = (foldLevel & SC_FOLDLEVELNUMBERMASK) - SC_FOLDLEVELBASE
    	
    	-- Get the header flag
    	local headerFlag = (foldLevel & SC_FOLDLEVELHEADERFLAG) == SC_FOLDLEVELHEADERFLAG
    
    	print(string.format("Line %d, has a fold level of %d", i + 1, foldNumber))
    	if headerFlag then
    		print("\tand it is a fold point")
    	end
    end
    

    Will output:

        Line 1, has a fold level of 0
        	and it is a fold point
        Line 2, has a fold level of 1
        Line 3, has a fold level of 1
        Line 4, has a fold level of 1
        	and it is a fold point
        Line 5, has a fold level of 2
        Line 6, has a fold level of 2
        Line 7, has a fold level of 1
        Line 8, has a fold level of 1
    

    Once you have this information you can do things like select all the words within the same fold level (great for renaming local variables) or select all the text of a fold level (great for extracting code within a if or for statement and creating a separate function from it).


    A couple of the things you specifically mentioned:

    it always changes between 9217<->1025 when I restart notepad++.

    If the text is the same, then these shouldn’t change at all. Maybe now you know a bit more how the fold level works, you can figure out why this might be different.

    Additionally, there is a FOLDLEVEL.HEADERFLAG that always seems to return “Npp.FOLDLEVEL.HEADERFLAG” when used directly in the console, or “HEADERFLAG” when used in the console as “str(FOLDLEVEL.HEADERFLAG)”.

    These values are all constants so they will always be the same.

    Fold flags

    This is separate from everything described above. This is how the folding is drawn and does not apply per line but rather the entire document. With N++ the default is to draw a line below the text when it is folded. Such as:

    You can do things like set the fold flags to draw above and below the folded lines (again using LuaScript):

    editor.FoldFlags = SC_FOLDFLAG_LINEAFTER_CONTRACTED | SC_FOLDFLAG_LINEBEFORE_CONTRACTED
    

    Then the folding will look like this:



  • Nice post :), thank you, now I understand it!

    Looks like notepad sometimes thinks that the line has a folding box, and other times it thinks it doesn’t, thus the 9217<->1025 change in fold level. The script I’m looking at (from here, which bugs sometimes and I’m trying to debug) is set to run when the document loads and I think that the fold levels are loading yet or something, because I put a few debug lines there telling me the values of the variables

    print "FOLDLEVEL.HEADERFLAG: " + str(int(FOLDLEVEL.HEADERFLAG))
    print "editor.getFoldLevel(" + str(_line) + "): " + str(editor.getFoldLevel(_line))
    print "editor.getFoldLevel(" + str(_line) + ") & FOLDLEVEL.HEADERFLAG: " + str(editor.getFoldLevel(_line) & FOLDLEVEL.HEADERFLAG)
    

    and the output was like this:

    FOLDLEVEL.HEADERFLAG: 8192
    editor.getFoldLevel(54): 1025
    editor.getFoldLevel(54) & FOLDLEVEL.HEADERFLAG: 8192
    

    which should be

    >>> 8192 & 1025
    0
    

    Weird, maybe it had changed from 1025 to 9217 between the two script lines, or something. I will leave the debug code, and next time something doesn’t work I will look at it better. (somehow it is working fine now)

    Do line numbers and fold levels stay the same after hiding some code?

    About the FOLDLEVEL.HEADERFLAG now I know it’s just a constant and I can look at it in the console using “int(FOLDLEVEL.HEADERFLAG)” :)



  • Do line numbers and fold levels stay the same after hiding some code?

    Yes. There are other various API calls provided by Scintilla than can tell you things like if the line is actually folded, or to manually fold/unfold the line and children of that line.


Log in to reply