1

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.

macroland
  • 973
  • 9
  • 27

2 Answers2

2

Note: The comments below point out a fatal flaw in the code for this answer. Adjusting the cursor position in an UpdateUI event handler like I tried to do here is a bad idea. I posted another answer that hopefully works better.


I can't guarentee that this is the best way, but here is one way. First, this requires adding an integer member to your script class to serve as a flag indicating that indentation needs to be added. In the following, I've called it 'm_indentToAdd'.

To detect that a line has been added, you can use the wxEVT_STC_MODIFIED event. If the modification type indicates that it was a user action, text has been inserted, and that 1 line has been added, then the next line will need to have indentation added. In addition to the enter key being pressed, this will catch when a single line including the line endings has been pasted.

void Script::OnModified(wxStyledTextEvent& event)
{
    int mt = event.GetModificationType();

    if(mt&wxSTC_MOD_INSERTTEXT && mt&wxSTC_PERFORMED_USER && event.GetLinesAdded()==1)
    {
        int cur_line = m_stc->LineFromPosition(event.GetPosition());
        int cur_indent = m_stc->GetLineIndentation(cur_line);
        m_indentToAdd=cur_indent;
    }
}

To avoid having the cursor start at the beginning of the line and then move to the indentation, you can handle the wxEVT_STC_UPDATEUI event and reset the position there:

void Script::OnUpdateUI(wxStyledTextEvent& event)
{
    if(m_indentToAdd)
    {
        int cur_pos = m_stc->GetCurrentPos();
        int cur_line = m_stc->LineFromPosition(cur_pos);
        m_stc->SetLineIndentation(cur_line, m_indentToAdd);
        m_stc->GotoPos(cur_pos+m_indentToAdd);

        m_indentToAdd=0;
    }
}

The UpdateUI event doesn't provide the current position or line, so they have to be recomputed before the indentation can be set. I suppose this could be optimized by storing those values in the OnModified event handler and then using them in the UpdateUI event handler.

New Pagodi
  • 3,484
  • 1
  • 14
  • 24
  • Thanks for your good and to the point answer! It works well except that if there is a blank line below the line that user will press enter, for example user is at 5th line and 6th line is blank (say there is 6th line in the editor), then the indentation follows from 7th line rather than 6th. If there was no 6th line, pressing enter does what it should do. I should find a way to fix that. Just one question: I see that you use `LineFromIndentation` rather than `GetLineIndentation`; is there a specific reason for this? – macroland Feb 15 '17 at 23:45
  • The solution I gave did have very little testing, but I'm not seeing a problem a problem due to empty following lines. For me it seems to work in all of the following cases: no next line, empty next line, and next line is non empty but with a different indentation level. If you can't find a fix can you give some more details? – New Pagodi Feb 16 '17 at 00:43
  • Also for your final question, were you asking why I uses LineFromPosition instead of PositionToXY? There's isn't any real reason. PositionToXY is implemented using LineFromPosition, so they both do the same thing. – New Pagodi Feb 16 '17 at 00:48
  • I am using the following code: `int cur_pos = GetCurrentPos(); int cur_line = LineFromPosition(cur_pos); SetLineIndentation(cur_line, m_indentToAdd); int NextLinePos = XYToPosition(m_indentToAdd, cur_line + 1); GotoPos(NextLinePos);` For example, when I first press enter at 2nd line where there is an indentation, the values are `cur_line=2; NextLinePos=16`. So it works correctly and goes to 3rd line. Now if I use up arrow key to go back to 2nd line and press enter again `cur_line=2; NextLinePos=18`. Not sure what is happening. – macroland Feb 18 '17 at 04:34
  • Thanks. I see the problem now - and because of it, this answer simply won't work. It seems that when you try to reset the cursor position, in the UpdateUI event, not all position information gets updated. As you described above, when you move from the 3rd line back to the 2nd, the cursor should go the the forth position in the line, but because I messed up the position information in the UpdateUI event, it instead goes to the first position. I'll post a new answer that works better (or at least doesn't have this problem). – New Pagodi Feb 18 '17 at 21:08
1

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.

New Pagodi
  • 3,484
  • 1
  • 14
  • 24
  • Many thanks! Yesterday I spent a couple hours to fix the line skipping problem but was to no avail. Now it works how it should work. – macroland Feb 19 '17 at 07:45
  • Sorry about the other answer. Once I realized the problem, I spent about 3 hours trying to work around it before realizing it was probably hopeless. – New Pagodi Feb 19 '17 at 16:16
  • With your recent improvements on the answer, it is now indeed a very good one! – macroland Feb 19 '17 at 22:44