Custom folding in a custom lexer with LEX_CONTAINER Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
TobiasA
Knows some wx things
Knows some wx things
Posts: 38
Joined: Mon Aug 28, 2017 8:42 am

Custom folding in a custom lexer with LEX_CONTAINER

Post by TobiasA » Wed Sep 27, 2017 10:44 am

Hi,

has anyone here working with custom folding in a custom lexer? I have a wxstyledtextctrl which is highlighted by a custom lexer to highlight and parse NC cycles.
There's a bit more than actually highlighting the code, now I'm trying hard to integrate custom folding.
When I switch to a CPP lexer, the folding works so the stc itself is setup to support folding. Margins work, slot works. I followed a nice example in the wiki section.
But whenever I add my custom folding, I don't get any markers (except in some cases whenever I hardcode-it).
This is the code I'm trying to fold:

Code: Select all

;Foldtest
DEF BOOL TESTVAR=FALSE
IF (TESTVAR)
  G0 G54 M0 ;doesn't make sense but who cares
ENDIF
M30

Comments are specified by ";" the part with "IF...ENDIF" should have been foldable.
Whenever "styleneeded" is called, the actual fold level is calculated for that range. I also tried to parse the whole program in a background thread and apply this when calculation is finished in an OnUpdate Event but I think it should be called by the Styleneeded event for better performance. It works fine by starting at the fold level that is applied to the line before that range.
In this case, the following fold levels are computed and set:
Line level flag
0 0 1024
1 0 1024
2 1 9217 (level 1 (1025) + wxSTC_FOLDLEVELHEADERFLAG )
3 1 1025
4 0 1024 (level 0 (1024)
5 0 1024
6 0 5120 (level 0 (1024) + wxSTC_FOLDLEVELWHITEFLAG) (this is the last, empty line)

I set these with:

Code: Select all

this->m_activeSTC->SetFoldLevel(i,foldflag)
for each line where i is the line number and foldflag ist the flag as described above.
So, the basic work is done, it should fold. But apparently I am missing something. Something that has to do with the lexer (me) because it works with the CPP lexer and the usual {} brackets whenever I switch the lexer from SetLexer(wxSTC_LEX_CONTAINER) to SetLexer(wxSTC_LEX_CPP) and insert brackets.
I can't find it in the documentation though and after the examples I have with scintilla folding examples, this should work.
But no symbols are displayed in the margin. I have some fear that I might have to set these by my own, but I didn't find the right piece of code yet.

Any help or link to the right documentation is greatly appreciated.

Thanks!

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 4043
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by ONEEYEMAN » Wed Sep 27, 2017 1:38 pm

Hi,
What is your wx version?
AFAIR, custome lexer is available in Git master only.

Thank you.

TobiasA
Knows some wx things
Knows some wx things
Posts: 38
Joined: Mon Aug 28, 2017 8:42 am

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by TobiasA » Wed Sep 27, 2017 1:56 pm

It's not really that kind of a lexer. I am doing all the things a lexer does in my GUI, using the events by LEX_CONTAINER which technically is a custom lexer.
It is just not included in the framework as a lexer.
Sorry and please correct me if this is the wrong term, English is not my first language.
I'm on 3.0.3 stable but consider switching to 3.1.0.

TobiasA
Knows some wx things
Knows some wx things
Posts: 38
Joined: Mon Aug 28, 2017 8:42 am

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by TobiasA » Wed Sep 27, 2017 2:14 pm

The basics do work, I already included syntax highlighting based on the events provided by the container and it works great.
So the lexer itself can't have any major problems, it is just the folding that won't work.

New Pagodi
Super wx Problem Solver
Super wx Problem Solver
Posts: 342
Joined: Tue Jun 20, 2006 6:47 pm
Contact:

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by New Pagodi » Wed Sep 27, 2017 4:19 pm

Folding is somewhat complicated. Here's a sample that shows how to set up the folding you described above. I'm using margin 2 for the fold margin, but you can use any margin you want.

Code: Select all

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include <wx/stc/stc.h>

#define MY_FOLDMARGIN 2

class myFrame : public wxFrame
{
    public:
        myFrame( wxWindow* parent, int id = wxID_ANY, wxString title = "Demo"
                , wxPoint pos = wxDefaultPosition, wxSize size = wxSize(481,466)
                , int style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
    private:
        void onMarginClick(wxStyledTextEvent& event);
        wxStyledTextCtrl* m_activeSTC;
};


myFrame::myFrame( wxWindow* parent, int id, wxString title, wxPoint pos
                 , wxSize size, int style )
        :wxFrame( parent, id, title, pos, size, style )
{
    m_activeSTC = new wxStyledTextCtrl(this);

    //Set the fold marging to have a width of 14 pixels and give it a
    //distinctive background
    m_activeSTC->SetMarginWidth(MY_FOLDMARGIN,14);
    m_activeSTC->SetMarginMask(MY_FOLDMARGIN,wxSTC_MASK_FOLDERS);
    m_activeSTC->SetFoldMarginColour(true,wxColor(255,255,255));
    m_activeSTC->SetFoldMarginHiColour(true,wxColor(233,233,233));

    //Set up the markers that will be shown in the fold margin
    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDEREND,wxSTC_MARK_BOXPLUSCONNECTED);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDEREND,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDEREND,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDEROPENMID,wxSTC_MARK_BOXMINUSCONNECTED);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDEROPENMID,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDEROPENMID,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDERMIDTAIL, wxSTC_MARK_TCORNER);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDERMIDTAIL,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDERMIDTAIL,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDERTAIL,wxSTC_MARK_LCORNER);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDERTAIL,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDERTAIL,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDERSUB,wxSTC_MARK_VLINE);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDERSUB,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDERSUB,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDER,wxSTC_MARK_BOXPLUS);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDER,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDER,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDEROPEN,wxSTC_MARK_BOXMINUS);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDEROPEN,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDEROPEN,wxColor(128,128,128));

    //Turn the fold markers red when the caret is a line in the group (optional)
    m_activeSTC->MarkerEnableHighlight(true);

    //The margin will only respond to clicks if it set sensitive.  Also, connect
    //the event handler that will do the collapsing/restoring
    m_activeSTC->SetMarginSensitive(MY_FOLDMARGIN,true);
    m_activeSTC->Bind(wxEVT_STC_MARGINCLICK, &myFrame::onMarginClick, this);

    //enter some text and set fold levels
    m_activeSTC->AppendText(";Foldtest\n");
    m_activeSTC->AppendText("DEF BOOL TESTVAR=FALSE\n");
    m_activeSTC->AppendText("IF (TESTVAR)\n");
    m_activeSTC->AppendText("  G0 G54 M0 ;doesn't make sense but who cares\n");
    m_activeSTC->AppendText("ENDIF\n");
    m_activeSTC->AppendText("M30");

    m_activeSTC->SetFoldLevel(0, 1024);
    m_activeSTC->SetFoldLevel(1, 1024);
    m_activeSTC->SetFoldLevel(2, 1024|wxSTC_FOLDLEVELHEADERFLAG);
    m_activeSTC->SetFoldLevel(3, 1025);
    m_activeSTC->SetFoldLevel(4, 1025);
    m_activeSTC->SetFoldLevel(5, 1024);
    m_activeSTC->SetFoldLevel(6, 1024|wxSTC_FOLDLEVELWHITEFLAG);
}

void myFrame::onMarginClick(wxStyledTextEvent& event)
{
    int margin   = event.GetMargin();
    int position = event.GetPosition();

    int  line        = m_activeSTC->LineFromPosition(position);

    int  foldLevel  = m_activeSTC->GetFoldLevel(line);
    bool headerFlag = (foldLevel & wxSTC_FOLDLEVELHEADERFLAG)!=0;

    if( margin==MY_FOLDMARGIN && headerFlag )
    {
        m_activeSTC->ToggleFold(line);
    }
}

class myApp : public wxApp
{
    public:
        virtual bool OnInit()
        {
            myFrame* frame = new myFrame(NULL);
            frame->Show();
            return true;
        }
};

wxIMPLEMENT_APP(myApp);

TobiasA
Knows some wx things
Knows some wx things
Posts: 38
Joined: Mon Aug 28, 2017 8:42 am

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by TobiasA » Thu Sep 28, 2017 7:12 am

New Pagodi wrote:Folding is somewhat complicated. Here's a sample that shows how to set up the folding you described above. I'm using margin 2 for the fold margin, but you can use any margin you want.

Code: Select all

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include <wx/stc/stc.h>

#define MY_FOLDMARGIN 2

class myFrame : public wxFrame
{
    public:
        myFrame( wxWindow* parent, int id = wxID_ANY, wxString title = "Demo"
                , wxPoint pos = wxDefaultPosition, wxSize size = wxSize(481,466)
                , int style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
    private:
        void onMarginClick(wxStyledTextEvent& event);
        wxStyledTextCtrl* m_activeSTC;
};


myFrame::myFrame( wxWindow* parent, int id, wxString title, wxPoint pos
                 , wxSize size, int style )
        :wxFrame( parent, id, title, pos, size, style )
{
    m_activeSTC = new wxStyledTextCtrl(this);

    //Set the fold marging to have a width of 14 pixels and give it a
    //distinctive background
    m_activeSTC->SetMarginWidth(MY_FOLDMARGIN,14);
    m_activeSTC->SetMarginMask(MY_FOLDMARGIN,wxSTC_MASK_FOLDERS);
    m_activeSTC->SetFoldMarginColour(true,wxColor(255,255,255));
    m_activeSTC->SetFoldMarginHiColour(true,wxColor(233,233,233));

    //Set up the markers that will be shown in the fold margin
    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDEREND,wxSTC_MARK_BOXPLUSCONNECTED);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDEREND,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDEREND,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDEROPENMID,wxSTC_MARK_BOXMINUSCONNECTED);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDEROPENMID,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDEROPENMID,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDERMIDTAIL, wxSTC_MARK_TCORNER);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDERMIDTAIL,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDERMIDTAIL,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDERTAIL,wxSTC_MARK_LCORNER);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDERTAIL,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDERTAIL,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDERSUB,wxSTC_MARK_VLINE);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDERSUB,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDERSUB,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDER,wxSTC_MARK_BOXPLUS);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDER,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDER,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDEROPEN,wxSTC_MARK_BOXMINUS);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDEROPEN,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDEROPEN,wxColor(128,128,128));

    //Turn the fold markers red when the caret is a line in the group (optional)
    m_activeSTC->MarkerEnableHighlight(true);

    //The margin will only respond to clicks if it set sensitive.  Also, connect
    //the event handler that will do the collapsing/restoring
    m_activeSTC->SetMarginSensitive(MY_FOLDMARGIN,true);
    m_activeSTC->Bind(wxEVT_STC_MARGINCLICK, &myFrame::onMarginClick, this);

    //enter some text and set fold levels
    m_activeSTC->AppendText(";Foldtest\n");
    m_activeSTC->AppendText("DEF BOOL TESTVAR=FALSE\n");
    m_activeSTC->AppendText("IF (TESTVAR)\n");
    m_activeSTC->AppendText("  G0 G54 M0 ;doesn't make sense but who cares\n");
    m_activeSTC->AppendText("ENDIF\n");
    m_activeSTC->AppendText("M30");

    m_activeSTC->SetFoldLevel(0, 1024);
    m_activeSTC->SetFoldLevel(1, 1024);
    m_activeSTC->SetFoldLevel(2, 1024|wxSTC_FOLDLEVELHEADERFLAG);
    m_activeSTC->SetFoldLevel(3, 1025);
    m_activeSTC->SetFoldLevel(4, 1025);
    m_activeSTC->SetFoldLevel(5, 1024);
    m_activeSTC->SetFoldLevel(6, 1024|wxSTC_FOLDLEVELWHITEFLAG);
}

void myFrame::onMarginClick(wxStyledTextEvent& event)
{
    int margin   = event.GetMargin();
    int position = event.GetPosition();

    int  line        = m_activeSTC->LineFromPosition(position);

    int  foldLevel  = m_activeSTC->GetFoldLevel(line);
    bool headerFlag = (foldLevel & wxSTC_FOLDLEVELHEADERFLAG)!=0;

    if( margin==MY_FOLDMARGIN && headerFlag )
    {
        m_activeSTC->ToggleFold(line);
    }
}

class myApp : public wxApp
{
    public:
        virtual bool OnInit()
        {
            myFrame* frame = new myFrame(NULL);
            frame->Show();
            return true;
        }
};

wxIMPLEMENT_APP(myApp);
This example should make it into some wiki or FAQ article. My fault was the header flag- I did set the header flag on the first line with the new fold level which is wrong. I assumed the whole "fold part" would receive the higher fold level. It does make sense though since you want the header line to remain visible. The line containing the "IF" to increase folding depth remains on the previous fold level and receives the wxSTC_FOLDLEVELHEADERFLAG. After fixing this, it worked flawlessly.

Thank you!

User avatar
evstevemd
Part Of The Furniture
Part Of The Furniture
Posts: 2292
Joined: Wed Jan 28, 2009 11:57 am
Location: United Republic of Tanzania
Contact:

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by evstevemd » Thu Sep 28, 2017 7:33 am

TobiasA wrote: This example should make it into some wiki or FAQ article.
I believe your whole experience would merit a Wiki article. If you can document how to add custom lexer using LEX_CONTAINER would be great.
Actually I think it should be added to Intro to wxSTC in official documentation
Chief Justice: We have trouble dear citizens!
Citizens: What it is his honor?
Chief Justice:Our president is an atheist, who will he swear to?
[Ubuntu 19.04/Windows 10 Pro/MacOS 10.13 - GCC/MinGW/Clang, CodeLite IDE]

TobiasA
Knows some wx things
Knows some wx things
Posts: 38
Joined: Mon Aug 28, 2017 8:42 am

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by TobiasA » Thu Sep 28, 2017 10:36 am

evstevemd wrote:
TobiasA wrote: This example should make it into some wiki or FAQ article.
I believe your whole experience would merit a Wiki article. If you can document how to add custom lexer using LEX_CONTAINER would be great.
Actually I think it should be added to Intro to wxSTC in official documentation
This sounds like a good idea. I would spare out my parse routine since it is somewhat special but explain how to write one or maybe give an example. Mine is rather complex, a simpler one would be easier to understand.
I'll try to put up a simple example.
It's not as hard as it sounds though, pretty much all you need is the styleneeded event. The hard part is to find the right keywords and positions :lol:

Will be pretty busy the next days though.

User avatar
evstevemd
Part Of The Furniture
Part Of The Furniture
Posts: 2292
Joined: Wed Jan 28, 2009 11:57 am
Location: United Republic of Tanzania
Contact:

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by evstevemd » Thu Sep 28, 2017 10:45 am

TobiasA wrote: This sounds like a good idea. I would spare out my parse routine since it is somewhat special but explain how to write one or maybe give an example. Mine is rather complex, a simpler one would be easier to understand.
I'll try to put up a simple example.
It's not as hard as it sounds though, pretty much all you need is the styleneeded event. The hard part is to find the right keywords and positions :lol:

Will be pretty busy the next days though.
That will be nice! Don't forget to comment here with a link when you write it.
All the best!
Chief Justice: We have trouble dear citizens!
Citizens: What it is his honor?
Chief Justice:Our president is an atheist, who will he swear to?
[Ubuntu 19.04/Windows 10 Pro/MacOS 10.13 - GCC/MinGW/Clang, CodeLite IDE]

TobiasA
Knows some wx things
Knows some wx things
Posts: 38
Joined: Mon Aug 28, 2017 8:42 am

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by TobiasA » Fri Sep 29, 2017 5:32 pm

Well... Seems I have to test some more. I found out that relying on styleneeded for the folding is not that relyable because you might have to change the line in front of it...
I ended up in pulling the fold levels in a background threat and apply them during the next wxEVT_STC_UPDATEUI event. I have some heavier workload in that background threat like parsing used variables- it now also calculates fold levels.

I really should have started with a basic example :lol:

Another question: I have never written a wiki yet- am I even allowed to do so? I think I'll just reply to that thread here.

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 4043
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by ONEEYEMAN » Fri Sep 29, 2017 11:30 pm

Hi,
Wiki is actually for everybody to change/edit.
For wxWidgets you should get registered in order to prevent SPAM. I am not sure if you can use wxForum credentials.

Thank you.
'

User avatar
evstevemd
Part Of The Furniture
Part Of The Furniture
Posts: 2292
Joined: Wed Jan 28, 2009 11:57 am
Location: United Republic of Tanzania
Contact:

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by evstevemd » Sat Sep 30, 2017 2:53 pm

Just to add to what ONEEYEMAN have said, if you hit registration issue, the admin for wxWidgets site (and so the Wiki) is on wx-user/wx-dev mailing list, so post there and he will definitely help!
Chief Justice: We have trouble dear citizens!
Citizens: What it is his honor?
Chief Justice:Our president is an atheist, who will he swear to?
[Ubuntu 19.04/Windows 10 Pro/MacOS 10.13 - GCC/MinGW/Clang, CodeLite IDE]

TobiasA
Knows some wx things
Knows some wx things
Posts: 38
Joined: Mon Aug 28, 2017 8:42 am

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by TobiasA » Mon Oct 02, 2017 12:52 pm

To throw in some code... The following code seems to work as an example:

Code: Select all

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include <wx/stc/stc.h>
#include <vector>
#include <utility>
#include <algorithm>

#define MY_FOLDMARGIN 2

class myFrame : public wxFrame {
    //be sure to enable the C++11 standard for this example if you're using GCC
public:
    myFrame( wxWindow* parent, int id = wxID_ANY, wxString title = "Demo"
             , wxPoint pos = wxDefaultPosition, wxSize size = wxSize(481,466)
                     , int style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
private:
    void onMarginClick(wxStyledTextEvent& event);
    void OnStyleNeeded(wxStyledTextEvent& event);
    void highlightSTCsyntax(size_t fromPos,size_t toPos,wxString &text);
    void setfoldlevels(size_t fromPos,int startfoldlevel,wxString &text);
    wxStyledTextCtrl* m_activeSTC;
    wxColor m_GCodecolor{255,130,0}; //color for highlighted parts
    //this is the mask for styling- just remember to set this to 0 with >3.1.0
    int m_stylemask=255; //for wxwidgets >3.1.0 set this to 0
};


myFrame::myFrame( wxWindow* parent, int id, wxString title, wxPoint pos
                  , wxSize size, int style )
    :wxFrame( parent, id, title, pos, size, style ) {
    m_activeSTC = new wxStyledTextCtrl(this);

    //set Lexer to LEX_CONTAINER: This will trigger the styleneeded event so you can do your own highlighting
    m_activeSTC->SetLexer(wxSTC_LEX_CONTAINER);

    //folding example by New Pagodi copied from WxWidgets Forum
    //Set the fold marging to have a width of 14 pixels and give it a
    //distinctive background
    m_activeSTC->SetMarginWidth(MY_FOLDMARGIN,14);
    m_activeSTC->SetMarginMask(MY_FOLDMARGIN,wxSTC_MASK_FOLDERS);
    m_activeSTC->SetFoldMarginColour(true,wxColor(255,255,255));
    m_activeSTC->SetFoldMarginHiColour(true,wxColor(233,233,233));

    //Set up the markers that will be shown in the fold margin
    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDEREND,wxSTC_MARK_BOXPLUSCONNECTED);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDEREND,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDEREND,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDEROPENMID,wxSTC_MARK_BOXMINUSCONNECTED);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDEROPENMID,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDEROPENMID,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDERMIDTAIL, wxSTC_MARK_TCORNER);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDERMIDTAIL,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDERMIDTAIL,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDERTAIL,wxSTC_MARK_LCORNER);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDERTAIL,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDERTAIL,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDERSUB,wxSTC_MARK_VLINE);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDERSUB,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDERSUB,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDER,wxSTC_MARK_BOXPLUS);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDER,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDER,wxColor(128,128,128));

    m_activeSTC->MarkerDefine(wxSTC_MARKNUM_FOLDEROPEN,wxSTC_MARK_BOXMINUS);
    m_activeSTC->MarkerSetForeground(wxSTC_MARKNUM_FOLDEROPEN,wxColor(243,243,243));
    m_activeSTC->MarkerSetBackground(wxSTC_MARKNUM_FOLDEROPEN,wxColor(128,128,128));

    //Turn the fold markers red when the caret is a line in the group (optional)
    m_activeSTC->MarkerEnableHighlight(true);

    //The margin will only respond to clicks if it set sensitive.  Also, connect
    //the event handler that will do the collapsing/restoring
    m_activeSTC->SetMarginSensitive(MY_FOLDMARGIN,true);
    m_activeSTC->Bind(wxEVT_STC_MARGINCLICK, &myFrame::onMarginClick, this);
    m_activeSTC->Bind(wxEVT_STC_STYLENEEDED, &myFrame::OnStyleNeeded, this);

    //set color for G-Code highlighting
    m_activeSTC->StyleSetForeground(19,m_GCodecolor);

    //enter some text and set fold levels
    m_activeSTC->AppendText(";Foldtest\n");
    m_activeSTC->AppendText("DEF BOOL TESTVAR=FALSE\n");
    m_activeSTC->AppendText("IF (TESTVAR)\n");
    m_activeSTC->AppendText("  G0 G54 M0\n");
    m_activeSTC->AppendText("ENDIF\n");
    m_activeSTC->AppendText("M30\n");

    /*given the text above, folding should produce this output:
    m_activeSTC->SetFoldLevel(0, 1024);
    m_activeSTC->SetFoldLevel(1, 1024);
    m_activeSTC->SetFoldLevel(2, 1024|wxSTC_FOLDLEVELHEADERFLAG); //header flag: one item before increasing fold level!
    m_activeSTC->SetFoldLevel(3, 1025);  //here comes the new fold level in line G0 G54 M0
    m_activeSTC->SetFoldLevel(4, 1025);  //the ENDIF
    m_activeSTC->SetFoldLevel(5, 1024);  //and this has the lower fold level again
    m_activeSTC->SetFoldLevel(6, 1024|wxSTC_FOLDLEVELWHITEFLAG); //this is an empty line: set fold level white flag
    */

    /*note: If you load text into the styled text control for example with file load or like we did above:
    style the whole document for once. If you don't, the document remains unstyled until you click some position below*/
    wxString text=m_activeSTC->GetText().Upper(); //Upper() makes it case insensitive
    this->highlightSTCsyntax(0,m_activeSTC->GetTextLength(),text);
    this->setfoldlevels(0,0,text);
}

void myFrame::onMarginClick(wxStyledTextEvent& event) {
    int margin   = event.GetMargin();
    int position = event.GetPosition();

    int  line        = m_activeSTC->LineFromPosition(position);

    int  foldLevel  = m_activeSTC->GetFoldLevel(line);
    bool headerFlag = (foldLevel & wxSTC_FOLDLEVELHEADERFLAG)!=0;

    if( margin==MY_FOLDMARGIN && headerFlag ) {
        m_activeSTC->ToggleFold(line);
    }
}

void myFrame::OnStyleNeeded(wxStyledTextEvent& event) {
    /*this is called every time the styler detects a line that needs style, so we style that range.
    This will save a lot of performance since we only style text when needed instead of parsing the whole file every time.*/
    size_t line_end=m_activeSTC->LineFromPosition(m_activeSTC->GetCurrentPos());
    size_t line_start=m_activeSTC->LineFromPosition(m_activeSTC->GetEndStyled());
    /*fold level: May need to include the two lines in front because of the fold level these lines have- the line above
    may be affected*/
    if(line_start>1) {
        line_start-=2;
    } else {
        line_start=0;
    }
    if(line_end<line_start) {
        //that happens when you select parts that are in front of the styled area
        line_end=line_start;
    }
    //style the line following the style area too (if present) in case fold level decreases in that one
    if(line_end<m_activeSTC->GetLineCount()-1){
        line_end++;
    }
    //get exact start positions
    size_t startpos=m_activeSTC->PositionFromLine(line_start);
    size_t endpos=(m_activeSTC->GetLineEndPosition(line_end));
    int startfoldlevel=m_activeSTC->GetFoldLevel(line_start);
    startfoldlevel &= wxSTC_FOLDFLAG_LEVELNUMBERS; //mask out the flags and only use the fold level
    wxString text=m_activeSTC->GetTextRange(startpos,endpos).Upper();
    this->highlightSTCsyntax(startpos,endpos,text);
    //calculate and apply foldings
    this->setfoldlevels(startpos,startfoldlevel,text);
}

void myFrame::highlightSTCsyntax(size_t fromPos,size_t toPos, wxString &text) {
    //this vector will hold the start and end position of each word to highlight
    //if you want to highlight more than one, you should pass a whole class or struct containing the offsets
    std::vector<std::pair<size_t,size_t>>GcodeVector;
    //the following example is a quick and dirty parser for G-Codes.
    //it just iterates through the Text Range and finds "Gxx" where xx is a digit.
    //you could also use regex, but one can build a pretty fast routine based on single char evaluation
    size_t actual_cursorpos = 0;
    size_t startpos = 0;
    size_t end_of_text = text.length();
    bool word_boundary = true; //check for word boundary
    char actualchar;
    while (actual_cursorpos<end_of_text) {
        actualchar= text[actual_cursorpos];
        //check if syntax matches "G" followed by a couple of numbers
        if((actualchar=='G')&&(word_boundary==true)) {
            //this is a new G-Code, store startposition
            startpos=actual_cursorpos;
            word_boundary=false;
            actual_cursorpos++;
            if(actual_cursorpos<end_of_text) {
                //refresh actual character
                actualchar= text[actual_cursorpos];
            }
            //add digits
            while((std::isdigit(actualchar)&&(actual_cursorpos<end_of_text))) {
                actual_cursorpos++;
                actualchar= text[actual_cursorpos];
            }
            //check if word boundary occurs at end of digits
            if((actualchar==' ')||(actualchar=='\n')||(actualchar=='\r')||(actualchar=='\t')||(actual_cursorpos==end_of_text)) {
                //success, append this one
                if((actual_cursorpos-startpos)>1) {
                    //success, append to vector. DO NOT FORGET THE OFFSET HERE! We start from fromPos, so we need to add this
                    GcodeVector.push_back(std::make_pair(startpos+fromPos, actual_cursorpos+fromPos));
                }
                word_boundary=true;
            }
        }
        if((actualchar==' ')||(actualchar=='\n')||(actualchar=='\r')||(actualchar=='\t')||(actual_cursorpos==end_of_text)) {
            word_boundary=true;
        }
        actual_cursorpos++;
    }
    //remove old styling
    m_activeSTC->StartStyling(fromPos,m_stylemask); //from here
    m_activeSTC->SetStyling(toPos-fromPos,0); //with that length and style -> cleared
    //now style the G-Codes
    for (int i=0; i<GcodeVector.size(); i++) {
        size_t startpos=GcodeVector[i].first;
        size_t endpos=GcodeVector[i].second;
        size_t length=(endpos-startpos);
        m_activeSTC->StartStyling(startpos,m_stylemask);
        m_activeSTC->SetStyling(length,19); //must match the style set above
    }
}

void myFrame::setfoldlevels(size_t fromPos, int startfoldlevel, wxString& text) {
    /*we'll increase the fold level with "IF" and decrease it with "ENDIF".
    First, find all "IF" included in the text. Then we check if this IF is actually an ENDIF.
    Keep in mind that you still need to check if this is actually commented out and so on.
    This is a pretty simple and not perfect example to demonstrate basic folding*/
    std::vector<size_t>if_positions;
    size_t actual_cursorpos=0;
    while ((actual_cursorpos<text.size())&&(actual_cursorpos!=wxNOT_FOUND)) {
        actual_cursorpos=text.find("IF",actual_cursorpos+1);
        if(actual_cursorpos!=wxNOT_FOUND) {
            if_positions.push_back(actual_cursorpos+fromPos);
        }
    }
    //build a vector to include line and folding level
    //also, check if this IF is actually an ENDIF
    std::vector<std::pair<size_t,int>>foldingvector;
    int actualfoldlevel=startfoldlevel;
    for(int i=0; i<if_positions.size(); i++) {
        size_t this_line=m_activeSTC->LineFromPosition(if_positions[i]);
        //check if that "IF" is an ENDIF
        wxString endif_string;
        if(if_positions[i]>3) {
            endif_string=text.substr(if_positions[i]-3-fromPos,5);
        }
        //if it's an IF the fold level increases, if it's an ENDIF the foldlevel decreases
        if(endif_string=="ENDIF") {
            actualfoldlevel--;
            foldingvector.push_back(std::make_pair(this_line,actualfoldlevel));
        } else {
            actualfoldlevel++;
            foldingvector.push_back(std::make_pair(this_line,actualfoldlevel));
        }
    }
    //now that we know which lines shall influence folding, we can apply to folding level to the STC line for line
    int foldlevel=startfoldlevel; //this is a temporary marker containing the foldlevel of that position
    size_t vectorcount=0;
    //get positions from line from start and end
    size_t startline=m_activeSTC->LineFromPosition(fromPos);
    size_t endline=m_activeSTC->LineFromPosition(fromPos+text.size());
    //set folding for these lines
    for(size_t i=startline; i<=endline; i++) {
        int prevlevel=foldlevel; //previous foldlevel
        int foldflag=foldlevel; //this flag will be applied to the line
        if((foldingvector.size()>0)&&(vectorcount<foldingvector.size())) {
            if(i==foldingvector[vectorcount].first) { //if the fold level changes in that line
                //new foldlevel = foldlevel in that line
                foldlevel=foldingvector[vectorcount].second;
                vectorcount++;
                if(foldlevel>prevlevel) {
                    //when incremented, this line keeps the previous fold level (!) but is marked as a folder level header
                    foldflag= foldflag | wxSTC_FOLDLEVELHEADERFLAG; //incremented, set header flag
                }
            }
        }
        foldflag= foldflag | wxSTC_FOLDLEVELBASE; //add 1024 to the fold level
        if(m_activeSTC->GetLineLength(i)==0) { //if this does not contain any characters, set the white flag
            foldflag= foldflag | wxSTC_FOLDLEVELWHITEFLAG;
        }
        //finally, set fold level to line
        m_activeSTC->SetFoldLevel(i,foldflag);
    }
}

class myApp : public wxApp {
public:
    virtual bool OnInit() {
        myFrame* frame = new myFrame(NULL);
        frame->Show();
        return true;
    }
};

wxIMPLEMENT_APP(myApp);
This will highlight all "Gxx" occurences in the STC in a custom color (where xx is a digit) and apply folding to IF and ENDIF.
the example will fold on everything containing an "IF" but as an example I think it won't matter.
Basically, it is the fold example by New Pagodi with some enhancements.

--> Is it possible to understand what I am doing there? Still, a wiki is more than code- but I'd like to have someone to have a second look since I'm rather new to wxWidgets and have only a year experience of C++. Also, I'm using GCC TDM and things behave different with the VS build tools from time to time.
Thanks!

User avatar
evstevemd
Part Of The Furniture
Part Of The Furniture
Posts: 2292
Joined: Wed Jan 28, 2009 11:57 am
Location: United Republic of Tanzania
Contact:

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by evstevemd » Mon Oct 02, 2017 7:00 pm

Hi,
I will find time to test it with Clang.
As for writing style, the best I can think is explaining each step separately (following the code with related statements explained together) and at the end of it you throw the working code.

And don't worry about shortcomings, we all have them. After all these years with wxWidgets am still learning. So just do your best and may be someone will edit and explain where things aren't clear
Chief Justice: We have trouble dear citizens!
Citizens: What it is his honor?
Chief Justice:Our president is an atheist, who will he swear to?
[Ubuntu 19.04/Windows 10 Pro/MacOS 10.13 - GCC/MinGW/Clang, CodeLite IDE]

TobiasA
Knows some wx things
Knows some wx things
Posts: 38
Joined: Mon Aug 28, 2017 8:42 am

Re: Custom folding in a custom lexer with LEX_CONTAINER

Post by TobiasA » Tue Oct 31, 2017 1:36 pm

This may be a noob question... But how do you create a new article? Is there a short howto on it?
Thank you!

Post Reply