pyQode/pyqode.core — pull request #141 by ColinDuquesnoy (2014-07-29 20:15:28+00:00)
Title: Reintroducing code folding

This pull request (re)adds code folding panel to pyQode and resolves #121.

### v1.x issues
There were two main problem with the pyQode 1.x approach:

1) bad performance issues:

- load time for big files: In the previous API we were storing code folding data in a QTextBlockUserData. Just calling QTextBlock.setUserData takes approximatively 2ms on my dev system (i7 @2.8GHz), multiply that by the number of line to highlight and you get horrible loading times for big files (2s for 10 000 lines of code).

- drawing the folded box next to the triggering lines can be very expensive when you have a lots of them on screen (i.e. when there are a lots of folded blocks, typically in big files).

2) very buggy and dirty implement due to a lack of well defined rules to define trigger position and range and how to handle blank lines.

### Solutions

1) To fix the bad performances we will use QTextBlock.userState (which is nearly instaneaous while UserData take approximatively 2ms per call on my dev machine). I am not sure how I will solve the folded box issue, maybe by simply using a TextDecoration.

2) I took the time to think about what define a foldable code block and how to parse them. We will continue using the notion of fold indent (now called fold level) and detect them during syntax highlighting but the way detect_fold_level is called and the way fold trigger are detected will be done in a different way.

I actually came up with the following rules/ideas:
- **blank lines (stripped) always have the same fold level as the previous block**. This was probably the main issue in the previous implementation. ``detect_fold_level`` won't even be called on blank lines so that you don't have to worry about the blank line issues when implementing code folding for a new
- a **fold trigger is automatically set on the previous block if the current block fold level has increased**
- ``previous_block`` is always the first non blank previous block
- a "foldable" range contains blocks that follow the trigger and have a fold level higher than the trigger fold level. The range stops whenever a block with a fold level lower or equal to the trigger fold level and does not contains the last blank lines.
- folding a node simply consists in marking the block as folded and hiding its "foldable" range.

### Status

This pull request can be decomposed in the folliwing sub-tasks:

- [x] API for detecting fold level
- [x] Detect fold level based on indentation (generic)
- [x] Implement fold detection for pyqode.python
- [ ] Implement fold detection for OpenCobolIDE (to proof to myself that the new API can be used for exotic language such as COBOL where folding is typically harder than for other more classic languages)
- [ ] Add trigger visualisation on panel
- [ ] Add fold range visualisation (I plan to highlight parents fold zones with darker color as it is done in Qt Creator, but make it optional)
- [ ] Add fold/unfold on fold markers
- [ ] Add fold all/unfold all actions
- [ ] Find an alternative to folded boxes which are too slow