Correct indents in python files

I'm trying to create a python mode for 4coder. I have already made the 4coder_language_py.h file, but now I just need to make 4coder do the line indentation correctly. I'm guessing this might have something to do with 4coder_auto_indent.cpp or 4coder_default_hooks.cpp, but I'm not fimilar enough with the customization layer to know for sure. Any help will be appreciated. Thank you.

Edited by Barret Gaylor on Reason: Initial post
Ok, I think I figured it out.

here's the code I created:
https://github.com/Barret5Ocal/4coderPython

first I added the code for the parse context.

1
2
3
4
5
6
7
                if(match(ext, "py")){
                    if(parse_context_language_py == 0){
                        init_language_py(app);
                    }
                    parse_context_id = parse_context_language_py;
                    python_yes = true;
                }     


I added the python_yes variable for the next bit of code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
if(python_yes)
  // NOTE(allen): Decide buffer settings
    bool32 use_virtual_whitespace = false;
    bool32 use_lexer = false;
    bool32 wrap_lines = true;
    if (treat_as_todo){
        lex_without_strings = true;
        wrap_lines = true;
        use_virtual_whitespace = true;
        use_lexer = true;
    }
    else if (treat_as_code){
        wrap_lines = global_config.enable_code_wrapping;
        use_virtual_whitespace = global_config.enable_virtual_whitespace;
        use_lexer = true;
    }
    if (match(make_string(buffer.buffer_name, buffer.buffer_name_len), "*compilation*")){
        wrap_lines = false;
    }
    if(python_yes)
    {
        use_virtual_whitespace = false;
        wrap_lines = false;
    }
    //if (buffer.size >= (192 << 10)){
    if (buffer.size >= (128 << 10)){
        wrap_lines = false;
        use_virtual_whitespace = false;
    }


I don't know how to get 4coder to auto indent when you press enter if anyone knows how to do that it world be a great help.
I don't know if having auto indentation for python will be possible as I believe 4coder at the moment rely on the tokenizer for C like language (using {, }, (, ) ) to do the indentation.

Dang. Well turning off virtual white space will be fine for now. It would be great if I could find a way to run the python code in the editor
Actually this is way harder than I thought it was going to be. I order for this to work I'm going to have to get the auto indents to work for python.
Maybe you should try contacting Allen on twitter or the 4coder discord to know what would be the best way to handle python.

I don't know much about adding support for a new language in 4coder, but my guess would be that for python you'd better treat the file as plain text, add a function to highlight python keywords just by comparing text, and do a few simple indentation functions. For example when you press "enter" call write_and_indent_python that would look at the text line, search for "if", "for"... or other keyword that would increment or decrement the indentation. And probably having a function to increase / decrease the indentation of the selection. I know next to nothing about Python so I don't really know what's necessary.

To do text highlighting you could use something like this. Note that this is a simple text search so you probably want to modify it to be more robust :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
typedef struct Highlight_Pair {
    String_Const_u8 needle;
    ARGB_Color color;
} Highlight_Pair;

function void draw_string_highlights( Application_Links *app, Buffer_ID buffer, Text_Layout_ID text_layout_id, Highlight_Pair *pairs, i32 pair_count ) {
    
    Range_i64 visible_range = text_layout_get_visible_range( app, text_layout_id );
    
    Highlight_Pair* pair = pairs;
    
    for ( i32 i = 0; i < pair_count; i += 1, pair += 1 ) {
        
        if ( pair->needle.size <= 0 ) {
            continue;
        }
        
        i64 position = visible_range.min;
        seek_string_forward( app, buffer, position - 1, visible_range.max, pair->needle, &position );
        
        while ( position < visible_range.max ) {
            
            Range_i64 range = Ii64_size( position, pair->needle.size );
            paint_text_color( app, text_layout_id, range, pair->color );
            seek_string_forward( app, buffer, position, visible_range.max, pair->needle, &position );
        }
    }
}

/* In the render_buffer hook */

Highlight_Pair pairs[ ] = {
    string_u8_litexpr( "if " ), finalize_color( defcolor_keyword, 0 ),
    string_u8_litexpr( "for " ), finalize_color( defcolor_keyword, 0 ),
    string_u8_litexpr( "def " ), finalize_color( defcolor_keyword, 0 ),
    ...
};
        
draw_string_highlights( app, buffer, text_layout_id, pairs, ArrayCount( pairs ) );

Edited by Simon Anciaux on
Thanks for the help. Maybe you could make the auto indenter make an indent whenever the user presses enter on a line that ends with a ':'. Also, do you know how to get into the 4coder discord?
On http://4coder.net/ At the bottom of the page there is a link that reads "Handmade Network discord" which is actually the invite to the 4coder discord.

The problem with the python indentation (as I understand it, and I will say it again: I know next to nothing about Python) is that there is no rules to when the indentation should decrease a level. For example:
1
2
3
if test:
    do_something()
do_semithing_2()


There is no rule saying that do_something_2 should not be indented, and if you change the indentation you change the flow of the program.

If I remember correctly the auto indentation in 4coder will run on the whole file when you save the file and some other actions, and that would not work with python. That's why I think you should treat python files as plain text and write some indentation function to help you, but that wouldn't be an auto indenter.

Here is an example of indenting a line after pressing enter. It will not work if treat_as_code is set as that would cause the C++ auto indenter to reindent (I think), and it assumes virtual whitespace is off. I believe that having another function bound to <shift + enter> that would create a new line with a decreased indentation level would be useful to "close a scope". It's not properly tested.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
CUSTOM_COMMAND_SIG( python_new_line_indent ) {
    
    View_ID view = get_active_view( app, 0 );
    Buffer_ID buffer = view_get_buffer( app, view, 0 );
    
    Scratch_Block scratch( app );
    i64 line = get_line_number_from_pos( app, buffer, view_get_cursor_pos( app, view ) );
    String_Const_u8 string = push_buffer_line( app, scratch, buffer, line );
    
    i64 tab_count = 0;
    i64 space_count = 0;
    
    for ( umm i = 0; i < string.size; i++ ) {
        
        if ( string.str[ i ] == ' ' ) {
            space_count++;
        } else if ( string.str[ i ] == '\t' ) {
            tab_count++;
        } else {
            break;
        }
    }
    
    i64 indent_level = tab_count;
    indent_level += space_count / global_config.indent_width;
    indent_level += ( space_count % global_config.indent_width ) ? 1 : 0;
    
    i64 cursor = view_get_cursor_pos( app, view );
    
    if ( cursor > 1 ) {
        
        u8 c = buffer_get_char( app, buffer, cursor - 1 );
        
        if ( c == ':' ) {
            indent_level += 1;
        }
    }
    
    String_u8 insert = { 0 };
    
    if ( global_config.indent_with_tabs ) {
        
        i64 size = 1 + indent_level;
        insert = string_u8_push( scratch, size );
        string_append_character( &insert, '\n' );
        
        for ( i64 i = 0; i < indent_level; i++ ) {
            string_append_character( &insert, '\t' );
        }
        
    } else {
        
        i64 size = 1 + indent_level * global_config.indent_width;
        insert = string_u8_push( scratch, size );
        string_append_character( &insert, '\n' );
        
        for ( i64 i = 1; i < size; i++ ) {
            string_append_character( &insert, ' ' );
        }
    }
    
    write_text( app, SCu8( insert.str, insert.size ) );
}

Edited by Simon Anciaux on Reason: typo
Sounds good. How would I implements this code in my 4coder? Sorry, I'm new to modding 4coder. Also, how do you turn on keyword highlighting on a plain text file?
I would like to reiterate that
- You really should try asking Allen or people on discord about this as there might be a better solution;
- The solution I proposed isn't perfect and is quite complicated, especially if you're not familiar with the custom layer and C++.

There is a getting started article in the wiki to help you setup your platform layer. Note that the code I posted before is about 4coder 4.1.x and will not work with 4.0.x.

You can't turn syntax highlighting on for plain text file. But if you modify the draw_strings_highlights function, you can use it to search for strings and highlight those strings. You would need for example to search for "if " (if followed by a space), make sure the character before this string is a space, a tab or a new line and highlight the text in that case. That would be a quick and dirty way to do it. The proper way would be to write a parser.

Edited by Simon Anciaux on