added query helpers, replace functions

master
Allen Webster 2016-02-27 17:23:40 -05:00
parent 16108d0f57
commit c8b901e299
4 changed files with 221 additions and 62 deletions

View File

@ -223,7 +223,7 @@ CUSTOM_COMMAND_SIG(switch_to_compilation){
// to change the specific type of view and set files even when the view didn't
// contain a file.
view = app->get_active_file_view(app);
buffer = app->get_buffer_by_name(app, make_string(name, name_size));
buffer = app->get_buffer_by_name(app, name, name_size);
app->view_set_buffer(app, &view, buffer.buffer_id);
}
@ -302,44 +302,16 @@ CUSTOM_COMMAND_SIG(switch_to_file_in_quotes){
}
CUSTOM_COMMAND_SIG(goto_line){
User_Input in;
Query_Bar bar;
char string_space[256];
int line_number;
String input;
char string_space[256];
// NOTE(allen|a3.4.4): It will not cause an *error* if we continue on after failing to.
// start a query bar, but it will be unusual behavior from the point of view of the
// user, if this command starts intercepting input even though no prompt is shown.
// This will only happen if you have a lot of bars open already or if the current view
// doesn't support query bars.
if (app->start_query_bar(app, &bar, 0) == 0) return;
// NOTE(allen|a3.4.4): The application side is storing a pointer straight to your Query_Bar
// any change you make to it will be reflected in what the application renders. The application
// also makes sure that it destroys all query bars whenever a command exists or an abort
// mesasge is sent to it.
bar.prompt = make_lit_string("Goto Line: ");
bar.string = make_fixed_width_string(string_space);
while (1){
in = app->get_user_input(app, EventOnAnyKey, EventOnEsc | EventOnButton);
if (in.abort) break;
if (in.type == UserInputKey){
if (in.key.character >= '0' && in.key.character <= '9'){
append(&bar.string, in.key.character);
}
else if (in.key.keycode == key_back){
--bar.string.size;
}
else if (in.key.keycode == '\n' || in.key.keycode == '\t'){
break;
}
}
input = make_fixed_width_string(string_space);
if (query_user_number(app, make_lit_string("Goto Line: "), &input)){
line_number = str_to_int(input);
active_view_to_line(app, line_number);
}
if (in.abort) return;
line_number = str_to_int(bar.string);
active_view_to_line(app, line_number);
}
CUSTOM_COMMAND_SIG(search);
@ -371,12 +343,17 @@ isearch(Application_Links *app, int start_reversed){
String rsearch = make_lit_string("Reverse-I-Search: ");
while (1){
// NOTE(allen): Change the bar's prompt to match the current direction.
if (reverse) bar.prompt = rsearch;
else bar.prompt = isearch;
in = app->get_user_input(app, EventOnAnyKey, EventOnEsc | EventOnButton);
if (in.abort) break;
// NOTE(allen): If we're getting mouse events here it's a 4coder bug, because we
// only asked to intercept key events.
assert(in.type == UserInputKey);
int made_change = 0;
if (in.key.keycode == '\n' || in.key.keycode == '\t'){
break;
@ -413,22 +390,33 @@ isearch(Application_Links *app, int start_reversed){
if (in.key.keycode != key_back){
int new_pos;
if (reverse){
app->buffer_seek_string(app, &buffer, start_pos - 1, bar.string, 0, &new_pos);
if (step_backward){
pos = new_pos;
start_pos = new_pos;
app->buffer_seek_string(app, &buffer, start_pos - 1, bar.string, 0, &new_pos);
app->buffer_seek_string(app, &buffer, start_pos - 1, bar.string.str, bar.string.size, 0, &new_pos);
if (new_pos >= 0){
if (step_backward){
pos = new_pos;
start_pos = new_pos;
app->buffer_seek_string(app, &buffer, start_pos - 1, bar.string.str, bar.string.size, 0, &new_pos);
if (new_pos < 0) new_pos = start_pos;
}
match.start = new_pos;
match.end = match.start + bar.string.size;
}
}
else{
app->buffer_seek_string(app, &buffer, start_pos + 1, bar.string, 1, &new_pos);
if (step_forward){
pos = new_pos;
start_pos = new_pos;
app->buffer_seek_string(app, &buffer, start_pos + 1, bar.string, 1, &new_pos);
app->buffer_seek_string(app, &buffer, start_pos + 1, bar.string.str, bar.string.size, 1, &new_pos);
if (new_pos < buffer.size){
if (step_forward){
pos = new_pos;
start_pos = new_pos;
app->buffer_seek_string(app, &buffer, start_pos + 1, bar.string.str, bar.string.size, 1, &new_pos);
if (new_pos >= buffer.size) new_pos = start_pos;
}
match.start = new_pos;
match.end = match.start + bar.string.size;
}
}
match.start = new_pos;
}
else{
match.end = match.start + bar.string.size;
}
@ -448,6 +436,84 @@ CUSTOM_COMMAND_SIG(reverse_search){
isearch(app, 1);
}
CUSTOM_COMMAND_SIG(replace_in_range){
char replace_space[1024];
String replace = make_fixed_width_string(replace_space);
char with_space[1024];
String with = make_fixed_width_string(with_space);;
if (!query_user_string(app, make_lit_string("Replace: "), &replace)) return;
if (!query_user_string(app, make_lit_string("With: "), &with)) return;
Buffer_Summary buffer;
File_View_Summary view;
view = app->get_active_file_view(app);
buffer = app->get_buffer(app, view.buffer_id);
Range range = get_range(&view);
int pos, new_pos;
pos = range.min;
app->buffer_seek_string(app, &buffer, pos, replace.str, replace.size, 1, &new_pos);
while (new_pos < range.end){
app->buffer_replace_range(app, &buffer, new_pos, new_pos + replace.size, with.str, with.size);
pos = new_pos + with.size;
app->buffer_seek_string(app, &buffer, pos, replace.str, replace.size, 1, &new_pos);
}
}
CUSTOM_COMMAND_SIG(query_replace){
char replace_space[1024];
String replace = make_fixed_width_string(replace_space);
char with_space[1024];
String with = make_fixed_width_string(with_space);;
if (!query_user_string(app, make_lit_string("Replace: "), &replace)) return;
if (!query_user_string(app, make_lit_string("With: "), &with)) return;
Query_Bar bar;
Buffer_Summary buffer;
File_View_Summary view;
int pos, new_pos;
bar.prompt = make_lit_string("Replace? (y)es, (n)ext, (esc)\n");
bar.string = {};
view = app->get_active_file_view(app);
buffer = app->get_buffer(app, view.buffer_id);
pos = view.cursor.pos;
app->buffer_seek_string(app, &buffer, pos, replace.str, replace.size, 1, &new_pos);
User_Input in = {};
while (new_pos < buffer.size){
Range match = make_range(new_pos, new_pos + replace.size);
app->view_set_highlight(app, &view, match.min, match.max, 1);
in = app->get_user_input(app, EventOnAnyKey, EventOnButton);
if (in.abort || in.key.keycode == key_esc) break;
if (in.key.character == 'y' || in.key.character == 'Y' || in.key.character == '\n' || in.key.character == '\t'){
app->buffer_replace_range(app, &buffer, match.min, match.max, with.str, with.size);
pos = match.start + with.size;
}
else{
pos = match.max;
}
app->buffer_seek_string(app, &buffer, pos, replace.str, replace.size, 1, &new_pos);
}
app->view_set_highlight(app, &view, 0, 0, 0);
if (in.abort) return;
app->view_set_cursor(app, &view, seek_pos(pos), 1);
}
CUSTOM_COMMAND_SIG(open_in_other){
exec_command(app, cmdid_change_active_panel);
exec_command(app, cmdid_interactive_open);
@ -678,6 +744,8 @@ extern "C" GET_BINDING_DATA(get_bindings){
bind(context, 'f', MDFR_CTRL, search);
bind(context, 'r', MDFR_CTRL, reverse_search);
bind(context, 'g', MDFR_CTRL, goto_line);
bind(context, 'q', MDFR_CTRL, query_replace);
bind(context, 'a', MDFR_CTRL, replace_in_range);
bind(context, 'K', MDFR_CTRL, cmdid_kill_buffer);
bind(context, 'O', MDFR_CTRL, cmdid_reopen);

View File

@ -183,11 +183,11 @@ struct Application_Links;
#define GET_BUFFER_MAX_INDEX_SIG(name) int name(Application_Links *context)
#define GET_BUFFER_SIG(name) Buffer_Summary name(Application_Links *context, int index)
#define GET_ACTIVE_BUFFER_SIG(name) Buffer_Summary name(Application_Links *context)
#define GET_BUFFER_BY_NAME(name) Buffer_Summary name(Application_Links *context, String filename)
#define GET_BUFFER_BY_NAME(name) Buffer_Summary name(Application_Links *context, char *filename, int len)
#define REFRESH_BUFFER_SIG(name) int name(Application_Links *context, Buffer_Summary *buffer)
#define BUFFER_SEEK_DELIMITER_SIG(name) int name(Application_Links *context, Buffer_Summary *buffer, int start, char delim, int seek_forward, int *out)
#define BUFFER_SEEK_STRING_SIG(name) int name(Application_Links *context, Buffer_Summary *buffer, int start, String string, int seek_forward, int *out)
#define BUFFER_SEEK_STRING_SIG(name) int name(Application_Links *context, Buffer_Summary *buffer, int start, char *str, int len, int seek_forward, int *out)
#define BUFFER_READ_RANGE_SIG(name) int name(Application_Links *context, Buffer_Summary *buffer, int start, int end, char *out)
#define BUFFER_REPLACE_RANGE_SIG(name) int name(Application_Links *context, Buffer_Summary *buffer, int start, int end, char *str, int len)
#define BUFFER_SAVE_SIG(name) int name(Application_Links *context, Buffer_Summary *buffer, char *filename, int len)

View File

@ -250,3 +250,91 @@ key_is_unmodified(Key_Event_Data *key){
return(unmodified);
}
static int
query_user_general(Application_Links *app, String prompt, String *string, int force_number){
User_Input in;
Query_Bar bar;
int success = 1;
int good_character = 0;
// NOTE(allen|a3.4.4): It will not cause an *error* if we continue on after failing to.
// start a query bar, but it will be unusual behavior from the point of view of the
// user, if this command starts intercepting input even though no prompt is shown.
// This will only happen if you have a lot of bars open already or if the current view
// doesn't support query bars.
if (app->start_query_bar(app, &bar, 0) == 0) return 0;
// NOTE(allen|a3.4.4): The application side is storing a pointer straight to your Query_Bar
// any change you make to it will be reflected in what the application renders. The application
// also makes sure that it destroys all query bars whenever a command exits.
bar.prompt = prompt;
bar.string = *string;
while (1){
// NOTE(allen|a3.4.4): This call will block until the user does one of the input
// types specified in the flags. The first set of flags are inputs you'd like to intercept
// that you don't want to abort on. The second set are inputs that you'd like to cause
// the command to abort. If an event satisfies both flags, it is treated as an abort.
in = app->get_user_input(app, EventOnAnyKey, EventOnEsc | EventOnButton);
// NOTE(allen|a3.4.4): The responsible thing to do on abort is to end the command
// without waiting on get_user_input again.
if (in.abort){
success = 0;
break;
}
good_character = 0;
if (key_is_unmodified(&in.key)){
if (force_number){
if (in.key.character >= '0' && in.key.character <= '9'){
good_character = 1;
}
}
else{
if (in.key.character != 0){
good_character = 1;
}
}
}
// NOTE(allen|a3.4.4): All we have to do to update what is shown on the query bar
// is to edit or local Query_Bar struct! This is handy because it means our Query_Bar
// can double as storing the state of the input AND as the source for the UI.
if (in.type == UserInputKey){
if (in.key.keycode == '\n' || in.key.keycode == '\t'){
break;
}
else if (in.key.keycode == key_back){
--bar.string.size;
}
else if (good_character){
append(&bar.string, in.key.character);
}
}
}
// NOTE(allen|a3.4.4): It is not always necessary to end your query bars, because
// 4coder will clean them up when the command exits anyway, but because this is
// a local scope we should clean up the bar manually because the pointer the application
// stores to our Query_Bar is about to go bad.
app->end_query_bar(app, &bar, 0);
if (success){
*string = bar.string;
}
return(success);
}
inline int
query_user_string(Application_Links *app, String prompt, String *string){
int success = query_user_general(app, prompt, string, 0);
return(success);
}
inline int
query_user_number(Application_Links *app, String prompt, String *string){
int success = query_user_general(app, prompt, string, 1);
return(success);
}

29
4ed.cpp
View File

@ -2065,7 +2065,7 @@ extern "C"{
Buffer_Summary buffer = {};
working_set = cmd->working_set;
if (table_find(&working_set->table, filename, &index)){
if (table_find(&working_set->table, make_string(filename, len), &index)){
file = working_set->files + index;
if (!file->state.is_dummy && file_is_ready(file)){
fill_buffer_summary(&buffer, file, working_set);
@ -2094,17 +2094,19 @@ extern "C"{
file = working_set->files + buffer->buffer_id;
if (!file->state.is_dummy && file_is_ready(file)){
size = buffer_size(&file->state.buffer);
if (start >= 0 && start < size){
result = 1;
result = 1;
if (start < 0 && !seek_forward) *out = start;
else if (start >= size && seek_forward) *out = start;
else{
if (seek_forward){
*out = buffer_seek_delimiter(&file->state.buffer, start, delim);
}
else{
*out = buffer_reverse_seek_delimiter(&file->state.buffer, start, delim);
}
if (*out < 0) *out = 0;
if (*out > size) *out = size;
}
fill_buffer_summary(buffer, file, working_set);
}
}
@ -2127,19 +2129,20 @@ extern "C"{
file = working_set->files + buffer->buffer_id;
if (!file->state.is_dummy && file_is_ready(file)){
size = buffer_size(&file->state.buffer);
if (start >= 0 && start < size){
if (start < 0 && !seek_forward) *out = start;
else if (start >= size && seek_forward) *out = start;
else{
part = &cmd->mem->part;
temp = begin_temp_memory(part);
spare = push_array(part, char, string.size);
spare = push_array(part, char, len);
result = 1;
if (seek_forward){
*out = buffer_find_string(&file->state.buffer, start, size, string.str, string.size, spare);
*out = buffer_find_string(&file->state.buffer, start, size, str, len, spare);
}
else{
*out = buffer_rfind_string(&file->state.buffer, start, string.str, string.size, spare);
*out = buffer_rfind_string(&file->state.buffer, start, str, len, spare);
}
if (*out < 0) *out = 0;
if (*out > size) *out = size;
end_temp_memory(temp);
}
fill_buffer_summary(buffer, file, working_set);
@ -2647,7 +2650,7 @@ app_hardcode_styles(App_Vars *vars){
style->main.file_info_style = file_info_style;
style->font_changed = 1;
++style;
/////////////////
*style = *(style-1);
style_set_name(style, make_lit_string("4coder-mono"));
@ -2689,7 +2692,7 @@ app_hardcode_styles(App_Vars *vars){
file_info_style.bar_color = 0xFFCACACA;
file_info_style.bar_active_color = 0xFFA8A8A8;
file_info_style.base_color = 0xFF000000;
file_info_style.pop1_color = 0xFF1504CF;
file_info_style.pop1_color = 0xFF03CF0C;
file_info_style.pop2_color = 0xFFFF0000;
style->main.file_info_style = file_info_style;
style->font_changed = 1;