Search code examples
c++wxwidgetsscintillawxstyledtextctrl

How to control LineIndentation in wxStyledTextCtrl when user presses Enter


When user presses Enter key in wxStyledTextCtrl, it seems that the cursor always goes to beginning of the line (zero indentation), which is most likely the expected behavior.

I want to be able to write Script code with the following format, with line indents.

for i=1,10 do --say there is no indentation
   i=i+1 -- now there is indentation via tab key
   -- pressing enter should proceed with this level of indentation
   print(i) -- same level of indentation with the previous code line
end

I use the following C++ code to be able to control indentation at a very basic level.

void Script::OnKeyUp(wxKeyEvent& evt)
{
    if ((evt.GetKeyCode() == WXK_RETURN || evt.GetKeyCode() == WXK_NUMPAD_ENTER)) {
        long int col, line;
        PositionToXY(GetInsertionPoint(), &col, &line);
        int PreviousIndentation = GetLineIndentation(line-1);
        SetLineIndentation(line, PreviousIndentation);
        GotoPos(GetCurrentPos() + PreviousIndentation);
    }
}

The above C++ code preserves the indentation level, however, the cursor first goes to the beginning of the line and then to the indentation level. When using other IDEs, this does not happen in such way, such as going to the beginning of line and then to the indentation level. Rather, the cursor immediately goes to /follows the indentation level. Is there a way that the cursor can immediately go to the indentation level without initially going to zero indentation level.

By the way, I tried EVT_STC_CHARADDED, which seems like the way implemented in ZeroBraneStudio, but when Enter key is pressed evt.GetKeyCode() returns a weird integer and evt.GetUnicodeKey returns \0 and moreover EVT_STC_CHARADDED event is called twice (I guess due to CR+LF).

By the way, I am using wxWidgets-3.1.0 on Windows 10.

Any ideas would be appreciated.


Solution

  • We need to intercept an event and add a copy of the indentation from the previous line to the new line. The first question is which event to use. When the enter key is pressed, the following events are fired:

    • wxEVT_CHAR_HOOK
    • wxEVT_KEY_DOWN
    • wxEVT_STC_MODIFIED - ModificationType: 0x00100000
    • wxEVT_STC_MODIFIED - ModificationType: 0x00000410
    • wxEVT_STC_MODIFIED - ModificationType: 0x00002011
    • wxEVT_STC_CHARADDED
    • wxEVT_STC_UPDATEUI
    • wxEVT_STC_PAINTED
    • wxEVT_KEY_UP

    With the char_hook and key_down events, the key hasn't been sent to the control yet, so it won't be able to give the needed position information. The control shouldn't be changed in the stc_modified event, so we shouldn't use those events. By the time of stc_painted event, the cursor has already been drawn, so it and the key_up event are too late. And I learned in the other answer that stc_updateui event won't work.

    So by process of elimination, the only possibility is the wxEVT_STC_CHARADDED event. The next question is what to do in that event handler. I've adapted the code from here to work with wxStyledTextCtrl.

    void Script::OnCharAdded(wxStyledTextEvent& event)
    {
        int new_line_key=(GetEOLMode()==wxSTC_EOL_CR)?13:10;
    
        if ( event.GetKey() == new_line_key )
        {
            int cur_pos = GetCurrentPos();
            int cur_line = LineFromPosition(cur_pos);
    
            if ( cur_line > 0 )
            {
                wxString prev_line = GetLine(cur_line-1);
                size_t prev_line_indent_chars(0);
                for ( size_t i=0; i<prev_line.Length(); ++i )
                {
                    wxUniChar cur_char=prev_line.GetChar(i);
    
                    if (cur_char==' ')
                    {
                        ++prev_line_indent_chars;
                    }
                    else if (cur_char=='\t')
                    {
                        ++prev_line_indent_chars;
                    }
                    else
                    {
                        break;
                    }
                }
    
                AddText(prev_line.Left(prev_line_indent_chars));
            }
        }
    }
    

    This might be better since it physically counts the spaces and tabs.