Useful custom functions

I've only been using 4coder for roughly a day so far, but was so impressed with it even in the alpha stage that I decided to give $10 for the 'power' version.

I don't know if I'll keep on posting or not, but I thought it might be useful to start keeping a record of useful functions that I'm writing while I get used to/figure out the 4coder API.

I'm coming from Subline Text 3, which I've been using for around 6 years now, and while it does have it's problems there are a few things from it that I miss.

Being new to 4coder I don't know if the functionality I'm going to post here already exists, so I'll just post the things I'm making anyway.

The first two things are the `ctrl+shift+enter` and `ctrl+enter` commands from Sublime.

`ctrl+shift+enter` will create a new line above the current line you are on and set the caret to the correct indent.
`ctrl+enter` will do the same, but create a line below the current line instead of above.

The usefulness of these two functions comes from the fact that it doesn't matter where your caret is positioned within the current line.

This is what I came up with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
CUSTOM_COMMAND_SIG(lucas_newline_above)
{
    View_Summary view = app->get_active_view(app);

    exec_command(app, move_up);
    app->refresh_view(app, &view);
    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 65536), 1);
    exec_command(app, write_and_auto_tab);
}

CUSTOM_COMMAND_SIG(lucas_newline_below)
{
    View_Summary view = app->get_active_view(app);

    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 65536), 1);
    exec_command(app, write_and_auto_tab);
}


Which can then be bound as follows:

1
2
bind(context, '\n', MDFR_CTRL | MDFR_SHIFT, lucas_newline_above);
bind(context, '\n', MDFR_CTRL, lucas_newline_below);

Edited by Lucas89 on
Nice! This gives me an idea for extending the cursor setting API so that you can do positions relative from the end as well as the beginning of the line. So you could do something like

1
app->view_set_cursor(app, &view, seek_line_char_end(cursor.line, 0), true);


to always get the spot at the end of the line. Thanks for the thought!
Here are some functions I wrote to jump to next/previous functions (assuming you keep all your functions prefixed with tags like internal/inline). The code is kind of janky, but it works.

 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
CUSTOM_COMMAND_SIG(previous_function){
    char *FunctionTags[] = {"internal", "inline"};
    int NumFunctionTags = sizeof(FunctionTags)/sizeof(FunctionTags[0]);
    
    exec_command(app, cmdid_move_up);
    
    View_Summary view = app->get_active_view(app);
    Buffer_Summary buffer = app->get_buffer(app, view.buffer_id);
    
    int NewLinePosition = -1;

    for(int TagIndex = 0;
        TagIndex < NumFunctionTags;
        TagIndex++)
    {
        char *Tag = FunctionTags[TagIndex];
        int TagPos = -1;
        app->buffer_seek_string(app, &buffer, view.cursor.pos, Tag, (int)strlen(Tag), false, &TagPos);
        if((TagPos != -1) && (TagPos > NewLinePosition))
        {
            NewLinePosition = TagPos;
        }
    }

    if(NewLinePosition != -1)
    {
        app->view_set_cursor(app, &view, seek_pos(NewLinePosition), 0);
        exec_command(app, cmdid_seek_end_of_line);
        exec_command(app, cmdid_center_view);
    }
    else
    {
        exec_command(app, cmdid_move_down);
    }
}

CUSTOM_COMMAND_SIG(next_function){
    char *FunctionTags[] = {"internal", "inline"};
    int NumFunctionTags = sizeof(FunctionTags)/sizeof(FunctionTags[0]);
    
    View_Summary view = app->get_active_view(app);
    Buffer_Summary buffer = app->get_buffer(app, view.buffer_id);
    
    int NewLinePosition = -1;

    for(int TagIndex = 0;
        TagIndex < NumFunctionTags;
        TagIndex++)
    {
        char *Tag = FunctionTags[TagIndex];
        int TagPos = -1;
        app->buffer_seek_string(app, &buffer, view.cursor.pos, Tag, (int)strlen(Tag), true, &TagPos);
        if((TagPos != -1) && ((TagPos < NewLinePosition) || NewLinePosition == -1))
        {
            NewLinePosition = TagPos;
        }
    }

    if(NewLinePosition != -1)
    {
        app->view_set_cursor(app, &view, seek_pos(NewLinePosition), 0);
        exec_command(app, cmdid_seek_end_of_line);
        exec_command(app, cmdid_center_view);
    }
}
Good to hear it's given you an idea :)

Now, another one:

One of the plugins for Sublime allowed me to select all of the contents between the closest matching {}, () or [].
I think it also included "" and '', but I wasn't so bothered about those.

Anyway, this was useful for instantly selecting a block and cutting it or deleting it, instead of having to manually move the cursor to get into position for deleting etc.

I've spent the past 15 mins writing an extremely naïve implementation; no fancy parsing of the code or anything like that, literally just some flags to decide what to do in a big loop.

I've only been using C for about a week now, so of course this could be cleaned up and be made to behave more intelligently.

As such this extension will not ignore comments so if you have a {}, [] or () in a comment it will count that as a match.
Strings should be ok though.

Just to try and explain a bit better, here is a gif of it working:



And here is the naïve code:

(See this later post for better code)

Bound like this:

1
bind(context, 'M', MDFR_CTRL, lucas_select_bracket_contents);

Edited by Lucas89 on
xelNitram
Here are some functions I wrote to jump to next/previous functions (assuming you keep all your functions prefixed with tags like internal/inline). The code is kind of janky, but it works.


Nice :)

I'll try it out for myself.
I'm always looking for new ways to navigate the buffer.
I found the function jumping a useful replacement for page up/down. The block selection function you made seems really cool, thanks for sharing.
Yet another one.

Duplicate 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
27
28
CUSTOM_COMMAND_SIG(lucas_write_newline){
    View_Summary view = app->get_active_view(app);
    char character = '\n';

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

    app->buffer_replace_range(app, &buffer, pos, pos, &character, 1);
    app->view_set_cursor(app, &view, seek_pos(next_pos), 1);
}

CUSTOM_COMMAND_SIG(lucas_duplicate_line)
{
    View_Summary view = app->get_active_view(app);

    int cursor_pos = view.cursor.pos;

    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);
    exec_command(app, cmdid_copy);
    exec_command(app, lucas_write_newline);
    exec_command(app, cmdid_paste);
    app->view_set_cursor(app, &view, seek_pos(cursor_pos), 1);
    exec_command(app, move_down);
}

bind(context, 'D', MDFR_CTRL, lucas_duplicate_line);

Edited by Lucas89 on
And a few more for moving the mark like a normal cursor:

 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
CUSTOM_COMMAND_SIG(lucas_mark_next_token)
{
    View_Summary view = app->get_active_view(app);

    exec_command(app, cursor_mark_swap);
    exec_command(app, seek_alphanumeric_or_camel_right);
    exec_command(app, cursor_mark_swap);
}

CUSTOM_COMMAND_SIG(lucas_mark_prev_token)
{
    View_Summary view = app->get_active_view(app);

    exec_command(app, cursor_mark_swap);
    exec_command(app, seek_alphanumeric_or_camel_left);
    exec_command(app, cursor_mark_swap);
}

CUSTOM_COMMAND_SIG(lucas_mark_next_char)
{
    View_Summary view = app->get_active_view(app);

    exec_command(app, cursor_mark_swap);
    exec_command(app, move_right);
    exec_command(app, cursor_mark_swap);
}

CUSTOM_COMMAND_SIG(lucas_mark_prev_char)
{
    View_Summary view = app->get_active_view(app);

    exec_command(app, cursor_mark_swap);
    exec_command(app, move_left);
    exec_command(app, cursor_mark_swap);
}

CUSTOM_COMMAND_SIG(lucas_mark_up)
{
    View_Summary view = app->get_active_view(app);

    exec_command(app, cursor_mark_swap);
    exec_command(app, move_up);
    exec_command(app, cursor_mark_swap);
}

CUSTOM_COMMAND_SIG(lucas_mark_down)
{
    View_Summary view = app->get_active_view(app);

    exec_command(app, cursor_mark_swap);
    exec_command(app, move_down);
    exec_command(app, cursor_mark_swap);
}

bind(context, key_right, MDFR_CTRL | MDFR_SHIFT, lucas_mark_next_token);
bind(context, key_left, MDFR_CTRL | MDFR_SHIFT, lucas_mark_prev_token);
bind(context, key_right, MDFR_CTRL | MDFR_SHIFT | MDFR_ALT, lucas_mark_next_char);
bind(context, key_left, MDFR_CTRL | MDFR_SHIFT | MDFR_ALT, lucas_mark_prev_char);
bind(context, key_up, MDFR_CTRL | MDFR_SHIFT, lucas_mark_up);
bind(context, key_down, MDFR_CTRL | MDFR_SHIFT, lucas_mark_down);
bind(context, key_up, MDFR_CTRL | MDFR_SHIFT | MDFR_ALT, lucas_mark_up);
bind(context, key_down, MDFR_CTRL | MDFR_SHIFT | MDFR_ALT, lucas_mark_down);

Edited by Lucas89 on
Nice! That's a lot of really cool stuff.

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.

I'm curious to know, are you all using the latest version (4.0.7)?
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.

I actually read them in order to learn how to move through the buffer character by character.
The reason I wrote my own was because yours was limited to { and }, and I also wanted both steps to happen in one key binding instead of having to press a key binding to move the cursor, then again to move the mark.

Plus I wanted it to just find the closest {, ( or [ and then match to whichever was the closest.

Also I noticed that yours stopped on curly braces inside strings, which I didn't want.
My rewrite isn't much better, but as you can see the naïve logic makes it good enough to skip over most strings and allows you to pass in a `find` and `opposite` character; although comments still mess it up.

Since I could see you were working on something like that I basically just took yours and rewrote it to be good enough for now, because in Sublime I use that kind of functionality all the time to quickly replace strings or cut if blocks etc. all the time :)

In Sublime though, if I press the key binding again it will expand the selection to the next outer scope, and there is another key binding to shrink the selection to the next inner scope, but I couldn't be bothered with all of that just yet; I'm too new to C to be thinking of more complicated functionality right now :P

Mr4thDimention
I'm curious to know, are you all using the latest version (4.0.7)?


Yes, I'm using the 4.0.7 version; Kill rect has been very useful so far :)
I'm still on 4.0.6 super, but I think kill-rect is going to convert me real soon.
Another two in case anyone is interested.

First of all here is a simple one, delete line:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
CUSTOM_COMMAND_SIG(lucas_delete_line)
{
    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);
    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);
}

bind(context, 'K', MDFR_CTRL, lucas_delete_line);


Next, I improved the scope selection code so it will now ignore characters in single line comments, multi-line comments, string literals and character literals.

The code has become a bit sprawling but I'm sure it could be tidied up should you want to do so.

This would replace the code I posted a day or two ago:

  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
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
int
count_unescaped_chars(Application_Links *app, int pos, int until, char find, char forward, char uninterrupted)
{
    View_Summary view = app->get_active_view(app);
    Buffer_Summary buffer = app->get_buffer(app, view.buffer_id);
    Stream_Chunk chunk;
    char chunk_size[(1 << 10)];

    if (!init_stream_chunk(&chunk, app, &buffer, pos, chunk_size, sizeof(chunk_size)))
    {
        return 0;
    }

    int i = pos;
    int count = 0;
    char at_cursor = 0;
    char before_cursor = 0;

    for (;;)
    {
        // Check streams etc at the start of the loop if we are searching backwards
        // This ensures that the character at the cursor is not included in the search
        if (!forward)
        {
            if (--i == chunk.start && !backward_stream_chunk(&chunk)) break;
            if (i <= chunk.start) break;
            if (until >= 0 && i <= until) break;
        }

        at_cursor = chunk.data[i];
        before_cursor = i > chunk.start ? chunk.data[i - 1] : 0;

        if (uninterrupted && count && at_cursor != find) return count;
        if (at_cursor == find && before_cursor != '\\') ++count;

        // Check streams etc at the end of the loop if we are searching forwards
        // This ensures that the character at the cursor is included in the search
        if (forward)
        {
            if (++i == chunk.end && !forward_stream_chunk(&chunk)) break;
            if (i >= chunk.end) break;
            if (until >= 0 && i >= until) break;
        }
    }

    return count;
}

int
seek_char(Application_Links *app, int pos, char find, char forward)
{
    View_Summary view = app->get_active_view(app);
    Buffer_Summary buffer = app->get_buffer(app, view.buffer_id);
    Stream_Chunk chunk;
    char chunk_size[(1 << 10)];

    if (!init_stream_chunk(&chunk, app, &buffer, pos, chunk_size, sizeof(chunk_size)))
    {
        return 0;
    }

    int i = pos;

    for (;;)
    {
        // Check streams etc at the start of the loop if we are searching backwards
        // This ensures that the character at the cursor is not included in the search
        if (!forward)
        {
            if (--i == chunk.start && !backward_stream_chunk(&chunk)) break;
            if (i <= chunk.start) break;
        }

        if (chunk.data[i] == find) return i;

        // Check streams etc at the end of the loop if we are searching forwards
        // This ensures that the character at the cursor is included in the search
        if (forward)
        {
            if (++i == chunk.end && !forward_stream_chunk(&chunk)) break;
            if (i >= chunk.end) break;
        }
    }

    return 0;
}

int
seek_matching_char_pos(Application_Links *app, int pos, char find, char opposite, char forward)
{
    View_Summary view = app->get_active_view(app);
    Buffer_Summary buffer = app->get_buffer(app, view.buffer_id);
    Stream_Chunk chunk;
    char chunk_size[(1 << 10)];

    if (!init_stream_chunk(&chunk, app, &buffer, pos, chunk_size, sizeof(chunk_size)))
    {
        return 0;
    }

    int i = pos;
    int nest_count = 0;
    int newline_left = 0;
    int newline_right = 0;
    int char_count_left = 0;
    int char_count_right = 0;
    int left_char_pos = 0;
    int right_char_pos = 0;
    int pos_found = 0;

    char at_cursor = 0;
    char before_cursor = 0;

    char in_str_lit = 0;
    char in_char_lit = 0;
    char in_multi_comment = 0;
    char in_single_comment = 0;

    for (;;)
    {
        // Check streams etc at the start of the loop if we are searching backwards
        // This ensures that the character at the cursor is not included in the search
        if (!forward)
        {
            if (--i == chunk.start && !backward_stream_chunk(&chunk)) break;
            if (i <= chunk.start) break;
        }

        at_cursor = chunk.data[i];
        before_cursor = i > chunk.start ? chunk.data[i - 1] : 0;

        if ((at_cursor == find || at_cursor == opposite) && before_cursor != '\\')
        {
            in_str_lit = 0;
            in_char_lit = 0;
            in_multi_comment = 0;
            in_single_comment = 0;

            newline_left = seek_char(app, i, '\n', 0);
            newline_right = seek_char(app, i, '\n', 1);

            if (!in_single_comment)
            {
                // Check for single line comment
                char_count_left = count_unescaped_chars(app, i, newline_left, '/', 0, 1, &pos_found);

                if (char_count_left >= 2)
                {
                    // Check for string literal
                    char_count_left = count_unescaped_chars(app, pos_found, newline_left, '"', 0, 0);
                    char_count_right = count_unescaped_chars(app, pos_found, newline_right, '"', 1, 0);

                    char_count_left = char_count_left ? char_count_left % 2 : char_count_left;
                    char_count_right = char_count_right ? char_count_right % 2 : char_count_right;

                    in_str_lit = char_count_left % 2 && char_count_left % 2 ;

                    // Check for character literal
                    char_count_left = count_unescaped_chars(app, pos_found, newline_left, '\'', 0, 0);
                    char_count_right = count_unescaped_chars(app, pos_found, newline_right, '\'', 1, 0);

                    char_count_left = char_count_left ? char_count_left % 2 : char_count_left;
                    char_count_right = char_count_right ? char_count_right % 2 : char_count_right;

                    in_char_lit = char_count_left % 2 && char_count_left % 2 ;

                    if (!in_str_lit && !in_char_lit)
                    {
                        in_single_comment = 1;
                    }

                    in_char_lit = 0;
                    in_str_lit = 0;
                }
            }

            if (!in_single_comment)
            {
                // Check for multi-line comment
                left_char_pos = seek_char(app, i, '*', 0);
                right_char_pos = seek_char(app, i, '*', 1);
                in_multi_comment = 0;

                if (left_char_pos < right_char_pos &&
                    seek_char(app, left_char_pos, '/', 0) == (left_char_pos - 1) &&
                    seek_char(app, right_char_pos, '/', 1) == (right_char_pos + 1))
                {
                    in_multi_comment = 1;
                }
            }

            if (!in_multi_comment)
            {
                // Check for string literal
                char_count_left = count_unescaped_chars(app, i, newline_left, '"', 0, 0);
                char_count_right = count_unescaped_chars(app, i, newline_right, '"', 1, 0);

                char_count_left = char_count_left ? char_count_left % 2 : char_count_left;
                char_count_right = char_count_right ? char_count_right % 2 : char_count_right;

                in_str_lit = char_count_left % 2 && char_count_left % 2 ;
            }

            if (!in_str_lit)
            {
                // Check for character literal
                char_count_left = count_unescaped_chars(app, i, newline_left, '\'', 0, 0);
                char_count_right = count_unescaped_chars(app, i, newline_right, '\'', 1, 0);

                char_count_left = char_count_left ? char_count_left % 2 : char_count_left;
                char_count_right = char_count_right ? char_count_right % 2 : char_count_right;

                in_char_lit = char_count_left % 2 && char_count_left % 2 ;
            }

            if (!in_str_lit && !in_char_lit && !in_single_comment && !in_multi_comment)
            {
                if (at_cursor == opposite) ++nest_count;
                if (at_cursor == find && !nest_count) return i;
                if (at_cursor == find) --nest_count;
            }
        }

        // Check streams etc at the end of the loop if we are searching forwards
        // This ensures that the character at the cursor is included in the search
        if (forward)
        {
            if (++i == chunk.end && !forward_stream_chunk(&chunk)) break;
            if (i >= chunk.end) break;
        }
    }

    return 0;
}

CUSTOM_COMMAND_SIG(lucas_select_bracket_contents)
{
    View_Summary view = app->get_active_view(app);
    int curly = seek_matching_char_pos(app, view.cursor.pos, '{', '}', 0);
    int round = seek_matching_char_pos(app, view.cursor.pos, '(', ')', 0);
    int square = seek_matching_char_pos(app, view.cursor.pos, '[', ']', 0);
    int matching = 0;

    if (curly > round && curly > square)
    {
        matching = seek_matching_char_pos(app, view.cursor.pos, '}', '{', 1);
        app->view_set_cursor(app, &view, seek_pos(curly + 1), 1);
        app->view_set_mark(app, &view, seek_pos(matching));
    }
    else if (round > curly && round > square)
    {
        matching = seek_matching_char_pos(app, view.cursor.pos, ')', '(', 1);
        app->view_set_cursor(app, &view, seek_pos(round + 1), 1);
        app->view_set_mark(app, &view, seek_pos(matching));
    }
    else if (square > curly && square > round)
    {
        matching = seek_matching_char_pos(app, view.cursor.pos, ']', '[', 1);
        app->view_set_cursor(app, &view, seek_pos(square + 1), 1);
        app->view_set_mark(app, &view, seek_pos(matching));
    }
}

bind(context, 'E', MDFR_CTRL, lucas_select_bracket_contents);

Edited by Lucas89 on
In addition to the previous two changes I made in the last post, I've also just created these two commands for moving the current line up or down:

 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
CUSTOM_COMMAND_SIG(lucas_move_line_up)
{
    View_Summary view = app->get_active_view(app);

    if (view.cursor.line <= 1) return;

    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);
    exec_command(app, cmdid_cut);
    exec_command(app, backspace_char);
    app->refresh_view(app, &view);
    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 0), 1);
    exec_command(app, cmdid_paste);
    exec_command(app, lucas_write_newline);
    exec_command(app, move_up);
    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 65536), 1);
    exec_command(app, auto_tab_line_at_cursor);
}

CUSTOM_COMMAND_SIG(lucas_move_line_down)
{
    View_Summary view = app->get_active_view(app);
    int newline_count = count_unescaped_chars(app, 0, -1, '\n', 1, 0);

    if (view.cursor.line >= newline_count) return;

    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);
    exec_command(app, cmdid_cut);
    exec_command(app, delete_char);
    exec_command(app, move_down);
    app->refresh_view(app, &view);
    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 0), 1);
    exec_command(app, cmdid_paste);
    exec_command(app, lucas_write_newline);
    exec_command(app, move_up);
    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 65536), 1);
    exec_command(app, auto_tab_line_at_cursor);
}

bind(context, key_up, MDFR_CTRL | MDFR_SHIFT, lucas_move_line_up);
bind(context, key_down, MDFR_CTRL | MDFR_SHIFT, lucas_move_line_down);


Those two commands are a bit long, and there may be a simpler algorithm but it's the best I can come up with for now.

One thing to note is that the move down function makes use of my `count_unescaped_chars` function from this post.

I only used it because I couldn't find an obvious way to get the line count for a view.

Also you may have noticed that in most of my custom functions I don't give too much care to the mark position.

I wonder, is it people's preference to preserve the mark position in commands or not really care about it.

Edited by Lucas89 on
So moving lines up and down wasn't enough for me, I needed to be able to move ranges too:

 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
CUSTOM_COMMAND_SIG(lucas_move_range_up)
{
    View_Summary view = app->get_active_view(app);

    if (view.cursor.line == view.mark.line)
    {
        exec_command(app, lucas_move_line_up);
        return;
    }

    if (view.cursor.line < view.mark.line)
    {
        exec_command(app, cursor_mark_swap);
        app->refresh_view(app, &view);
    }

    if (view.mark.line <= 1) return;

    app->view_set_mark(app, &view, seek_line_char(view.mark.line, 0));
    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 65536), 1);
    exec_command(app, cmdid_cut);
    exec_command(app, backspace_char);
    app->refresh_view(app, &view);
    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 0), 1);
    exec_command(app, cmdid_paste);
    app->refresh_view(app, &view);
    exec_command(app, lucas_write_newline);
    exec_command(app, move_up);
    app->view_set_mark(app, &view, seek_line_char(view.mark.line, 0));
    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 65536), 1);
    exec_command(app, cmdid_auto_tab_range);
}

CUSTOM_COMMAND_SIG(lucas_move_range_down)
{
    View_Summary view = app->get_active_view(app);

    if (view.cursor.line == view.mark.line)
    {
        exec_command(app, lucas_move_line_down);
        return;
    }

    if (view.cursor.line < view.mark.line)
    {
        exec_command(app, cursor_mark_swap);
        app->refresh_view(app, &view);
    }

    int newline_count = count_unescaped_chars(app, 0, -1, '\n', 1, 0);

    if (view.cursor.line >= newline_count) return;

    app->view_set_mark(app, &view, seek_line_char(view.mark.line, 0));
    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 65536), 1);
    exec_command(app, cmdid_cut);
    exec_command(app, delete_char);
    exec_command(app, move_down);
    app->refresh_view(app, &view);
    exec_command(app, cmdid_paste);
    app->refresh_view(app, &view);
    exec_command(app, lucas_write_newline);
    exec_command(app, move_up);
    app->view_set_mark(app, &view, seek_line_char(view.mark.line, 0));
    app->view_set_cursor(app, &view, seek_line_char(view.cursor.line, 65536), 1);
    exec_command(app, cmdid_auto_tab_range);
}

bind(context, key_up, MDFR_CTRL | MDFR_ALT | MDFR_SHIFT, lucas_move_range_up);
bind(context, key_down, MDFR_CTRL | MDFR_ALT | MDFR_SHIFT, lucas_move_range_down);


This uses `count_unescaped_chars` from this post, and `lucas_move_line_up` and `lucas_move_line_down` from this post.

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