Useful custom functions

ratchetfreak
instead of moving the whole range up, why not move the line above the range down below the range (and vice versa)


I'm glad I decided to post these ideas on the forums.
I've been using C for just over a week now, so sometimes I focus too much on the language and can get stuck looking for a specific solution to a problem.

My solution works, but I like yours better so I'll get onto that after I've implemented my other required functionality :)

Edited by Lucas89 on
I know the docs are abysmal so here's a tip, the "refresh" calls are only necessary after you call an exec_command that changes the state of the view. So for instance lucas_delete_line could be written as:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
View_Summary view = app->get_active_view(app);

app->view_set_mark(app, &view, seek_line_char(view.cursor.line, 0));
app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 65536), 1);
//app->refresh_view(app, &view);
exec_command(app, delete_range);
exec_command(app, delete_char);
app->refresh_view(app, &view);
app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 65536), 1);
//app->refresh_view(app, &view);


Also, in the next version, as apart of my work to cut down on the API cruft, the refresh is actually gone, and replaced with this helper:

1
2
3
4
void
refresh_view(Application_Links *app, View_Summary *view){
    *view = app->get_view(app, view->view_id, AccessAll);
}


So, as you can see, it literally just does another get so that your local copy is up to date.
Thanks for the tip :)
I've edited my previous posts to remove useless calls to `refresh_view`.

I'm writing these things as I need them while I do my work, so I'm mostly just guessing how to use the entire API.
It's starting to make more sense to me now, but it's useful to know these things so I can tidy up my code a bit.

I'm starting to run out of things I want to add now, but here is the latest.
When I type (, { or [ I like it to automatically insert the closing ), } or ].
In addition when the cursor is on a ), } or ], and I type the same character I like it to just skip the character, so this is what I came up with:

(This uses the `seek_char` function from this post)

 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
64
65
66
67
void
write_companion_character(Application_Links *app, char companion)
{
    View_Summary view = app->get_active_view(app);

    exec_command(app, write_character);
    app->refresh_view(app, &view);

    Buffer_Summary buffer = app->get_buffer(app, view.buffer_id);
    int pos = view.cursor.pos;

    app->buffer_replace_range(app, &buffer, pos, pos, &companion, 1);
    exec_command(app, move_left);
}

void
write_or_skip_character(Application_Links *app, char character)
{
    View_Summary view = app->get_active_view(app);

    if (view.cursor.pos == seek_char(app, view.cursor.pos, character, 1))
    {
        exec_command(app, move_right);
    }
    else
    {
        exec_command(app, write_character);
    }
}

CUSTOM_COMMAND_SIG(lucas_write_bracket_pair)
{
    write_companion_character(app, ')');
}

CUSTOM_COMMAND_SIG(lucas_write_brace_pair)
{
    write_companion_character(app, '}');
}

CUSTOM_COMMAND_SIG(lucas_write_square_pair)
{
    write_companion_character(app, ']');
}

CUSTOM_COMMAND_SIG(lucas_write_close_bracket)
{
    write_or_skip_character(app, ')');
}

CUSTOM_COMMAND_SIG(lucas_write_close_brace)
{
    write_or_skip_character(app, '}');
}

CUSTOM_COMMAND_SIG(lucas_write_close_square)
{
    write_or_skip_character(app, ']');
}

bind(context, '(', MDFR_NONE, lucas_write_bracket_pair);
bind(context, '{', MDFR_NONE, lucas_write_brace_pair);
bind(context, '[', MDFR_NONE, lucas_write_square_pair);

bind(context, ')', MDFR_NONE, lucas_write_close_bracket);
bind(context, '}', MDFR_NONE, lucas_write_close_brace);
bind(context, ']', MDFR_NONE, lucas_write_close_square);


Is there a built in function to get the character at the cursor, or next to the cursor etc?

As you can see, right now I use my own `seek_char` function a lot to compare positions, but being able to do something like `char_at_cursor(view.cursor)` or `char_after_cursor(view.cursor)` would be really useful.

Edited by Lucas89 on
Are you familiar with the function "buffer_read_range"? It lets you read out text from an arbitrary range within the buffer. So to get the character at the cursor you could do:

1
2
3
4
5
6
static char
get_char(Application_Links *app, Buffer_Summary *buffer, int pos){
    char result = 0;
    app->buffer_read_range(app, buffer, pos, pos+1, &result);
    return(result);
}
Mr4thDimention
Are you familiar with the function "buffer_read_range"? It lets you read out text from an arbitrary range within the buffer.


I didn't see that one. When I had a look through the header files I think I was focusing on words like 'seek' and 'view' so even if I did see it I must have just skipped over it without thinking.

Thanks for letting me know about it!
I can go back and replace some of my own functions with a call to `buffer_read_range` now :)
Here's one I use all the time, just switches a period to an arrow or vice versa.

 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
CUSTOM_COMMAND_SIG(james_toggle_period_arrow)
{
    String cursor_character;//character under the cursor
    Buffer_Summary buffer;
    View_Summary view;

    view = app->get_active_view(app, AccessOpen);
    buffer = app->get_buffer(app, view.buffer_id, AccessAll);

    cursor_character.str = (char*)app->memory;
    cursor_character.size = 1;

    app->buffer_read_range(app, &buffer, view.cursor.pos, view.cursor.pos + 1, cursor_character.str);

    if (cursor_character.str[0] == '.')
    {
        app->buffer_replace_range(app, &buffer, view.cursor.pos, view.cursor.pos + 1, "->", 2);
    }
    else if (cursor_character.str[0] == '-')
    {
        app->buffer_replace_range(app, &buffer, view.cursor.pos, view.cursor.pos + 2, ".", 1);
    }
    else if (cursor_character.str[0] == '>')
    {
        app->buffer_replace_range(app, &buffer, view.cursor.pos - 1, view.cursor.pos + 1, ".", 1);
    }
}



Edited by James Fulop on
In name of robustness I'd have read a char on either side of the cursor and made sure there was an arrow before replacing.

If buffer read range doesn't crash on out of bounds read it's pretty simple. otherwise you'd need to adjust the call based on whether the cursor is at the start or end of the buffer.
If you do an out of range read, the read will fail. The fail is indicated as a zero return value from buffer_read_range.
Would it be possible to make a custom function to pop to the matching header file when in a C/cpp file, and vice versa?
The idea would be a recursive search through the folder and any subfolders looking for a .h/.cpp with the same name. This is really handy in visual studio.

If the necessary things are exposed to make that happen I'll have a go at it.
Yes this is definitely possible.

You can get the buffer's file name. You can then use the string library to make a copy with the changed extension, or you can do that by hand obviously. Then you can use the app->directory_cd function or the app->get_file_list/app->free_file_list functions to explore the file system. Finally you can use app->create_buffer or the helper view_open_file to open the file.
Hi, i'm new to 4coder and i wanted to create the "newline_above/below" functions like Lucas has. I understand the API has changed since then. I read the documentation to try updating it but i failed. Here is what i currently have

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
CUSTOM_COMMAND_SIG(insert_newline_above)
{
    View_Summary view = app->get_active_view_(app, AccessAll);
    exec_command(app, move_up);
    refresh_view(app, &view);
    exec_command(app, seek_end_of_line);
    refresh_view(app, &view);
    exec_command(app, write_and_auto_tab);
    refresh_view(app, &view);
}

CUSTOM_COMMAND_SIG(insert_newline_below)
{
    View_Summary view = app->get_active_view_(app, AccessAll);
    exec_command(app, seek_end_of_line);
    refresh_view(app, &view);
    exec_command(app, write_and_auto_tab);
    refresh_view(app, &view);
}
write_and_auto_tab insert the character used to call the command as text. Unless you bind the command to '\n' it's not going to create a new line. You can use write_string to write a new line character instead. And since you don't use the View_Summary struct in the function there is no need to create and refresh it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
CUSTOM_COMMAND_SIG(insert_newline_above)
CUSTOM_DOC( "Insert a new line above the current line." )
{
    exec_command(app, move_up);
    exec_command(app, seek_end_of_line);
    write_string(app, make_lit_string( "\n" ));
}

CUSTOM_COMMAND_SIG(insert_newline_below)
CUSTOM_DOC( "Insert a new line below the current line." )
{
    exec_command(app, seek_end_of_line);
    write_string(app, make_lit_string( "\n" ));
}
Ohh i see, that works perfectly, thank you very much :).
Recently purchased 4Coder, and was a little overwhelmed due to lack of official getting started documentation and almost put it away, but this forum really helped me, I found my feet a bit, so I'll try and give back, if just a little.

My "work" productivity has gone to zero, because I've just spent so much time customising so many things in the tiny little ways I like. I've really enjoyed it and want to make this my long term editor.

Anyway, the couple of functions that might be of interest to others (disclaimer - probably buggy, made in version 4.0.30):

Automatically add include guards based on the filename, if a header file is created:

 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
OPEN_FILE_HOOK_SIG(custom_new_file_hook)
{
    Buffer_Summary buffer = get_buffer(app, buffer_id, AccessOpen);
    
    char filename[256];
    for (int i = 0; i < buffer.buffer_name_len; i++)
    {
        char c = buffer.buffer_name[i];
        if (c == '.')
            c = '_';
        c = char_to_upper(c);   
        filename[i] = c;
    }
    filename[buffer.buffer_name_len] = '\0';
    
    int filename_length = str_size(filename);
    
    if (filename[filename_length - 2] == '_' && filename[filename_length - 1] == 'H')
    {
        char out[512];
        strcpy(out, "#ifndef ");
        strcat(out, filename);
        strcat(out, "\n#define ");
        strcat(out, filename);
        strcat(out, "\n\n#endif\n");
        
        buffer_replace_range(app, &buffer, 0, 0, out, str_size(out));
    }
    
    
    return 0;
}


Display a lister of the last few items on the clipboard:

 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
64
65
66
67
68
69
70
71
void paste_clipboard_index(Application_Links* app, int clip_index)
{
    View_Summary view = get_active_view(app, AccessOpen);
    
    int32_t len = clipboard_index(app, 0, clip_index, 0, 0);
    char *str = 0;
    
    if (len <= app->memory_size)
        str = (char*)app->memory;
    
    if (str != 0)
    {
        clipboard_index(app, 0, clip_index, str, len);
        
        Buffer_Summary buffer = get_buffer(app, view.buffer_id, AccessOpen);
		int32_t pos = view.cursor.pos;
        
        buffer_replace_range(app, &buffer, pos, pos, str, len);
        view_set_cursor(app, &view, seek_pos(pos + len), true);
        
        Theme_Color paste = {};
        paste.tag = Stag_Paste;
        get_theme_colors(app, &paste, 1);
        view_post_fade(app, &view, 0.667f, pos, pos + len, paste.color);
        
        auto_tab_range(app);
    }
}

static void
activate_clipboard_lister(Application_Links *app, Partition *scratch, Heap *heap,
                          View_Summary *view, Lister_State *state,
                          String text_field, void *user_data, bool32 activated_by_mouse)
{
    lister_default(app, scratch, heap, view, state, ListerActivation_Finished);
	int clip_index = (int) PtrAsInt(user_data);
    paste_clipboard_index(app, clip_index);
}

CUSTOM_COMMAND_SIG(clipmate_lister)
{
    Partition* arena = &global_part;
    Temp_Memory temp = begin_temp_memory(arena);
    
    View_Summary view = get_active_view(app, AccessOpen);
    view_end_ui_mode(app, &view);
    
    int32_t option_count = clipboard_count(app, 0);
    if (option_count > 10)
        option_count = 10;
    
    Lister_Option* options = push_array(arena, Lister_Option, option_count);
    for (int32_t i = 0; i < option_count; i++)
    {
        int32_t contents_length = clipboard_index(app, 0, i, 0, 0);
        
		char* str_index = push_array(arena, char, 5);
		itoa(i, str_index, 10);
        
        char* clipboard_contents = push_array(arena, char, contents_length);
        clipboard_index(app, 0, i, clipboard_contents, contents_length);
        
        options[i].string = make_string(str_index, (int) strlen(str_index));
        options[i].status = make_string(clipboard_contents, contents_length);
        options[i].user_data = IntAsPtr(i);
    }
    
    begin_integrated_lister__basic_list(app, "Clipboard:", activate_clipboard_lister, 0, 0, options, option_count, 0, &view);
    
    end_temp_memory(temp);
}


Seek function/struct definition up (look for a '{' in the first column of a line):

 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
CUSTOM_COMMAND_SIG(seek_first_column_brace_up)
{
    View_Summary view = get_active_view(app, AccessOpen);
    Buffer_Summary buffer = get_buffer(app, view.buffer_id, AccessOpen);
    
    int current_line = view.cursor.line - 1;
    
    while (current_line > 1)
    {
        char read_result[1];
        
        Full_Cursor cursor = {};
        view_compute_cursor(app, &view, seek_line_char(current_line, 1), &cursor);
        buffer_read_range(app, &buffer, cursor.pos, cursor.pos + 1, read_result);
        
        if (read_result[0] == '{')
        {
            current_line--;
            break;
        }
        
        current_line--;
    }
    
    view_set_cursor(app, &view, seek_line_char(current_line, 1), 1);
}

Edited by Jackson on
Guess having a match brace key is a very simple thing to achieve but I am really confused how to do it...
I can not find functions mark_matching_brace and cursor_to_surrounding_scope mentioned in the quote below.
Where are those functions Allen refers to for placing the mark on the brace of a parent scope and the mark on the corresponding close brace?

Lucas89
Mr4thDimention
Lucas89, not sure if you saw it, but the power version does have functions for placing the mark on the brace of a parent scope and the mark on the corresponding close brace. It looks like yours is pretty much the same idea though, just combining it in more useful ways. It seems like there should just be a helper for "find containing scope open/close" and "find matching brace", because I can now imagine a lot of commands you might want with those.


If you're talking about the functions `mark_matching_brace` and `cursor_to_surrounding_scope` in the experiments file then yes, I did see them.