18 posts
Useful custom functions
Edited by Lucas89 on
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); 
Allen Webster
473 posts / 4 projects
Heyo
Useful custom functions
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!
xel
18 posts
presses plastic buttons for fun
Useful custom functions
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); } } 
18 posts
Useful custom functions
Edited by Lucas89 on
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); 
18 posts
Useful custom functions
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.
xel
18 posts
presses plastic buttons for fun
Useful custom functions
I found the function jumping a useful replacement for page up/down. The block selection function you made seems really cool, thanks for sharing.
18 posts
Useful custom functions
Edited by Lucas89 on
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); 
18 posts
Useful custom functions
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); 
Allen Webster
473 posts / 4 projects
Heyo
Useful custom functions
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)?
18 posts
Useful custom functions
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 :)
xel
18 posts
presses plastic buttons for fun
Useful custom functions
I'm still on 4.0.6 super, but I think kill-rect is going to convert me real soon.
18 posts
Useful custom functions
Edited by Lucas89 on
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); 
18 posts
Useful custom functions
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.
18 posts
Useful custom functions
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.
497 posts
Useful custom functions
instead of moving the whole range up, why not move the line above the range down below the range (and vice versa)