Bug #55358 Iterate statement folding does not work
Submitted: 19 Jul 2010 10:48 Modified: 29 Sep 2010 16:48
Reporter: Anders Karlsson Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Workbench: SQL Editor Severity:S3 (Non-critical)
Version:5.2.25 OS:Any (Windows XP, Mac OS X)
Assigned to: Mike Lischke CPU Architecture:Any

[19 Jul 2010 10:48] Anders Karlsson
Description:
Folding of iteration statement blocks, like WHILE, REPEAT and LOOP, does not work properly in the SQL Editor, it seems that the loop end (END WHILE, END REPEAT and END LOOP) are not recognized as expected.

How to repeat:
Open MySQL Workbench. In the SQL Editor, paste the following simple valid code:
<CODE>
drop procedure if exists p1;
delimiter //
create procedure p1()
begin

repeat
select 'foo1';
until true
end repeat;

while false do
select 'foo2';
end while;
select 'bar';

loop
select 'foo5';
end loop;

select 'foo6';
end;
//
</CODE>

If you fold unfold any of the WHILE, REPEAT or LOOP blocks, the rest of the procedure will fold/unfold and not only the statement block in question.
[19 Jul 2010 10:59] Valeriy Kravchuk
Thank you for the bug report.
[19 Jul 2010 12:11] Anders Karlsson
The issue is with how endFound is handled in FoldMySQLDoc in the Scintilla MySQL Parser:

endFound is set at:
                        if (MatchIgnoreCase(styler, i, "end"))
                        {
                          // Multiple "end" in a row are counted multiple times!
                          if (endFound)
                          {
                            levelNext--;
                            if (levelNext < SC_FOLDLEVELBASE)
                              levelNext = SC_FOLDLEVELBASE;
                          }
                          endFound = true;
                          whenFound = false;
                        }

And is then supposed to be picked up when a wile / repeat etc. keyword is found here:
          if (!foldOnlyBegin && endFound && (ifFound || whileFound || loopFound))
          {
            endFound = false;
            levelNext--;
            if (levelNext < SC_FOLDLEVELBASE)
              levelNext = SC_FOLDLEVELBASE;
            
            // Note that "else" is special here. It may or may not be followed by an "if .. then",
            // but in any case the level stays the same. When followed by an "if .. then" the level
            // will be increased later, if not, then at eol.
          }

The problem is that endFound will be reset before it is check above by the block that handles "end" statement with a following while etc. keyword here:
    // Handle the case of a trailing end without an if / while etc, as in the case of a begin.
		if (endFound)
    {
			endFound = false;
			levelNext--;
			if (levelNext < SC_FOLDLEVELBASE)
        levelNext = SC_FOLDLEVELBASE;
		}

The fix should not be too difficult and I just tried one myself, and it seems to work. First I add a variable to flag that the privious word was an "end":
  bool endFound0 = false;
  bool endFound = false;

Then instead of setting endFound, I set endFound0:
                        if (MatchIgnoreCase(styler, i, "end"))
                        {
                          // Multiple "end" in a row are counted multiple times!
                          if (endFound)
                          {
                            levelNext--;
                            if (levelNext < SC_FOLDLEVELBASE)
                              levelNext = SC_FOLDLEVELBASE;
                          }
                          endFound0 = true;
                          whenFound = false;
                        }

And finally, I need to set endFound when I hit a second keyword and endFound0 is set (i.e. the previous keyword was an "end"):
        if (style != stylePrev)
        {
          bool beginFound = MatchIgnoreCase(styler, i, "begin");
          bool ifFound = MatchIgnoreCase(styler, i, "if");
          bool thenFound = MatchIgnoreCase(styler, i, "then");
          bool whileFound = MatchIgnoreCase(styler, i, "while");
          bool loopFound = MatchIgnoreCase(styler, i, "loop");
          bool repeatFound = MatchIgnoreCase(styler, i, "repeat");

// If the previous word was END, then set that. This is to make sure that an END xxx isn't skipped. */
          if(endFound0)
             endFound = true;
          endFound0 = false;

          if (!foldOnlyBegin && endFound && (ifFound || whileFound || loopFound))
[19 Jul 2010 18:26] Anders Karlsson
A bug-report for this has been placed with Scintilla (bug# 3031742).
[5 Aug 2010 12:16] Mike Lischke
Fixed in repository.
[23 Sep 2010 10:37] Johannes Taxacher
fix confirmed in repository
[29 Sep 2010 16:48] Tony Bedford
An entry has been added to the 5.2.29 changelog:

Folding of iteration statement blocks, such as WHILE, REPEAT and LOOP, did not work correctly in the SQL Editor. The loop end constructs such as END WHILE, END REPEAT and END LOOP were not recognized as expected.