3037 lines
105 KiB
C++
3037 lines
105 KiB
C++
/*
|
|
* Mr. 4th Dimention - Allen Webster
|
|
*
|
|
* 12.12.2014
|
|
*
|
|
* Application layer for project codename "4ed"
|
|
*
|
|
*/
|
|
|
|
// TOP
|
|
// TODO(allen):
|
|
//
|
|
// BUGS & PROBLEMS
|
|
//
|
|
// - more rendering optimizations (1920 X 1080 is too slow)
|
|
//
|
|
// - the String class should always maintain an extra byte for NULL termination
|
|
// so that we don't have problems going to C style strings
|
|
//
|
|
// - LOGGING SYSTEM FOR ALL BUILD CONFIGS
|
|
//
|
|
// - when opening files don't try to open twice, have a function that figures out
|
|
// whether there is a direct match and returns exactly the name the open function
|
|
// should use. Further, this should always just return the first thing that an
|
|
// iteration across possible matches would return. Then make that match ordering
|
|
// algorithm super smart.
|
|
//
|
|
// - same file multiple panel editing problems
|
|
// scrolling doesn't always work
|
|
// cursor doesn't stay put on other panels
|
|
//
|
|
// GENERAL
|
|
//
|
|
// - untabification
|
|
//
|
|
// - mode switching
|
|
//
|
|
// - auto mode switching
|
|
//
|
|
// - disk file syncing GUI
|
|
//
|
|
// - meta command stuff
|
|
// command use frequency
|
|
//
|
|
// - configuration / GUI for generating configuration
|
|
//
|
|
// - more feed back messages ("File saved" etc)
|
|
//
|
|
// - line wrap, line mode change, no scroll
|
|
//
|
|
// - travel packaging
|
|
//
|
|
// - replace SDL with stb
|
|
//
|
|
// - multiple live name conflicts
|
|
//
|
|
// - non-file panels
|
|
//
|
|
// - nav links
|
|
//
|
|
// - undo / redo
|
|
//
|
|
// - on file reopen diff and try to find place for cursor
|
|
//
|
|
// TOOLS
|
|
//
|
|
// - calculator with: dec hex translation
|
|
//
|
|
// - solver
|
|
//
|
|
// TEXT MODE
|
|
//
|
|
// - replace
|
|
//
|
|
// - select word
|
|
//
|
|
// - match list
|
|
//
|
|
// - regular expression
|
|
//
|
|
// BASIC CODE MODE - general any language
|
|
//
|
|
// - select token
|
|
//
|
|
// - seek token or whitespace within token
|
|
//
|
|
// - reprogrammable lexing / auto indent
|
|
//
|
|
// - bracket match / mismatch highlighting
|
|
//
|
|
// - smart line wrapping (using tokens and white space as separation points)
|
|
//
|
|
// - auto casing rules?
|
|
//
|
|
// SUPER CODE MODE - C/C++ editing
|
|
//
|
|
// - identifier rename
|
|
//
|
|
// - boolean inverse polarity
|
|
//
|
|
// - compose/explode class/hierarchy
|
|
// - virtual to vtable sim/switch
|
|
//
|
|
// - explode class template?
|
|
//
|
|
// - enumerate template combinations?
|
|
//
|
|
// - generate header
|
|
//
|
|
|
|
/*
|
|
* Quick and Dirty Partition System
|
|
*/
|
|
|
|
struct Partition_Cursor{
|
|
u8 *memory_base;
|
|
u8 *memory_cursor;
|
|
i32 max_size;
|
|
};
|
|
|
|
internal Partition_Cursor
|
|
partition_open(void *memory, i32 size){
|
|
Partition_Cursor partition = {};
|
|
partition.memory_base =
|
|
partition.memory_cursor = (u8*)memory;
|
|
partition.max_size = size;
|
|
return partition;
|
|
}
|
|
|
|
internal void*
|
|
partition_allocate(Partition_Cursor *data, i32 size){
|
|
void *ret = 0;
|
|
if ((data->memory_cursor - data->memory_base) + size <= data->max_size &&
|
|
size > 0){
|
|
ret = data->memory_cursor;
|
|
data->memory_cursor += size;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Plaform Independent File Name Helpers
|
|
*/
|
|
|
|
struct File_List_Iterator{
|
|
bool32 folder_stage;
|
|
u8 **filename_ptr;
|
|
};
|
|
|
|
internal File_List_Iterator
|
|
files_iterator_init(File_List *files){
|
|
File_List_Iterator files_it = {};
|
|
if (files->folder_count > 0){
|
|
files_it.filename_ptr = files->folder_names;
|
|
files_it.folder_stage = 1;
|
|
}
|
|
else if (files->file_count > 0){
|
|
files_it.filename_ptr = files->file_names;
|
|
}
|
|
return files_it;
|
|
}
|
|
|
|
internal void
|
|
files_iterator_step(File_List *files, File_List_Iterator *files_it){
|
|
++files_it->filename_ptr;
|
|
if (files_it->folder_stage){
|
|
if (files_it->filename_ptr >= files->folder_names + files->folder_count){
|
|
if (files->file_count > 0){
|
|
files_it->filename_ptr = files->file_names;
|
|
}
|
|
else{
|
|
files_it->filename_ptr = 0;
|
|
}
|
|
files_it->folder_stage = 0;
|
|
}
|
|
}
|
|
else{
|
|
if (files_it->filename_ptr >= files->file_names + files->file_count){
|
|
files_it->filename_ptr = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Drawing Functions
|
|
*/
|
|
|
|
internal u32
|
|
style_token_color(Editing_Style *style,
|
|
Cpp_Token_Type type){
|
|
u32 result;
|
|
switch (type){
|
|
case CPP_TOKEN_COMMENT:
|
|
result = style->comment_color;
|
|
break;
|
|
|
|
case CPP_TOKEN_KEYWORD:
|
|
result = style->keyword_color;
|
|
break;
|
|
|
|
case CPP_TOKEN_STRING_CONSTANT:
|
|
case CPP_TOKEN_CHARACTER_CONSTANT:
|
|
case CPP_TOKEN_INTEGER_CONSTANT:
|
|
case CPP_TOKEN_FLOATING_CONSTANT:
|
|
case CPP_TOKEN_BOOLEAN_CONSTANT:
|
|
case CPP_TOKEN_INCLUDE_FILE:
|
|
result = style->constant_color;
|
|
break;
|
|
|
|
default:
|
|
result = style->default_color;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
panel_draw(Thread_Context *thread, Render_Target *target,
|
|
Editing_Panel *panel, bool32 is_active){
|
|
Editing_File *file = panel->file;
|
|
Editing_Style *style = file->style;
|
|
Font *font = style->font;
|
|
|
|
i32 character_w = (i32)(style_get_character_width(style));
|
|
i32 character_h = (i32)(font->line_skip);
|
|
i32 offset_x = (i32)(panel->x);
|
|
i32 offset_y = (i32)(panel->y);
|
|
i32 max_x = (i32)(panel->w);
|
|
i32 max_y = (i32)(panel->h);
|
|
|
|
Blit_Rect panel_area;
|
|
panel_area.x_start = offset_x;
|
|
panel_area.y_start = offset_y;
|
|
panel_area.x_end = offset_x + max_x;
|
|
panel_area.y_end = offset_y + max_y;
|
|
|
|
if (!file || !file->data || file->is_dummy){
|
|
i32 start_x = (panel_area.x_start + panel_area.x_end)/2;
|
|
i32 start_y = (panel_area.y_start + panel_area.y_end)/2;
|
|
persist String null_file_message = make_lit_string("NULL FILE");
|
|
start_x -= (character_w*null_file_message.size)/2;
|
|
start_y -= (character_h)/2;
|
|
|
|
real32 pos_x = 0;
|
|
real32 pos_y = 0;
|
|
|
|
for (i32 i = 0; i < null_file_message.size; ++i){
|
|
u8 to_render = null_file_message.str[i];
|
|
|
|
if (font->glyphs[to_render].data){
|
|
font_draw_glyph_clipped(target, font, to_render,
|
|
(real32)start_x + pos_x, (real32)start_y + pos_y,
|
|
style->special_character_color,
|
|
panel_area);
|
|
pos_x += character_w;
|
|
}
|
|
}
|
|
}
|
|
|
|
else{
|
|
u32 tab_width = style->tab_width;
|
|
i32 size = (i32)file->size;
|
|
u8 *data = (u8*)file->data;
|
|
|
|
real32 shift_x = 0;
|
|
shift_x = -panel->scroll_x * character_w;
|
|
|
|
i32 truncated_start_y = (i32)panel->scroll_y;
|
|
real32 scroll_amount = panel->scroll_y - truncated_start_y;
|
|
|
|
Panel_Cursor_Data start_cursor;
|
|
start_cursor = panel->scroll_y_cursor;
|
|
start_cursor = panel_compute_cursor_from_xy(panel, 0, truncated_start_y);
|
|
panel->scroll_y_cursor = start_cursor;
|
|
|
|
i32 start_character = start_cursor.pos;
|
|
|
|
real32 pos_x = 0;
|
|
real32 pos_y = -character_h*scroll_amount;
|
|
|
|
Cpp_Token_Stack token_stack = file->token_stack;
|
|
u32 main_color = style->default_color;
|
|
u32 highlight_color = 0x00000000;
|
|
i32 token_i = 0;
|
|
bool32 tokens_exist = file->tokens_exist;
|
|
|
|
if (tokens_exist){
|
|
// TODO(allen): Use cpp_get_token, it binary searches this shit!
|
|
while (token_i < token_stack.count &&
|
|
start_character > token_stack.tokens[token_i].start){
|
|
++token_i;
|
|
}
|
|
if (token_i != 0){
|
|
main_color =
|
|
style_token_color(style, token_stack.tokens[token_i-1].type);
|
|
if (token_stack.tokens[token_i-1].type == CPP_TOKEN_JUNK){
|
|
highlight_color = style->highlight_junk_color;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(allen): Render through NULLS
|
|
for (i32 i = start_character; i < size && data[i]; ++i){
|
|
u8 to_render = data[i];
|
|
|
|
u32 fade_color = 0xFFFF00FF;
|
|
real32 fade_amount = 0.f;
|
|
if (style->use_paste_color && panel->paste_effect.tick_down > 0 &&
|
|
panel->paste_effect.start <= i && i < panel->paste_effect.end){
|
|
fade_color = style->paste_color;
|
|
fade_amount = (real32)(panel->paste_effect.tick_down) / panel->paste_effect.tick_max;
|
|
}
|
|
|
|
if (tokens_exist && token_i < token_stack.count){
|
|
if (i == token_stack.tokens[token_i].start){
|
|
main_color =
|
|
style_token_color(style, token_stack.tokens[token_i].type);
|
|
++token_i;
|
|
}
|
|
if (token_i > 0 &&
|
|
i >= token_stack.tokens[token_i-1].start + token_stack.tokens[token_i-1].size){
|
|
main_color = 0xFFFFFFFF;
|
|
}
|
|
}
|
|
|
|
highlight_color = 0x00000000;
|
|
|
|
if (tokens_exist && token_i > 0 &&
|
|
token_stack.tokens[token_i-1].type == CPP_TOKEN_JUNK &&
|
|
i >= token_stack.tokens[token_i-1].start &&
|
|
i <= token_stack.tokens[token_i-1].start + token_stack.tokens[token_i-1].size){
|
|
highlight_color = style->highlight_junk_color;
|
|
}
|
|
else if (panel->show_whitespace &&
|
|
character_is_any_whitespace(data[i])){
|
|
highlight_color = style->highlight_white_color;
|
|
}
|
|
|
|
i32 cursor_mode = 0;
|
|
if (panel->show_temp_highlight){
|
|
if (panel->temp_highlight.pos <= i && i < panel->temp_highlight_end_pos){
|
|
cursor_mode = 2;
|
|
}
|
|
}
|
|
else{
|
|
if (panel->cursor.pos == i){
|
|
cursor_mode = 1;
|
|
}
|
|
}
|
|
|
|
if (!panel->unwrapped_lines &&
|
|
pos_x + character_w > max_x){
|
|
pos_x = 0;
|
|
pos_y += font->line_skip;
|
|
}
|
|
|
|
if (highlight_color != 0){
|
|
if (file->endline_mode == ENDLINE_RN_COMBINED &&
|
|
to_render == '\r' &&
|
|
i + 1 < size &&
|
|
data[i+1] == '\n'){
|
|
// DO NOTHING
|
|
}
|
|
else{
|
|
draw_rectangle_clipped(target,
|
|
(i32)shift_x + offset_x + (i32)pos_x,
|
|
offset_y + (i32)pos_y,
|
|
character_w, font->line_skip,
|
|
highlight_color, panel_area);
|
|
}
|
|
}
|
|
|
|
if (cursor_mode){
|
|
if (is_active){
|
|
u32 color = 0x00000000;
|
|
switch (cursor_mode){
|
|
case 1:
|
|
color = style->cursor_color;
|
|
break;
|
|
case 2:
|
|
color = style->highlight_color;
|
|
break;
|
|
}
|
|
draw_rectangle_clipped(target,
|
|
(i32)shift_x + offset_x + (i32)pos_x,
|
|
offset_y + (i32)pos_y,
|
|
character_w, font->line_skip,
|
|
color, panel_area);
|
|
}
|
|
else{
|
|
draw_rectangle_outline_clipped(target, (i32)shift_x + offset_x + (i32)pos_x, offset_y + (i32)pos_y,
|
|
character_w, font->line_skip, style->cursor_color,
|
|
panel_area);
|
|
}
|
|
}
|
|
|
|
if (i == panel->mark){
|
|
draw_rectangle_outline_clipped(target, (i32)shift_x + offset_x + (i32)pos_x, offset_y + (i32)pos_y,
|
|
character_w, font->line_skip, style->mark_color,
|
|
panel_area);
|
|
}
|
|
|
|
if (to_render == '\r'){
|
|
switch (file->endline_mode){
|
|
case ENDLINE_RN_COMBINED:
|
|
{
|
|
if (i + 1 < size &&
|
|
data[i+1] == '\n'){
|
|
// DO NOTHING
|
|
}
|
|
else{
|
|
u32 char_color = style->special_character_color;
|
|
if (cursor_mode && is_active){
|
|
switch (cursor_mode){
|
|
case 1:
|
|
char_color = style->at_cursor_color;
|
|
break;
|
|
case 2:
|
|
char_color = style->at_highlight_color;
|
|
break;
|
|
}
|
|
}
|
|
|
|
char_color = color_blend(char_color, fade_amount, fade_color);
|
|
|
|
font_draw_glyph_clipped(target, font, '\\',
|
|
shift_x + offset_x + pos_x,
|
|
(real32)offset_y + pos_y,
|
|
char_color, panel_area);
|
|
|
|
pos_x += character_w;
|
|
draw_rectangle_clipped(target,
|
|
(i32)shift_x + offset_x + (i32)pos_x,
|
|
offset_y + (i32)pos_y,
|
|
character_w, font->line_skip,
|
|
highlight_color, panel_area);
|
|
|
|
font_draw_glyph_clipped(target, font, 'r',
|
|
shift_x + offset_x + pos_x,
|
|
(real32)offset_y + pos_y,
|
|
char_color, panel_area);
|
|
pos_x += character_w;
|
|
}
|
|
}break;
|
|
|
|
case ENDLINE_RN_SEPARATE:
|
|
{
|
|
pos_x = 0;
|
|
pos_y += font->line_skip;
|
|
}break;
|
|
|
|
case ENDLINE_RN_SHOWALLR:
|
|
{
|
|
u32 char_color = style->special_character_color;
|
|
if (cursor_mode && is_active){
|
|
switch (cursor_mode){
|
|
case 1:
|
|
char_color = style->at_cursor_color;
|
|
break;
|
|
case 2:
|
|
char_color = style->at_highlight_color;
|
|
break;
|
|
}
|
|
}
|
|
|
|
char_color = color_blend(char_color, fade_amount, fade_color);
|
|
|
|
font_draw_glyph_clipped(target, font, '\\',
|
|
shift_x + offset_x + pos_x,
|
|
(real32)offset_y + pos_y,
|
|
char_color, panel_area);
|
|
|
|
pos_x += character_w;
|
|
draw_rectangle_clipped(target,
|
|
(i32)shift_x + offset_x + (i32)pos_x,
|
|
offset_y + (i32)pos_y,
|
|
character_w, font->line_skip,
|
|
highlight_color, panel_area);
|
|
|
|
font_draw_glyph_clipped(target, font, 'r',
|
|
shift_x + offset_x + pos_x,
|
|
(real32)offset_y + pos_y,
|
|
char_color, panel_area);
|
|
pos_x += character_w;
|
|
}break;
|
|
}
|
|
}
|
|
|
|
else if (to_render == '\n'){
|
|
pos_x = 0;
|
|
pos_y += font->line_skip;
|
|
}
|
|
else if (to_render == '\t'){
|
|
if (highlight_color != 0){
|
|
draw_rectangle_clipped(target,
|
|
(i32)shift_x + offset_x + (i32)pos_x + character_w,
|
|
offset_y + (i32)pos_y,
|
|
character_w*(tab_width-1), font->line_skip,
|
|
highlight_color, panel_area);
|
|
}
|
|
pos_x += character_w*tab_width;
|
|
}
|
|
|
|
else if (font->glyphs[to_render].data){
|
|
u32 char_color = main_color;
|
|
if (cursor_mode && is_active){
|
|
switch (cursor_mode){
|
|
case 1:
|
|
char_color = style->at_cursor_color;
|
|
break;
|
|
case 2:
|
|
char_color = style->at_highlight_color;
|
|
break;
|
|
}
|
|
}
|
|
|
|
char_color = color_blend(char_color, fade_amount, fade_color);
|
|
|
|
font_draw_glyph_clipped(target, font, to_render,
|
|
shift_x + offset_x + pos_x,
|
|
(real32)offset_y + pos_y, char_color,
|
|
panel_area);
|
|
pos_x += character_w;
|
|
}
|
|
|
|
else{
|
|
pos_x += character_w;
|
|
}
|
|
|
|
if (pos_y > max_y){
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Hot Directory
|
|
*/
|
|
|
|
struct Hot_Directory{
|
|
String string;
|
|
File_List file_list;
|
|
};
|
|
|
|
internal void
|
|
hot_directory_init(Hot_Directory *hot_directory){
|
|
hot_directory->string = make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
|
|
|
|
i32 dir_size = system_get_working_directory(hot_directory->string.str,
|
|
hot_directory->string.memory_size);
|
|
|
|
if (dir_size <= 0){
|
|
dir_size = system_get_easy_directory(hot_directory->string.str);
|
|
}
|
|
|
|
hot_directory->string.size = dir_size;
|
|
|
|
append(&hot_directory->string, (u8*)"\\");
|
|
}
|
|
|
|
internal void
|
|
hot_directory_reload_list(Hot_Directory *hot_directory){
|
|
if (hot_directory->file_list.block){
|
|
system_free_file_list(hot_directory->file_list);
|
|
}
|
|
hot_directory->file_list = system_get_files(hot_directory->string);
|
|
}
|
|
|
|
internal bool32
|
|
hot_directory_set(Hot_Directory *hot_directory, String str){
|
|
bool32 did_set = 0;
|
|
if (copy_checked(&hot_directory->string, str)){
|
|
did_set = 1;
|
|
system_free_file_list(hot_directory->file_list);
|
|
hot_directory->file_list = system_get_files(str);
|
|
}
|
|
return did_set;
|
|
}
|
|
|
|
struct Hot_Directory_Match{
|
|
u8 *filename;
|
|
bool32 is_folder;
|
|
};
|
|
|
|
internal Hot_Directory_Match
|
|
hot_directory_first_match(Hot_Directory *hot_directory,
|
|
String str,
|
|
bool32 include_files,
|
|
bool32 exact_match){
|
|
Hot_Directory_Match result = {};
|
|
|
|
File_List files = hot_directory->file_list;
|
|
File_List_Iterator files_it = files_iterator_init(&files);
|
|
|
|
while (files_it.filename_ptr &&
|
|
(include_files || files_it.folder_stage)){
|
|
u8 *filename = *files_it.filename_ptr;
|
|
|
|
bool32 is_match = 0;
|
|
if (exact_match){
|
|
if (match(filename, str)){
|
|
is_match = 1;
|
|
}
|
|
}
|
|
else{
|
|
if (str.size == 0 ||
|
|
has_substr_unsensitive(filename, str)){
|
|
is_match = 1;
|
|
}
|
|
}
|
|
|
|
if (is_match){
|
|
result.is_folder = files_it.folder_stage;
|
|
result.filename = filename;
|
|
break;
|
|
}
|
|
|
|
files_iterator_step(&files, &files_it);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* App Structs
|
|
*/
|
|
|
|
struct Command_Data;
|
|
typedef void (*Command_Function)(Command_Data *command);
|
|
|
|
enum Input_Request_Type{
|
|
REQUEST_SYS_FILE,
|
|
REQUEST_LIVE_FILE,
|
|
// never below this
|
|
REQUEST_COUNT
|
|
};
|
|
|
|
struct Input_Request{
|
|
Input_Request_Type type;
|
|
union{
|
|
struct{
|
|
String query;
|
|
String dest;
|
|
bool32 hit_ctrl_newline;
|
|
bool32 fast_folder;
|
|
} sys_file;
|
|
|
|
struct{
|
|
String query;
|
|
String dest;
|
|
bool32 hit_ctrl_newline;
|
|
} live_file;
|
|
};
|
|
};
|
|
|
|
enum App_State{
|
|
APP_STATE_EDIT,
|
|
APP_STATE_SEARCH,
|
|
APP_STATE_RESIZING,
|
|
// never below this
|
|
APP_STATE_COUNT
|
|
};
|
|
|
|
struct App_State_Incremental_Search{
|
|
String str;
|
|
bool32 reverse;
|
|
i32 pos;
|
|
};
|
|
|
|
struct App_State_Resizing{
|
|
Editing_Panel *left, *right;
|
|
};
|
|
|
|
struct Command_Map{
|
|
Command_Function basic_mode_key[1+sizeof(Key_Codes)/2];
|
|
Command_Function control_ascii[128];
|
|
Command_Function control_key[1+sizeof(Key_Codes)/2];
|
|
};
|
|
|
|
struct App_Vars{
|
|
Command_Map map;
|
|
|
|
Font font;
|
|
Editing_Style style;
|
|
Interactive_Style command_style;
|
|
|
|
Editing_Working_Set working_set;
|
|
|
|
Editing_Layout layout;
|
|
real32 last_click_x, last_click_y;
|
|
|
|
Hot_Directory hot_directory;
|
|
|
|
Input_Request request_queue[16];
|
|
i32 request_count, request_filled, request_max;
|
|
|
|
Command_Function pending_command;
|
|
|
|
App_State state;
|
|
union{
|
|
App_State_Incremental_Search isearch;
|
|
App_State_Resizing resizing;
|
|
};
|
|
};
|
|
|
|
/*
|
|
* Commands
|
|
*/
|
|
|
|
struct Command_Data{
|
|
Editing_Panel *panel;
|
|
Editing_Working_Set *working_set;
|
|
Editing_Layout *layout;
|
|
Editing_Style *style;
|
|
App_Vars *vars;
|
|
|
|
i32 screen_width, screen_height;
|
|
i32 screen_y_off;
|
|
Key_Event_Data key;
|
|
|
|
Input_Request *requests;
|
|
i32 request_count, request_filled;
|
|
};
|
|
|
|
internal void
|
|
app_clear_request_queue(App_Vars *vars){
|
|
for (i32 i = 0; i < vars->request_count; ++i){
|
|
Input_Request *request = vars->request_queue + i;
|
|
switch (request->type){
|
|
case REQUEST_SYS_FILE:
|
|
{
|
|
system_free_memory(request->sys_file.query.str);
|
|
system_free_memory(request->sys_file.dest.str);
|
|
}break;
|
|
|
|
case REQUEST_LIVE_FILE:
|
|
{
|
|
system_free_memory(request->live_file.query.str);
|
|
system_free_memory(request->live_file.dest.str);
|
|
}break;
|
|
}
|
|
}
|
|
vars->request_count = 0;
|
|
vars->request_filled = 0;
|
|
}
|
|
|
|
internal void
|
|
command_write_character(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
panel_write_character(panel, (u8)command->key.character);
|
|
if (panel->unwrapped_lines){
|
|
panel->preferred_x = panel->cursor.unwrapped_x;
|
|
}
|
|
else{
|
|
panel->preferred_x = panel->cursor.wrapped_x;
|
|
}
|
|
panel->file->cursor.pos = panel->cursor.pos;
|
|
|
|
switch ((u8)command->key.character){
|
|
case '{': case '}':
|
|
case '(': case ')':
|
|
case ';': case ':':
|
|
case '#': case '\n':
|
|
{
|
|
panel_auto_tab(panel, panel->cursor.pos, panel->cursor.pos);
|
|
}break;
|
|
}
|
|
panel_measure_all_wrapped_y(panel);
|
|
}
|
|
|
|
internal void
|
|
command_move_left(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
u8 *data = (u8*)file->data;
|
|
|
|
i32 pos = panel->cursor.pos;
|
|
if (pos > 0){
|
|
if (file->endline_mode == ENDLINE_RN_COMBINED){
|
|
pos = pos_adjust_to_left(pos, data);
|
|
if (pos > 0){
|
|
--pos;
|
|
}
|
|
else{
|
|
pos = pos_adjust_to_self(pos, data, file->size);
|
|
}
|
|
}
|
|
else{
|
|
--pos;
|
|
}
|
|
}
|
|
|
|
panel_cursor_move(panel, pos);
|
|
}
|
|
|
|
internal void
|
|
command_move_right(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
i32 size = file->size;
|
|
u8* data = (u8*)file->data;
|
|
|
|
i32 pos = panel->cursor.pos;
|
|
if (pos < size){
|
|
++pos;
|
|
if (file->endline_mode == ENDLINE_RN_COMBINED){
|
|
pos = pos_adjust_to_self(pos, data, size);
|
|
}
|
|
}
|
|
|
|
panel_cursor_move(panel, pos);
|
|
}
|
|
|
|
internal void
|
|
command_backspace(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
i32 cursor_pos = panel->cursor.pos;
|
|
Editing_File *file = panel->file;
|
|
i8 *data = (i8*)file->data;
|
|
|
|
if (cursor_pos > 0 && cursor_pos <= (i32)file->size){
|
|
|
|
if (file->endline_mode == ENDLINE_RN_COMBINED){
|
|
i32 target_pos = cursor_pos;
|
|
if (cursor_pos > 1 &&
|
|
data[cursor_pos] == '\n' &&
|
|
data[cursor_pos-1] == '\r'){
|
|
--target_pos;
|
|
}
|
|
|
|
if (target_pos > 1 &&
|
|
data[target_pos-1] == '\n' &&
|
|
data[target_pos-2] == '\r'){
|
|
|
|
buffer_delete_range(file, target_pos-2, target_pos);
|
|
if (panel->mark >= cursor_pos){
|
|
panel->mark -= 2;
|
|
}
|
|
cursor_pos -= 2;
|
|
}
|
|
else{
|
|
if (target_pos > 0){
|
|
buffer_delete(file, target_pos-1);
|
|
if (panel->mark >= cursor_pos){
|
|
--panel->mark;
|
|
}
|
|
--cursor_pos;
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
buffer_delete(file, cursor_pos-1);
|
|
if (panel->mark >= cursor_pos){
|
|
--panel->mark;
|
|
}
|
|
--cursor_pos;
|
|
}
|
|
|
|
panel_measure_all_wrapped_y(panel);
|
|
panel_cursor_move(panel, cursor_pos);
|
|
}
|
|
}
|
|
internal void
|
|
command_delete(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
i32 cursor_pos = panel->cursor.pos;
|
|
Editing_File *file = panel->file;
|
|
i8 *data = (i8*)file->data;
|
|
|
|
if (file->size > 0){
|
|
if (file->endline_mode == ENDLINE_RN_COMBINED){
|
|
if (cursor_pos > 0 &&
|
|
data[cursor_pos-1] == '\r' &&
|
|
data[cursor_pos] == '\n'){
|
|
buffer_delete_range(file, cursor_pos-1, cursor_pos+1);
|
|
if (panel->mark > cursor_pos){
|
|
panel->mark -= 2;
|
|
}
|
|
cursor_pos -= 1;
|
|
}
|
|
|
|
else{
|
|
buffer_delete(file, cursor_pos);
|
|
if (panel->mark > cursor_pos){
|
|
--panel->mark;
|
|
}
|
|
}
|
|
}
|
|
|
|
else{
|
|
buffer_delete(file, cursor_pos);
|
|
if (panel->mark > cursor_pos){
|
|
--panel->mark;
|
|
}
|
|
}
|
|
|
|
panel_measure_all_wrapped_y(panel);
|
|
panel_cursor_move(panel, cursor_pos);
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_move_up(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
i32 cy = panel_get_cursor_y(panel)-1;
|
|
i32 px = panel->preferred_x;
|
|
if (cy >= 0){
|
|
panel->cursor = panel_compute_cursor_from_xy(panel, px, cy);
|
|
panel->file->cursor.pos = panel->cursor.pos;
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_move_down(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
i32 cy = panel_get_cursor_y(panel)+1;
|
|
i32 px = panel->preferred_x;
|
|
|
|
panel->cursor = panel_compute_cursor_from_xy(panel, px, cy);
|
|
panel->file->cursor.pos = panel->cursor.pos;
|
|
}
|
|
|
|
internal void
|
|
command_seek_end_of_line(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
|
|
i32 pos = panel_find_end_of_line(panel, panel->cursor.pos);
|
|
|
|
panel_cursor_move(panel, pos);
|
|
}
|
|
|
|
internal void
|
|
command_seek_beginning_of_line(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
|
|
i32 pos = panel_find_beginning_of_line(panel, panel->cursor.pos);
|
|
|
|
panel_cursor_move(panel, pos);
|
|
}
|
|
|
|
internal void
|
|
command_seek_whitespace_right(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
u32 size = file->size;
|
|
u8* data = (u8*)file->data;
|
|
|
|
u32 pos = panel->cursor.pos;
|
|
while (pos < size && character_is_any_whitespace(data[pos])){
|
|
++pos;
|
|
}
|
|
|
|
while (pos < size && !character_is_any_whitespace(data[pos])){
|
|
++pos;
|
|
}
|
|
|
|
panel_cursor_move(panel, pos);
|
|
}
|
|
|
|
internal void
|
|
command_seek_whitespace_left(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
u8* data = (u8*)file->data;
|
|
|
|
u32 pos = panel->cursor.pos;
|
|
--pos;
|
|
while (pos > 0 && character_is_any_whitespace(data[pos])){
|
|
--pos;
|
|
}
|
|
|
|
while (pos > 0 && !character_is_any_whitespace(data[pos])){
|
|
--pos;
|
|
}
|
|
|
|
if (pos != 0){
|
|
++pos;
|
|
}
|
|
|
|
panel_cursor_move(panel, pos);
|
|
}
|
|
|
|
internal void
|
|
command_seek_whitespace_up(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
u8* data = (u8*)file->data;
|
|
|
|
u32 pos = panel->cursor.pos;
|
|
while (pos > 0 && character_is_any_whitespace(data[pos])){
|
|
--pos;
|
|
}
|
|
|
|
bool32 no_hard_character = 0;
|
|
while (pos > 0){
|
|
if (starts_new_line(data[pos], file->endline_mode)){
|
|
if (no_hard_character){
|
|
break;
|
|
}
|
|
else{
|
|
no_hard_character = 1;
|
|
}
|
|
}
|
|
else{
|
|
if (!character_is_any_whitespace(data[pos])){
|
|
no_hard_character = 0;
|
|
}
|
|
}
|
|
--pos;
|
|
}
|
|
|
|
if (pos != 0){
|
|
++pos;
|
|
}
|
|
|
|
if (file->endline_mode == ENDLINE_RN_COMBINED){
|
|
pos = pos_adjust_to_self(pos, data, file->size);
|
|
}
|
|
|
|
panel_cursor_move(panel, pos);
|
|
}
|
|
|
|
internal void
|
|
command_seek_whitespace_down(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
i32 size = file->size;
|
|
u8* data = (u8*)file->data;
|
|
|
|
i32 pos = panel->cursor.pos;
|
|
while (pos < size && character_is_any_whitespace(data[pos])){
|
|
++pos;
|
|
}
|
|
|
|
bool32 no_hard_character = 0;
|
|
i32 prev_endline = -1;
|
|
while (pos < size){
|
|
if (starts_new_line(data[pos], file->endline_mode)){
|
|
if (no_hard_character){
|
|
break;
|
|
}
|
|
else{
|
|
no_hard_character = 1;
|
|
prev_endline = pos;
|
|
}
|
|
}
|
|
else{
|
|
if (!character_is_any_whitespace(data[pos])){
|
|
no_hard_character = 0;
|
|
}
|
|
}
|
|
++pos;
|
|
}
|
|
|
|
if (prev_endline == -1 || prev_endline+1 >= size){
|
|
pos = size-1;
|
|
}
|
|
else{
|
|
pos = prev_endline+1;
|
|
}
|
|
|
|
panel_cursor_move(panel, pos);
|
|
}
|
|
|
|
internal void
|
|
command_seek_token_left(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
TentativeAssert(file->tokens_exist);
|
|
i32 current_token;
|
|
|
|
Cpp_Get_Token_Result get_result = cpp_get_token(&file->token_stack, panel->cursor.pos);
|
|
current_token = get_result.token_index;
|
|
|
|
// TODO(allen): Make nulltoken?
|
|
if (current_token == -1){
|
|
current_token = 0;
|
|
}
|
|
Cpp_Token *token = &file->token_stack.tokens[current_token];
|
|
if (token->start == panel->cursor.pos && current_token > 0){
|
|
--token;
|
|
}
|
|
|
|
panel_cursor_move(panel, token->start);
|
|
}
|
|
|
|
internal void
|
|
command_seek_token_right(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
TentativeAssert(file->tokens_exist);
|
|
// TODO(allen): Make nulltoken?
|
|
i32 current_token;
|
|
|
|
Cpp_Get_Token_Result get_result = cpp_get_token(&file->token_stack, panel->cursor.pos);
|
|
current_token = get_result.token_index;
|
|
if (get_result.in_whitespace){
|
|
++current_token;
|
|
}
|
|
|
|
if (current_token >= file->token_stack.count){
|
|
current_token = file->token_stack.count - 1;
|
|
}
|
|
|
|
Cpp_Token *token = &file->token_stack.tokens[current_token];
|
|
|
|
panel_cursor_move(panel, token->start + token->size);
|
|
}
|
|
|
|
internal void
|
|
command_begin_search_state(Command_Data *command){
|
|
App_Vars *vars = command->vars;
|
|
vars->state = APP_STATE_SEARCH;
|
|
vars->isearch.str =
|
|
make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
|
|
vars->isearch.reverse = 0;
|
|
vars->isearch.pos = command->panel->cursor.pos;
|
|
}
|
|
|
|
internal void
|
|
command_begin_rsearch_state(Command_Data *command){
|
|
App_Vars *vars = command->vars;
|
|
vars->state = APP_STATE_SEARCH;
|
|
vars->isearch.str =
|
|
make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
|
|
vars->isearch.reverse = 1;
|
|
vars->isearch.pos = command->panel->cursor.pos;
|
|
}
|
|
|
|
internal void
|
|
command_set_mark(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
panel->mark = (i32)panel->cursor.pos;
|
|
}
|
|
|
|
internal void
|
|
command_copy(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_Working_Set *working_set = command->working_set;
|
|
Range range = get_range(panel->cursor.pos, panel->mark);
|
|
if (range.smaller < range.larger){
|
|
u8 *data = (u8*)panel->file->data;
|
|
if (panel->file->endline_mode == ENDLINE_RN_COMBINED){
|
|
range = range_adjust_to_left(range, data);
|
|
}
|
|
clipboard_copy(working_set, data, range);
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_cut(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_Working_Set *working_set = command->working_set;
|
|
Range range = get_range(panel->cursor.pos, panel->mark);
|
|
if (range.smaller < range.larger){
|
|
Editing_File *file = panel->file;
|
|
u8 *data = (u8*)file->data;
|
|
if (file->endline_mode == ENDLINE_RN_COMBINED){
|
|
range = range_adjust_to_left(range, data);
|
|
}
|
|
clipboard_copy(working_set, data, range);
|
|
buffer_delete_range(file, range);
|
|
panel->mark = pos_universal_fix(range.smaller,
|
|
file->data, file->size,
|
|
file->endline_mode);
|
|
|
|
panel_measure_all_wrapped_y(panel);
|
|
panel_cursor_move(panel, panel->mark);
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_paste(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_Working_Set *working_set = command->working_set;
|
|
if (working_set->clipboard_size > 0){
|
|
panel->next_mode.rewrite = 1;
|
|
|
|
Editing_File *file = panel->file;
|
|
|
|
String *src = working_set_clipboard_head(working_set);
|
|
i32 pos_left = panel->cursor.pos;
|
|
if (file->endline_mode == ENDLINE_RN_COMBINED){
|
|
pos_left = pos_adjust_to_left(pos_left, file->data);
|
|
}
|
|
panel_write_chunk(panel, src, pos_left);
|
|
panel_measure_all_wrapped_y(panel);
|
|
panel->mark = pos_universal_fix(pos_left,
|
|
file->data, file->size,
|
|
file->endline_mode);
|
|
|
|
i32 ticks = 20;
|
|
panel->paste_effect.start = pos_left;
|
|
panel->paste_effect.end = pos_left + src->size;
|
|
panel->paste_effect.color = file->style->paste_color;
|
|
panel->paste_effect.tick_down = ticks;
|
|
panel->paste_effect.tick_max = ticks;
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_paste_next(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_Working_Set *working_set = command->working_set;
|
|
if (working_set->clipboard_size > 0 && panel->mode.rewrite){
|
|
panel->next_mode.rewrite = 1;
|
|
|
|
Range range = get_range(panel->mark, panel->cursor.pos);
|
|
|
|
if (range.smaller < range.larger){
|
|
Editing_File *file = panel->file;
|
|
if (file->endline_mode == ENDLINE_RN_COMBINED){
|
|
range = range_adjust_to_left(range, file->data);
|
|
}
|
|
|
|
String *src = working_set_clipboard_roll_down(working_set);
|
|
buffer_replace_range(file, range.smaller, range.larger,
|
|
src->str, src->size);
|
|
|
|
panel->cursor = panel_compute_cursor_from_pos(panel, range.smaller+src->size);
|
|
panel->preferred_x = panel_get_cursor_x(panel);
|
|
panel->file->cursor.pos = panel->cursor.pos;
|
|
|
|
// TODO(allen): faster way to recompute line measurements afterwards
|
|
buffer_measure_all_lines(file);
|
|
panel_measure_all_wrapped_y(panel);
|
|
panel->mark = pos_universal_fix(range.smaller,
|
|
file->data, file->size,
|
|
file->endline_mode);
|
|
|
|
i32 ticks = 20;
|
|
panel->paste_effect.start = range.smaller;
|
|
panel->paste_effect.end = range.smaller + src->size;
|
|
panel->paste_effect.color = file->style->paste_color;
|
|
panel->paste_effect.tick_down = ticks;
|
|
panel->paste_effect.tick_max = ticks;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_delete_chunk(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Range range = get_range(panel->cursor.pos, panel->mark);
|
|
if (range.smaller < range.larger){
|
|
Editing_File *file = panel->file;
|
|
if (file->endline_mode == ENDLINE_RN_COMBINED){
|
|
range = range_adjust_to_left(range, file->data);
|
|
}
|
|
buffer_delete_range(file, range);
|
|
panel_measure_all_wrapped_y(panel);
|
|
panel->cursor = panel_compute_cursor_from_pos(panel, range.smaller);
|
|
panel->mark = pos_universal_fix(range.smaller,
|
|
file->data, file->size,
|
|
file->endline_mode);
|
|
panel->file->cursor.pos = panel->cursor.pos;
|
|
}
|
|
}
|
|
|
|
// TODO(allen): Make this preserve order (look at layout_open_panel)
|
|
// Make this preserve old ratios or sizes of panels instead of
|
|
// resizing them all.
|
|
internal void
|
|
command_open_panel(Command_Data *command){
|
|
Editing_Layout *layout = command->layout;
|
|
Editing_Working_Set *working_set = command->working_set;
|
|
// TODO(allen): This is wrong. We should not be passing in the style
|
|
// like this. The system should be looking it up here.
|
|
Editing_Style *style = command->style;
|
|
// TODO(allen): change the screen info to real32 at the base level?
|
|
real32 screen_full_width = (real32)command->screen_width;
|
|
real32 screen_full_height = (real32)command->screen_height;
|
|
real32 screen_y_off = (real32)command->screen_y_off;
|
|
|
|
layout_open_panel(layout, working_set->files, style);
|
|
real32 panel_w = ((real32)screen_full_width / layout->panel_count);
|
|
real32 panel_x_pos = 0;
|
|
|
|
for (i32 panel_i = 0; panel_i < layout->panel_count; ++panel_i){
|
|
Editing_Panel *panel = &layout->panels[panel_i];
|
|
|
|
panel->full_x = Floor(panel_x_pos);
|
|
panel->full_w = Floor(panel_w);
|
|
panel->full_y = Floor(screen_y_off);
|
|
panel->full_h = Floor(screen_full_height - screen_y_off);
|
|
|
|
panel_fix_internal_area(panel);
|
|
|
|
panel_x_pos += panel_w;
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_close_panel(Command_Data *command){
|
|
Editing_Layout *layout = command->layout;
|
|
i32 share_w = layout->panels[layout->active_panel].full_w;
|
|
layout_close_panel(layout, layout->active_panel);
|
|
layout_redistribute_width(layout, share_w);
|
|
}
|
|
|
|
internal Input_Request*
|
|
app_request_sys_file(App_Vars *vars, String query, String dest_init, bool32 fast_folder){
|
|
Input_Request *request = vars->request_queue + (vars->request_count++);
|
|
*request = {};
|
|
request->type = REQUEST_SYS_FILE;
|
|
request->sys_file.fast_folder = 1;
|
|
|
|
// TODO(allen): Where does this memory come from IRL? I don't like calling to
|
|
// a system function all the time, knowing it might start doing weird things.
|
|
// Better to put some limitations on the system so we can gaurantee the memory
|
|
// this needs will exist.
|
|
request->sys_file.query =
|
|
make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
|
|
request->sys_file.dest =
|
|
make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
|
|
|
|
copy(&request->sys_file.query, query);
|
|
copy(&request->sys_file.dest, dest_init);
|
|
return request;
|
|
}
|
|
|
|
|
|
internal Input_Request*
|
|
app_request_live_file(App_Vars *vars, String query, String dest_init){
|
|
Input_Request *request = vars->request_queue + (vars->request_count++);
|
|
*request = {};
|
|
request->type = REQUEST_LIVE_FILE;
|
|
|
|
// TODO(allen): Where does this memory come from IRL? I don't like calling to
|
|
// a system function all the time, knowing it might start doing weird things.
|
|
// Better to put some limitations on the system so we can gaurantee the memory
|
|
// this needs will exist.
|
|
request->live_file.query =
|
|
make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
|
|
request->live_file.dest =
|
|
make_string((u8*)system_get_memory(256, SYSTEM_MEM_SMALL_STRING), 0, 256);
|
|
|
|
copy(&request->live_file.query, query);
|
|
copy(&request->live_file.dest, dest_init);
|
|
return request;
|
|
}
|
|
|
|
internal void
|
|
panel_set_to_new(Editing_Panel *panel){
|
|
panel->cursor = {};
|
|
panel->cursor.pos =
|
|
pos_adjust_to_self(0, panel->file->data, panel->file->size);
|
|
panel->scroll_y = 0;
|
|
panel->target_y = 0;
|
|
panel->vel_y = 1.f;
|
|
panel->scroll_x = 0;
|
|
panel->target_x = 0;
|
|
panel->vel_x = 1.f;
|
|
}
|
|
|
|
internal void
|
|
command_reopen(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
if (!file->is_dummy){
|
|
Editing_File temp_file;
|
|
if (buffer_load(&temp_file, (u8*)make_c_str(file->source_path))){
|
|
buffer_close(file);
|
|
|
|
// TODO(allen): Deduplicate!!
|
|
Cpp_File cpp_file;
|
|
cpp_file.data = temp_file.data;
|
|
cpp_file.size = temp_file.size;
|
|
{
|
|
i32 size = cpp_lex_file_token_count(cpp_file);
|
|
size = cpp_get_token_stack_size(size);
|
|
temp_file.token_stack = cpp_make_token_stack(size);
|
|
}
|
|
cpp_lex_file(cpp_file, &temp_file.token_stack);
|
|
temp_file.tokens_complete = 1;
|
|
temp_file.tokens_exist = 1;
|
|
|
|
*file = temp_file;
|
|
panel_set_to_new(panel);
|
|
panel_measure_all_wrapped_y(panel);
|
|
// TODO(allen): Update all other panels that also view this file.
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_interactive_open(Command_Data *command){
|
|
App_Vars *vars = command->vars;
|
|
if (command->request_count == 0){
|
|
// TODO(allen): Can all of this be simplified? I don't like having this much
|
|
// complexity for issuing a single request.
|
|
vars->pending_command = command_interactive_open;
|
|
|
|
Assert(vars->request_count == 0);
|
|
app_request_sys_file(vars, make_lit_string("Open: "), vars->hot_directory.string, 1);
|
|
|
|
hot_directory_reload_list(&vars->hot_directory);
|
|
}
|
|
else{
|
|
String *string = &command->requests[0].sys_file.dest;
|
|
Editing_File *new_file;
|
|
new_file = buffer_open(&vars->working_set, string->str, command->style);
|
|
|
|
if (!new_file){
|
|
// TODO(allen): Here's a really tough one. This crap is now happening twice.
|
|
// Once here and also in the single_file_input function which checks all of it
|
|
// whenever the fast_folder_select option is on to see if the user is selecting
|
|
// a folder. It would be nice to be able to share that information to here when it
|
|
// is available so we could avoid doing the exact same computations here.
|
|
u8 front_name_space[256];
|
|
String front_name =
|
|
make_string(front_name_space, 0, ArrayCount(front_name_space));
|
|
get_front_of_directory(&front_name, *string);
|
|
|
|
Hot_Directory_Match match =
|
|
hot_directory_first_match(&vars->hot_directory,
|
|
front_name, 1, 0);
|
|
|
|
if (match.filename){
|
|
// NOTE(allen): is_folder case is handled by the single_file_input function
|
|
// which would only pass control to this command if the user did not match to
|
|
// a folder.
|
|
Assert(!match.is_folder);
|
|
new_file = buffer_open(&vars->working_set, string->str, command->style);
|
|
}
|
|
}
|
|
|
|
if (new_file){
|
|
Editing_Panel *active_panel = command->panel;
|
|
active_panel->file = new_file;
|
|
panel_set_to_new(active_panel);
|
|
panel_measure_all_wrapped_y(active_panel);
|
|
|
|
Cpp_File file;
|
|
file.data = new_file->data;
|
|
file.size = new_file->size;
|
|
// TODO(allen): Where should the memory for tokens come from IRL?
|
|
{
|
|
i32 size = cpp_lex_file_token_count(file);
|
|
size = cpp_get_token_stack_size(size);
|
|
new_file->token_stack = cpp_make_token_stack(size);
|
|
}
|
|
cpp_lex_file(file, &new_file->token_stack);
|
|
new_file->tokens_complete = 1;
|
|
new_file->tokens_exist = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_save(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
String *file_path = &panel->file->source_path;
|
|
if (file_path->size > 0){
|
|
buffer_save(panel->file, file_path->str);
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_interactive_save_as(Command_Data *command){
|
|
App_Vars *vars = command->vars;
|
|
if (command->request_count == 0){
|
|
vars->pending_command = command_interactive_save_as;
|
|
|
|
Assert(vars->request_count == 0);
|
|
app_request_sys_file(vars, make_lit_string("Save As: "), vars->hot_directory.string, 1);
|
|
|
|
hot_directory_reload_list(&vars->hot_directory);
|
|
}
|
|
else{
|
|
String *string = &command->requests[0].sys_file.dest;
|
|
|
|
buffer_save_and_set_names(command->panel->file, string->str);
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_change_active_panel(Command_Data *command){
|
|
Editing_Layout *layout = command->layout;
|
|
if (layout->panel_count > 1){
|
|
++layout->active_panel;
|
|
if (layout->active_panel >= layout->panel_count){
|
|
layout->active_panel = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_interactive_switch_file(Command_Data *command){
|
|
App_Vars *vars = command->vars;
|
|
if (command->request_count == 0){
|
|
vars->pending_command = command_interactive_switch_file;
|
|
|
|
Assert(vars->request_count == 0);
|
|
app_request_live_file(vars, make_lit_string("Switch To: "), make_lit_string(""));
|
|
}
|
|
else{
|
|
String *string = &command->requests[0].live_file.dest;
|
|
|
|
Editing_File *file;
|
|
file = working_set_lookup_file(command->working_set, *string);
|
|
|
|
if (file){
|
|
Editing_Panel *active_panel = command->panel;
|
|
active_panel->file = file;
|
|
Panel_Cursor_Data cursor_data;
|
|
cursor_data = panel_compute_cursor_from_pos(active_panel, file->cursor.pos);
|
|
active_panel->cursor = cursor_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_kill_file(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
if (file && file->max_size != 0){
|
|
buffer_close(file);
|
|
buffer_get_dummy(file, command->style);
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_interactive_kill_file(Command_Data *command){
|
|
App_Vars *vars = command->vars;
|
|
if (command->request_count == 0){
|
|
vars->pending_command = command_interactive_kill_file;
|
|
|
|
Assert(vars->request_count == 0);
|
|
app_request_live_file(vars, make_lit_string("Kill: "), make_lit_string(""));
|
|
}
|
|
else{
|
|
String *string = &command->requests[0].live_file.dest;
|
|
|
|
Editing_File *file;
|
|
file = working_set_lookup_file(&vars->working_set, *string);
|
|
|
|
if (file){
|
|
buffer_close(file);
|
|
buffer_get_dummy(file, command->style);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_toggle_line_wrap(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
if (panel->unwrapped_lines){
|
|
panel->unwrapped_lines = 0;
|
|
panel->target_x = 0;
|
|
panel->cursor =
|
|
panel_compute_cursor_from_pos(panel, panel->cursor.pos);
|
|
}
|
|
else{
|
|
panel->unwrapped_lines = 1;
|
|
panel->cursor =
|
|
panel_compute_cursor_from_pos(panel, panel->cursor.pos);
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_toggle_endline_mode(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
switch (file->endline_mode){
|
|
case ENDLINE_RN_COMBINED:
|
|
{
|
|
panel->cursor.pos = pos_adjust_to_left(panel->cursor.pos, panel->file->data);
|
|
file->endline_mode = ENDLINE_RN_SEPARATE;
|
|
panel->cursor =
|
|
panel_compute_cursor_from_pos(panel, panel->cursor.pos);
|
|
}break;
|
|
|
|
case ENDLINE_RN_SEPARATE:
|
|
{
|
|
file->endline_mode = ENDLINE_RN_SHOWALLR;
|
|
panel->cursor =
|
|
panel_compute_cursor_from_pos(panel, panel->cursor.pos);
|
|
}break;
|
|
|
|
case ENDLINE_RN_SHOWALLR:
|
|
{
|
|
panel->cursor.pos = pos_adjust_to_self(panel->cursor.pos, panel->file->data,
|
|
panel->file->size);
|
|
file->endline_mode = ENDLINE_RN_COMBINED;
|
|
panel->cursor =
|
|
panel_compute_cursor_from_pos(panel, panel->cursor.pos);
|
|
}break;
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_to_uppercase(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Range range = get_range(panel->cursor.pos, panel->mark);
|
|
if (range.smaller < range.larger){
|
|
Editing_File *file = panel->file;
|
|
u8 *data = file->data;
|
|
for (i32 i = range.smaller; i < range.larger; ++i){
|
|
if (data[i] >= 'a' && data[i] <= 'z'){
|
|
data[i] += (u8)('A' - 'a');
|
|
}
|
|
}
|
|
// TODO(allen): RELEX POINT
|
|
if (file->token_stack.tokens){
|
|
Cpp_File cpp_file;
|
|
cpp_file.size = file->size;
|
|
cpp_file.data = (u8*)file->data;
|
|
cpp_relex_file(cpp_file, &file->token_stack, range.smaller, range.larger, 0);
|
|
file->tokens_complete = 1;
|
|
file->tokens_exist = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_to_lowercase(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Range range = get_range(panel->cursor.pos, panel->mark);
|
|
if (range.smaller < range.larger){
|
|
Editing_File *file = panel->file;
|
|
u8 *data = file->data;
|
|
for (i32 i = range.smaller; i < range.larger; ++i){
|
|
if (data[i] >= 'A' && data[i] <= 'Z'){
|
|
data[i] -= (u8)('A' - 'a');
|
|
}
|
|
}
|
|
// TODO(allen): RELEX POINT
|
|
if (file->token_stack.tokens){
|
|
Cpp_File cpp_file;
|
|
cpp_file.size = file->size;
|
|
cpp_file.data = (u8*)file->data;
|
|
cpp_relex_file(cpp_file, &file->token_stack, range.smaller, range.larger, 0);
|
|
file->tokens_complete = 1;
|
|
file->tokens_exist = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_toggle_show_whitespace(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
panel->show_whitespace = !panel->show_whitespace;
|
|
}
|
|
|
|
internal void
|
|
command_clean_line(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
u8 *data = (u8*)file->data;
|
|
|
|
i32 pos = panel_find_beginning_of_line(panel, panel->cursor.pos);
|
|
|
|
i32 last_hard_start = pos-1;
|
|
i32 last_hard = last_hard_start;
|
|
while (pos < file->size && data[pos] != '\n'){
|
|
if (!character_is_any_whitespace(data[pos])){
|
|
last_hard = pos;
|
|
}
|
|
++pos;
|
|
}
|
|
|
|
if (last_hard != last_hard_start){
|
|
pos = pos_adjust_to_left(pos, data);
|
|
|
|
if (last_hard + 1 < pos){
|
|
buffer_replace_range(file, last_hard+1, pos, 0, 0, REP_WHITESPACE);
|
|
panel_measure_all_wrapped_y(panel);
|
|
|
|
if (panel->cursor.pos > last_hard){
|
|
panel->cursor = panel_compute_cursor_from_pos(panel, last_hard + 1);
|
|
}
|
|
if (panel->mark > last_hard && panel->mark <= pos){
|
|
panel->mark = pos_adjust_to_self(last_hard+1, file->data, file->size);
|
|
}
|
|
else if (panel->mark > pos){
|
|
panel->mark -= pos - (last_hard + 1);
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
panel_measure_all_wrapped_y(panel);
|
|
panel_auto_tab(panel, pos, pos);
|
|
}
|
|
}
|
|
|
|
internal void
|
|
command_clean_all_lines(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Editing_File *file = panel->file;
|
|
u8 *data = (u8*)file->data;
|
|
|
|
i32 cursor_pos = panel->cursor.pos;
|
|
i32 pos = 0;
|
|
i32 last_hard = -1;
|
|
bool32 is_all_white = 1;
|
|
while (pos <= file->size){
|
|
if (pos == file->size || data[pos] == '\n'){
|
|
i32 line_pos = pos;
|
|
if (pos < file->size && data[pos] == '\n'){
|
|
line_pos = pos_adjust_to_left(pos, data);
|
|
}
|
|
|
|
// TODO(allen): This should be optimized by either keeping track of the nesting level
|
|
// in this funciton, or by at least having a nesting hint that is used and updated
|
|
// every time an auto tab happens. Also auto tab should take a nesting hint.
|
|
if (is_all_white){
|
|
panel_auto_tab(panel, pos, pos);
|
|
}
|
|
else{
|
|
if (last_hard + 1 < line_pos){
|
|
buffer_replace_range(file, last_hard+1, line_pos, 0, 0, REP_WHITESPACE);
|
|
|
|
if (cursor_pos > last_hard && cursor_pos <= pos){
|
|
cursor_pos = pos_adjust_to_self(last_hard+1, file->data, file->size);
|
|
}
|
|
else if (cursor_pos > pos){
|
|
cursor_pos -= line_pos - (last_hard + 1);
|
|
}
|
|
if (panel->mark > last_hard && panel->mark <= pos){
|
|
panel->mark = pos_adjust_to_self(last_hard+1, file->data, file->size);
|
|
}
|
|
else if (panel->mark > pos){
|
|
panel->mark -= line_pos - (last_hard + 1);
|
|
}
|
|
pos -= line_pos - (last_hard + 1);
|
|
}
|
|
}
|
|
|
|
last_hard = pos;
|
|
is_all_white = 1;
|
|
}
|
|
else if (!character_is_any_whitespace(data[pos])){
|
|
last_hard = pos;
|
|
is_all_white = 0;
|
|
}
|
|
++pos;
|
|
}
|
|
panel_measure_all_wrapped_y(panel);
|
|
panel->cursor = panel_compute_cursor_from_pos(panel, cursor_pos);
|
|
}
|
|
|
|
internal void
|
|
command_eol_dosify(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
panel_endline_convert(panel, ENDLINE_RN, ENDLINE_ERASE, ENDLINE_RN);
|
|
panel_measure_all_wrapped_y(panel);
|
|
}
|
|
|
|
internal void
|
|
command_eol_nixify(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
panel_endline_convert(panel, ENDLINE_N, ENDLINE_ERASE, ENDLINE_N);
|
|
panel_measure_all_wrapped_y(panel);
|
|
}
|
|
|
|
internal void
|
|
command_auto_tab(Command_Data *command){
|
|
Editing_Panel *panel = command->panel;
|
|
Range range = get_range(panel->cursor.pos, panel->mark);
|
|
panel_auto_tab(panel, range.smaller, range.larger);
|
|
panel_measure_all_wrapped_y(panel);
|
|
}
|
|
|
|
internal void
|
|
setup_commands(Command_Map *commands, Key_Codes *codes){
|
|
commands->basic_mode_key[codes->left] = command_move_left;
|
|
commands->basic_mode_key[codes->right] = command_move_right;
|
|
commands->basic_mode_key[codes->del] = command_delete;
|
|
commands->basic_mode_key[codes->back] = command_backspace;
|
|
commands->basic_mode_key[codes->up] = command_move_up;
|
|
commands->basic_mode_key[codes->down] = command_move_down;
|
|
commands->basic_mode_key[codes->end] = command_seek_end_of_line;
|
|
commands->basic_mode_key[codes->home] = command_seek_beginning_of_line;
|
|
|
|
#if 0
|
|
commands->control_key[codes->right] = command_seek_token_right;
|
|
commands->control_ascii['m'] = command_seek_token_right;
|
|
commands->control_key[codes->left] = command_seek_token_left;
|
|
commands->control_ascii['n'] = command_seek_token_left;
|
|
#else
|
|
commands->control_key[codes->right] = command_seek_whitespace_right;
|
|
commands->control_ascii['m'] = command_seek_whitespace_right;
|
|
commands->control_key[codes->left] = command_seek_whitespace_left;
|
|
commands->control_ascii['n'] = command_seek_whitespace_left;
|
|
#endif
|
|
|
|
commands->control_key[codes->up] = command_seek_whitespace_up;
|
|
commands->control_ascii['y'] = command_seek_whitespace_up;
|
|
commands->control_key[codes->down] = command_seek_whitespace_down;
|
|
commands->control_ascii['h'] = command_seek_whitespace_down;
|
|
|
|
commands->control_ascii['\t'] = command_auto_tab;
|
|
|
|
commands->control_ascii[' '] = command_set_mark;
|
|
commands->control_ascii['c'] = command_copy;
|
|
commands->control_ascii['x'] = command_cut;
|
|
commands->control_ascii['v'] = command_paste;
|
|
commands->control_ascii['V'] = command_paste_next;
|
|
commands->control_ascii['d'] = command_delete_chunk;
|
|
commands->control_ascii['p'] = command_open_panel;
|
|
commands->control_ascii['P'] = command_close_panel;
|
|
commands->control_ascii['o'] = command_interactive_open;
|
|
commands->control_ascii['O'] = command_reopen;
|
|
commands->control_ascii['s'] = command_save;
|
|
commands->control_ascii['w'] = command_interactive_save_as;
|
|
commands->control_ascii[','] = command_change_active_panel;
|
|
commands->control_ascii['i'] = command_interactive_switch_file;
|
|
commands->control_ascii['k'] = command_interactive_kill_file;
|
|
commands->control_ascii['K'] = command_kill_file;
|
|
commands->control_ascii['l'] = command_toggle_line_wrap;
|
|
commands->control_ascii['L'] = command_toggle_endline_mode;
|
|
commands->control_ascii['u'] = command_to_uppercase;
|
|
commands->control_ascii['j'] = command_to_lowercase;
|
|
commands->control_ascii['?'] = command_toggle_show_whitespace;
|
|
commands->control_ascii['`'] = command_clean_line;
|
|
commands->control_ascii['~'] = command_clean_all_lines;
|
|
commands->control_ascii['1'] = command_eol_dosify;
|
|
commands->control_ascii['!'] = command_eol_nixify;
|
|
commands->control_ascii['f'] = command_begin_search_state;
|
|
commands->control_ascii['r'] = command_begin_rsearch_state;
|
|
}
|
|
|
|
/*
|
|
* Interactive Bar
|
|
*/
|
|
|
|
internal void
|
|
intbar_draw_string(Render_Target *target,
|
|
Interactive_Bar *bar, u8 *str,
|
|
u32 char_color){
|
|
i32 char_w = font_get_character_width(bar->style.font);
|
|
for (i32 i = 0; str[i]; ++i){
|
|
font_draw_glyph(target, bar->style.font, str[i],
|
|
(real32)bar->pos_x, (real32)bar->pos_y, char_color);
|
|
bar->pos_x += char_w;
|
|
}
|
|
}
|
|
|
|
internal void
|
|
intbar_draw_string(Render_Target *target,
|
|
Interactive_Bar *bar, String str,
|
|
u32 char_color){
|
|
i32 char_w = font_get_character_width(bar->style.font);
|
|
for (i32 i = 0; i < str.size; ++i){
|
|
font_draw_glyph(target, bar->style.font, str.str[i],
|
|
(real32)bar->pos_x, (real32)bar->pos_y, char_color);
|
|
bar->pos_x += char_w;
|
|
}
|
|
}
|
|
|
|
internal void
|
|
hot_directory_draw_helper(Render_Target *target,
|
|
Hot_Directory *hot_directory,
|
|
Interactive_Bar *bar, String *string,
|
|
bool32 include_files){
|
|
persist u8 str_open_bracket[] = " {";
|
|
persist u8 str_close_bracket[] = "}";
|
|
persist u8 str_comma[] = ", ";
|
|
|
|
intbar_draw_string(target, bar, *string, bar->style.pop1_color);
|
|
|
|
u8 front_name_space[256];
|
|
String front_name =
|
|
make_string(front_name_space, 0, ArrayCount(front_name_space));
|
|
|
|
get_front_of_directory(&front_name, *string);
|
|
|
|
intbar_draw_string(target, bar, str_open_bracket, bar->style.base_color);
|
|
|
|
bool32 is_first_string = 1;
|
|
|
|
File_List files = hot_directory->file_list;
|
|
File_List_Iterator files_it = files_iterator_init(&files);
|
|
|
|
while (files_it.filename_ptr &&
|
|
(include_files || files_it.folder_stage)){
|
|
u8 *filename = *files_it.filename_ptr;
|
|
|
|
if (front_name.size == 0 || has_substr_unsensitive(filename, front_name)){
|
|
if (is_first_string){
|
|
is_first_string = 0;
|
|
}
|
|
else{
|
|
intbar_draw_string(target, bar, str_comma, bar->style.base_color);
|
|
}
|
|
if (files_it.folder_stage){
|
|
intbar_draw_string(target, bar, filename, bar->style.pop1_color);
|
|
intbar_draw_string(target, bar, (u8*)"/", bar->style.pop1_color);
|
|
}
|
|
else{
|
|
intbar_draw_string(target, bar, filename, bar->style.base_color);
|
|
}
|
|
}
|
|
|
|
files_iterator_step(&files, &files_it);
|
|
}
|
|
intbar_draw_string(target, bar, str_close_bracket, bar->style.base_color);
|
|
}
|
|
|
|
internal void
|
|
live_file_draw_helper(Render_Target *target,
|
|
Editing_Working_Set *working_set,
|
|
Interactive_Bar *bar, String *string){
|
|
persist u8 str_open_bracket[] = " {";
|
|
persist u8 str_close_bracket[] = "}";
|
|
persist u8 str_comma[] = ", ";
|
|
|
|
intbar_draw_string(target, bar, *string, bar->style.base_color);
|
|
|
|
intbar_draw_string(target, bar, str_open_bracket, bar->style.base_color);
|
|
|
|
bool32 is_first_string = 1;
|
|
for (i32 file_i = 0;
|
|
file_i < working_set->file_index_count;
|
|
++file_i){
|
|
Editing_File *file = &working_set->files[file_i];
|
|
if (file->live_name.str &&
|
|
(string->size == 0 || has_substr_unsensitive(file->live_name, *string))){
|
|
if (is_first_string){
|
|
is_first_string = 0;
|
|
}
|
|
else{
|
|
intbar_draw_string(target, bar, str_comma, bar->style.base_color);
|
|
}
|
|
intbar_draw_string(target, bar, file->live_name, bar->style.base_color);
|
|
}
|
|
}
|
|
intbar_draw_string(target, bar, str_close_bracket, bar->style.base_color);
|
|
}
|
|
|
|
/*
|
|
* App Functions
|
|
*/
|
|
|
|
internal bool32
|
|
app_init(Thread_Context *thread, Application_Memory *memory,
|
|
Key_Codes *loose_codes, Clipboard_Contents clipboard){
|
|
Partition_Cursor partition =
|
|
partition_open(memory->vars_memory, memory->vars_memory_size);
|
|
|
|
App_Vars *vars = (App_Vars*)
|
|
partition_allocate(&partition, sizeof(App_Vars));
|
|
|
|
Assert(vars);
|
|
*vars = {};
|
|
|
|
u32 panel_max_count = vars->layout.panel_max_count = 4;
|
|
u32 panel_count = vars->layout.panel_count = 1;
|
|
Assert(panel_max_count >= 1);
|
|
Editing_Panel *panels = vars->layout.panels = (Editing_Panel*)
|
|
partition_allocate(&partition, sizeof(Editing_Panel)*panel_max_count);
|
|
|
|
Assert(panels);
|
|
// TODO(allen): improved Assert
|
|
|
|
// NOTE(allen): command map setup
|
|
setup_commands(&vars->map, loose_codes);
|
|
|
|
// NOTE(allen): font setup
|
|
if (font_init() != 0){
|
|
FatalError("Error initializing fonts");
|
|
return 0;
|
|
}
|
|
|
|
i32 memory_used = 0;
|
|
if (font_load(&vars->font, 15, memory->font_memory,
|
|
font_predict_size(15), &memory_used) != 0){
|
|
FatalError("Could not find any fonts");
|
|
}
|
|
|
|
// NOTE(allen): file setup
|
|
vars->working_set.file_index_count = 1;
|
|
vars->working_set.file_max_count = 29;
|
|
|
|
vars->working_set.files = (Editing_File*)
|
|
partition_allocate(&partition,
|
|
sizeof(Editing_File)*vars->working_set.file_max_count);
|
|
|
|
buffer_get_dummy(&vars->working_set.files[0], &vars->style);
|
|
|
|
// NOTE(allen): clipboard setup
|
|
vars->working_set.clipboard_max_size = ArrayCount(vars->working_set.clipboards);
|
|
vars->working_set.clipboard_size = 0;
|
|
vars->working_set.clipboard_current = 0;
|
|
vars->working_set.clipboard_rolling = 0;
|
|
|
|
if (clipboard.str){
|
|
String *dest = working_set_next_clipboard_string(&vars->working_set, clipboard.size);
|
|
copy(dest, make_string(clipboard.str, clipboard.size));
|
|
}
|
|
|
|
// TODO(allen): more robust allocation solution for the clipboard?
|
|
|
|
// NOTE(allen): style setup
|
|
// TODO(allen): style_set_font function
|
|
vars->style.font = &vars->font;
|
|
vars->style.font_metrics.character_width = vars->font.glyphs[' '].advance;
|
|
vars->style.font_metrics.line_skip = vars->font.line_skip;
|
|
|
|
vars->style.back_color = 0xFF0C0C0C;
|
|
vars->style.margin_color = 0xFF181818;
|
|
vars->style.cursor_color = 0xFF00EE00;
|
|
vars->style.highlight_color = 0xFFDDEE00;
|
|
vars->style.mark_color = 0xFF494949;
|
|
vars->style.default_color = 0xFF90B080;
|
|
vars->style.at_cursor_color = vars->style.back_color;
|
|
vars->style.at_highlight_color = 0xFFFF44DD;
|
|
vars->style.comment_color = 0xFF2090F0;
|
|
vars->style.keyword_color = 0xFFD08F20;
|
|
vars->style.constant_color = 0xFF50FF30;
|
|
vars->style.special_character_color = 0xFFFF0000;
|
|
|
|
vars->style.use_paste_color = 1;
|
|
vars->style.paste_color = 0xFFDDEE00;
|
|
|
|
vars->style.highlight_junk_color = 0x44FF0000;
|
|
vars->style.highlight_white_color = 0x1100FFFF;
|
|
vars->style.tab_width = 4;
|
|
vars->style.margin_width = 5;
|
|
|
|
Interactive_Style file_info_style;
|
|
file_info_style.font = &vars->font;
|
|
file_info_style.bar_color = 0xFF888888;
|
|
file_info_style.base_color = 0xFF000000;
|
|
file_info_style.pop1_color = 0xFF4444AA;
|
|
file_info_style.pop2_color = 0xFFFF0000;
|
|
file_info_style.height = vars->style.font->line_skip + 2;
|
|
|
|
vars->style.file_info_style = file_info_style;
|
|
|
|
Interactive_Style command_style;
|
|
command_style.font = &vars->font;
|
|
command_style.bar_color = 0xFF0C0C0C;
|
|
command_style.base_color = 0xFFDDDDBB;
|
|
command_style.pop1_color = 0xFF4444AA;
|
|
command_style.pop2_color = 0xFF44FF44;
|
|
command_style.height = vars->style.font->line_skip + 2;
|
|
|
|
vars->command_style = command_style;
|
|
|
|
// NOTE(allen): panel setup
|
|
AllowLocal(panel_count);
|
|
panel_init(&panels[0], &vars->working_set.files[0]);
|
|
|
|
// NOTE(allen): hot directory setup
|
|
hot_directory_init(&vars->hot_directory);
|
|
|
|
// NOTE(allen): request stack setup
|
|
vars->request_count = 0;
|
|
vars->request_filled = 0;
|
|
vars->request_max = ArrayCount(vars->request_queue);
|
|
|
|
return 1;
|
|
}
|
|
|
|
struct Single_Line_Input_Step{
|
|
bool32 hit_newline;
|
|
bool32 hit_ctrl_newline;
|
|
bool32 hit_a_character;
|
|
bool32 hit_backspace;
|
|
bool32 hit_esc;
|
|
bool32 made_a_change;
|
|
bool32 did_command;
|
|
};
|
|
|
|
enum Single_Line_Input_Type{
|
|
SINGLE_LINE_STRING,
|
|
SINGLE_LINE_FILE
|
|
};
|
|
|
|
struct Single_Line_Mode{
|
|
Single_Line_Input_Type type;
|
|
String *string;
|
|
Hot_Directory *hot_directory;
|
|
bool32 fast_folder_select;
|
|
};
|
|
|
|
internal Single_Line_Input_Step
|
|
app_single_line_input_core(Key_Codes *codes,
|
|
Key_Input_Data *input,
|
|
Single_Line_Mode mode){
|
|
Single_Line_Input_Step result = {};
|
|
|
|
if (input->has_press){
|
|
if (input->press.keycode == codes->back){
|
|
result.hit_backspace = 1;
|
|
switch (mode.type){
|
|
case SINGLE_LINE_STRING:
|
|
{
|
|
// TODO(allen): Is this still okay? It looks like it's keeping a NULL terminator
|
|
// which is at least unecessary given the new protocol.
|
|
if (mode.string->size > 0){
|
|
--mode.string->size;
|
|
mode.string->str[mode.string->size] = 0;
|
|
result.made_a_change = 1;
|
|
}
|
|
}break;
|
|
|
|
case SINGLE_LINE_FILE:
|
|
{
|
|
if (mode.string->size > 0){
|
|
--mode.string->size;
|
|
i8 end_character = mode.string->str[mode.string->size];
|
|
if (character_is_slash(end_character)){
|
|
mode.string->size = reverse_seek_slash(*mode.string) + 1;
|
|
// TODO(allen): Is this still okay? It looks like it's keeping a NULL terminator
|
|
// which is at least unecessary given the new protocol.
|
|
// TODO(allen): What to do when the string becomes empty though?
|
|
mode.string->str[mode.string->size] = 0;
|
|
hot_directory_set(mode.hot_directory, *mode.string);
|
|
}
|
|
else{
|
|
// TODO(allen): Is this still okay? It looks like it's keeping a NULL terminator
|
|
// which is at least unecessary given the new protocol.
|
|
mode.string->str[mode.string->size] = 0;
|
|
}
|
|
result.made_a_change = 1;
|
|
}
|
|
}break;
|
|
}
|
|
}
|
|
|
|
else if (input->press.keycode == codes->newline){
|
|
if (input->control_keys[CONTROL_KEY_CONTROL]){
|
|
result.hit_ctrl_newline = 1;
|
|
result.made_a_change = 1;
|
|
}
|
|
|
|
else{
|
|
result.made_a_change = 1;
|
|
if (mode.fast_folder_select){
|
|
u8 front_name_space[256];
|
|
String front_name =
|
|
make_string(front_name_space, 0, ArrayCount(front_name_space));
|
|
get_front_of_directory(&front_name, *mode.string);
|
|
Hot_Directory_Match match;
|
|
match = hot_directory_first_match(mode.hot_directory, front_name, 1, 1);
|
|
|
|
if (!match.filename){
|
|
match = hot_directory_first_match(mode.hot_directory, front_name, 1, 0);
|
|
}
|
|
|
|
if (match.filename){
|
|
if (match.is_folder){
|
|
set_last_folder(mode.string, match.filename);
|
|
hot_directory_set(mode.hot_directory, *mode.string);
|
|
}
|
|
else{
|
|
mode.string->size = reverse_seek_slash(*mode.string) + 1;
|
|
append(mode.string, match.filename);
|
|
result.hit_newline = 1;
|
|
}
|
|
}
|
|
else{
|
|
result.hit_newline = 1;
|
|
}
|
|
}
|
|
else{
|
|
result.hit_newline = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (input->press.keycode == codes->esc){
|
|
result.hit_esc = 1;
|
|
result.made_a_change = 1;
|
|
}
|
|
|
|
else if (input->press.character){
|
|
result.hit_a_character = 1;
|
|
if (!input->control_keys[CONTROL_KEY_CONTROL]){
|
|
switch (mode.type){
|
|
case SINGLE_LINE_STRING:
|
|
{
|
|
if (mode.string->size+1 < mode.string->memory_size){
|
|
mode.string->str[mode.string->size] = (i8)input->press.character;
|
|
mode.string->size++;
|
|
// TODO(allen): More of this keeping a NULL terminator business...
|
|
// I want out of this business for the String struct.
|
|
mode.string->str[mode.string->size] = 0;
|
|
result.made_a_change = 1;
|
|
}
|
|
}break;
|
|
|
|
case SINGLE_LINE_FILE:
|
|
{
|
|
if (mode.string->size+1 < mode.string->memory_size){
|
|
i8 new_character = (i8)input->press.character;
|
|
mode.string->str[mode.string->size] = new_character;
|
|
mode.string->size++;
|
|
// TODO(allen): More of this keeping a NULL terminator business...
|
|
// I want out of this business for the String struct.
|
|
mode.string->str[mode.string->size] = 0;
|
|
|
|
if (character_is_slash(new_character)){
|
|
hot_directory_set(mode.hot_directory, *mode.string);
|
|
}
|
|
result.made_a_change = 1;
|
|
}
|
|
}break;
|
|
}
|
|
}
|
|
|
|
else{
|
|
result.did_command = 1;
|
|
result.made_a_change = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
inline internal Single_Line_Input_Step
|
|
app_single_line_input_step(Key_Codes *codes, Key_Input_Data *input, String *string){
|
|
Single_Line_Mode mode = {};
|
|
mode.type = SINGLE_LINE_STRING;
|
|
mode.string = string;
|
|
return app_single_line_input_core(codes, input, mode);
|
|
}
|
|
|
|
inline internal Single_Line_Input_Step
|
|
app_single_file_input_step(Key_Codes *codes, Key_Input_Data *input,
|
|
String *string, Hot_Directory *hot_directory,
|
|
bool32 fast_folder_select){
|
|
Single_Line_Mode mode = {};
|
|
mode.type = SINGLE_LINE_FILE;
|
|
mode.string = string;
|
|
mode.hot_directory = hot_directory;
|
|
mode.fast_folder_select = fast_folder_select;
|
|
return app_single_line_input_core(codes, input, mode);
|
|
}
|
|
|
|
inline internal Command_Function
|
|
app_get_command(App_Vars *vars, Key_Input_Data *input, Key_Event_Data key){
|
|
Command_Function function = 0;
|
|
if (input->control_keys[CONTROL_KEY_CONTROL]){
|
|
if (key.character_no_caps_lock){
|
|
function = vars->map.control_ascii[key.character_no_caps_lock];
|
|
}
|
|
else{
|
|
function = vars->map.control_key[key.keycode];
|
|
}
|
|
}
|
|
else if (!input->control_keys[CONTROL_KEY_ALT]){
|
|
if (key.character != 0){
|
|
function = command_write_character;
|
|
}
|
|
else{
|
|
function = vars->map.basic_mode_key[key.keycode];
|
|
}
|
|
}
|
|
return function;
|
|
}
|
|
|
|
internal bool32
|
|
smooth_camera_step(real32 *target, real32 *current, real32 *vel, real32 S, real32 T){
|
|
bool32 result = 0;
|
|
real32 targ = *target;
|
|
real32 curr = *current;
|
|
real32 v = *vel;
|
|
if (curr != targ){
|
|
real32 L = lerp(curr, T, targ);
|
|
|
|
i32 sign = (targ > curr) - (targ < curr);
|
|
real32 V = curr + sign*v;
|
|
|
|
if (sign > 0){
|
|
curr = Min(L, V);
|
|
}
|
|
else{
|
|
curr = Max(L, V);
|
|
}
|
|
|
|
if (curr == V){
|
|
v *= S;
|
|
}
|
|
|
|
if (curr > targ - .0001f && curr < targ + .0001f){
|
|
curr = targ;
|
|
v = 1.f;
|
|
}
|
|
*target = targ;
|
|
*current = curr;
|
|
*vel = v;
|
|
result = 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#if FRED_INTERNAL
|
|
Application_Memory *GLOBAL;
|
|
#endif
|
|
|
|
internal Application_Step_Result
|
|
app_step(Thread_Context *thread, Key_Codes *codes,
|
|
Key_Input_Data *input, Mouse_State *mouse,
|
|
bool32 time_step, Render_Target *target,
|
|
Application_Memory *memory,
|
|
Clipboard_Contents clipboard,
|
|
bool32 first_step){
|
|
|
|
#if FRED_INTERNAL
|
|
GLOBAL = memory;
|
|
#endif
|
|
|
|
ProfileStart(app_step);
|
|
ProfileSection(thread, app_step, "start");
|
|
|
|
Application_Step_Result app_result = {};
|
|
app_result.redraw = 1;
|
|
|
|
App_Vars *vars = (App_Vars*)memory->vars_memory;
|
|
|
|
// TODO(allen): Let's find another way to do first_step
|
|
// so we can get it out of app_step and the platform layer.
|
|
if (first_step || !time_step){
|
|
app_result.redraw = 1;
|
|
}
|
|
|
|
Editing_Panel *panels = vars->layout.panels;
|
|
Editing_Panel *active_panel = &panels[vars->layout.active_panel];
|
|
ProfileSection(thread, app_step, "setup");
|
|
|
|
// NOTE(allen): OS clipboard event handling
|
|
if (clipboard.str){
|
|
String *dest = working_set_next_clipboard_string(&vars->working_set, clipboard.size);
|
|
copy(dest, make_string(clipboard.str, clipboard.size));
|
|
}
|
|
|
|
// NOTE(allen): check files are up to date
|
|
for (i32 i = 0; i < vars->working_set.file_index_count; ++i){
|
|
Editing_File *file = vars->working_set.files + i;
|
|
|
|
if (!file->is_dummy){
|
|
Time_Stamp time_stamp;
|
|
time_stamp = system_file_time_stamp((u8*)make_c_str(file->source_path));
|
|
|
|
if (time_stamp.success){
|
|
file->last_sys_write_time = time_stamp.time;
|
|
if (file->last_sys_write_time != file->last_4ed_write_time){
|
|
app_result.redraw = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ProfileSection(thread, app_step, "OS syncing");
|
|
|
|
// NOTE(allen): keyboard input handling
|
|
if (time_step){
|
|
switch (vars->state){
|
|
case APP_STATE_EDIT:
|
|
{
|
|
Command_Data command_data;
|
|
command_data.panel = active_panel;
|
|
command_data.working_set = &vars->working_set;
|
|
command_data.layout = &vars->layout;
|
|
command_data.style = &vars->style;
|
|
command_data.vars = vars;
|
|
command_data.screen_width = target->width;
|
|
command_data.screen_height = target->height;
|
|
command_data.screen_y_off = vars->command_style.height;
|
|
command_data.requests = 0;
|
|
command_data.request_count = 0;
|
|
command_data.request_filled = 0;
|
|
|
|
if (vars->request_count == 0){
|
|
Command_Function function = 0;
|
|
if (input->has_press){
|
|
command_data.key = input->press;
|
|
function = app_get_command(vars, input, command_data.key);
|
|
}
|
|
|
|
else if (input->has_hold){
|
|
command_data.key = input->hold;
|
|
function = app_get_command(vars, input, command_data.key);
|
|
|
|
}
|
|
|
|
if (function){
|
|
command_data.panel->next_mode = {};
|
|
function(&command_data);
|
|
app_result.redraw = 1;
|
|
command_data.panel->mode = command_data.panel->next_mode;
|
|
}
|
|
}
|
|
|
|
else{
|
|
|
|
Input_Request *active_request = vars->request_queue + vars->request_filled;
|
|
bool32 killed_command = 0;
|
|
switch (active_request->type){
|
|
case REQUEST_SYS_FILE:
|
|
{
|
|
String *string = &active_request->sys_file.dest;
|
|
Single_Line_Input_Step result =
|
|
app_single_file_input_step(codes, input, string, &vars->hot_directory, 1);
|
|
|
|
if (result.made_a_change){
|
|
app_result.redraw = 1;
|
|
}
|
|
|
|
if (result.hit_ctrl_newline){
|
|
active_request->sys_file.hit_ctrl_newline = 1;
|
|
}
|
|
|
|
if (result.hit_newline || result.hit_ctrl_newline){
|
|
++vars->request_filled;
|
|
}
|
|
|
|
if (result.hit_esc){
|
|
app_clear_request_queue(vars);
|
|
killed_command = 1;
|
|
}
|
|
}break;
|
|
|
|
case REQUEST_LIVE_FILE:
|
|
{
|
|
String *string = &active_request->live_file.dest;
|
|
Single_Line_Input_Step result =
|
|
app_single_line_input_step(codes, input, string);
|
|
|
|
if (result.made_a_change){
|
|
app_result.redraw = 1;
|
|
}
|
|
|
|
if (result.hit_ctrl_newline){
|
|
active_request->live_file.hit_ctrl_newline = 1;
|
|
}
|
|
|
|
if (result.hit_newline || result.hit_ctrl_newline){
|
|
++vars->request_filled;
|
|
}
|
|
|
|
if (result.hit_esc){
|
|
app_clear_request_queue(vars);
|
|
killed_command = 1;
|
|
}
|
|
}break;
|
|
|
|
}
|
|
|
|
if (vars->request_filled == vars->request_count && !killed_command){
|
|
command_data.requests = vars->request_queue;
|
|
command_data.request_count = vars->request_count;
|
|
command_data.request_filled = vars->request_filled;
|
|
vars->pending_command(&command_data);
|
|
app_clear_request_queue(vars);
|
|
}
|
|
}
|
|
|
|
}break;
|
|
|
|
case APP_STATE_SEARCH:
|
|
{
|
|
String *string = &vars->isearch.str;
|
|
Single_Line_Input_Step result =
|
|
app_single_line_input_step(codes, input, string);
|
|
|
|
if (result.made_a_change){
|
|
app_result.redraw = 1;
|
|
|
|
Editing_File *file = active_panel->file;
|
|
|
|
bool32 step_forward = 0;
|
|
bool32 step_backward = 0;
|
|
|
|
if (input->has_press){
|
|
Key_Event_Data key = input->press;
|
|
Command_Function function = app_get_command(vars, input, key);
|
|
if (function == command_begin_search_state){
|
|
step_forward = 1;
|
|
}
|
|
if (function == command_begin_rsearch_state){
|
|
step_backward = 1;
|
|
}
|
|
}
|
|
|
|
if (!step_forward && !step_backward &&
|
|
!input->has_press && input->has_hold){
|
|
Key_Event_Data key = input->hold;
|
|
Command_Function function = app_get_command(vars, input, key);
|
|
if (function == command_begin_search_state){
|
|
step_forward = 1;
|
|
}
|
|
if (function == command_begin_rsearch_state){
|
|
step_backward = 1;
|
|
}
|
|
}
|
|
|
|
i32 start_pos = vars->isearch.pos;
|
|
if (step_forward){
|
|
if (vars->isearch.reverse){
|
|
start_pos = active_panel->temp_highlight.pos - 1;
|
|
vars->isearch.pos = start_pos;
|
|
vars->isearch.reverse = 0;
|
|
}
|
|
}
|
|
if (step_backward){
|
|
if (!vars->isearch.reverse){
|
|
start_pos = active_panel->temp_highlight.pos + 1;
|
|
vars->isearch.pos = start_pos;
|
|
vars->isearch.reverse = 1;
|
|
}
|
|
}
|
|
|
|
String file_string = make_string(file->data, file->size);
|
|
|
|
i32 pos;
|
|
|
|
if (vars->isearch.reverse){
|
|
if (result.hit_backspace){
|
|
start_pos = active_panel->temp_highlight.pos + 1;
|
|
vars->isearch.pos = start_pos;
|
|
}
|
|
else{
|
|
pos = rfind_substr(file_string, start_pos - 1, *string);
|
|
|
|
if (pos >= 0){
|
|
if (step_backward){
|
|
vars->isearch.pos = pos;
|
|
start_pos = pos;
|
|
pos = rfind_substr(file_string, start_pos - 1, *string);
|
|
if (pos == -1){
|
|
pos = start_pos;
|
|
}
|
|
}
|
|
|
|
panel_set_temp_highlight(active_panel, pos, pos+string->size);
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
if (result.hit_backspace){
|
|
start_pos = active_panel->temp_highlight.pos - 1;
|
|
vars->isearch.pos = start_pos;
|
|
}
|
|
else{
|
|
pos = find_substr(file_string, start_pos + 1, *string);
|
|
|
|
if (pos < file->size){
|
|
if (step_forward){
|
|
vars->isearch.pos = pos;
|
|
start_pos = pos;
|
|
pos = find_substr(file_string, start_pos + 1, *string);
|
|
if (pos == file->size){
|
|
pos = start_pos;
|
|
}
|
|
}
|
|
|
|
panel_set_temp_highlight(active_panel, pos, pos+string->size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result.hit_newline || result.hit_ctrl_newline){
|
|
panel_cursor_move(active_panel, active_panel->temp_highlight);
|
|
|
|
system_free_memory(vars->isearch.str.str);
|
|
vars->state = APP_STATE_EDIT;
|
|
}
|
|
|
|
if (result.hit_esc){
|
|
active_panel->show_temp_highlight = 0;
|
|
|
|
system_free_memory(vars->isearch.str.str);
|
|
vars->state = APP_STATE_EDIT;
|
|
}
|
|
|
|
}break;
|
|
|
|
case APP_STATE_RESIZING:
|
|
{
|
|
if (input->has_press){
|
|
vars->state = APP_STATE_EDIT;
|
|
}
|
|
}break;
|
|
}
|
|
}
|
|
ProfileSection(thread, app_step, "keyboard input");
|
|
|
|
// NOTE(allen): reorganizing panels on screen
|
|
i32 prev_width = vars->layout.full_width;
|
|
i32 prev_height = vars->layout.full_height;
|
|
i32 full_width = vars->layout.full_width = target->width;
|
|
i32 full_height = vars->layout.full_height = target->height;
|
|
|
|
if (prev_width != full_width || prev_height != full_height){
|
|
layout_refit(&vars->layout, 0, vars->command_style.height,
|
|
full_width, full_height,
|
|
prev_width, prev_height);
|
|
|
|
for (i32 panel_i = 0;
|
|
panel_i < vars->layout.panel_count &&
|
|
vars->layout.panel_count > 1;
|
|
++panel_i){
|
|
Editing_Panel *panel = &panels[panel_i];
|
|
|
|
Editing_Style *style = panel->file->style;
|
|
i32 character_w = style_get_character_width(style);
|
|
i32 margin_width = style->margin_width;
|
|
if (panel->full_w < character_w*6 + margin_width*2){
|
|
i32 share_w = panel->full_w;
|
|
layout_close_panel(&vars->layout, panel_i);
|
|
layout_redistribute_width(&vars->layout, share_w);
|
|
panel_i = 0;
|
|
}
|
|
}
|
|
|
|
for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){
|
|
Editing_Panel *panel = panels + panel_i;
|
|
if (!panel->file->is_dummy){
|
|
panel->cursor = panel_compute_cursor_from_pos(panel, panel->cursor.pos);
|
|
}
|
|
}
|
|
|
|
app_result.redraw = 1;
|
|
}
|
|
ProfileSection(thread, app_step, "reorganizing panels");
|
|
|
|
// NOTE(allen): mouse input handling
|
|
if (time_step && !mouse->out_of_window){
|
|
|
|
i32 mx = mouse->x;
|
|
i32 my = mouse->y;
|
|
bool32 mouse_press_event = 0;
|
|
|
|
if (mouse->left_button && !mouse->left_button_prev){
|
|
// TODO(allen):
|
|
// This means any mouse use will be impossible, I guess it will have
|
|
// to depend on the type of the request seen at the top???
|
|
app_clear_request_queue(vars);
|
|
mouse_press_event = 1;
|
|
|
|
switch (vars->state){
|
|
case APP_STATE_SEARCH:
|
|
{
|
|
active_panel->show_temp_highlight = 0;
|
|
|
|
system_free_memory(vars->isearch.str.str);
|
|
vars->state = APP_STATE_EDIT;
|
|
}break;
|
|
}
|
|
}
|
|
|
|
switch (vars->state){
|
|
case APP_STATE_EDIT:
|
|
{
|
|
for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){
|
|
|
|
Editing_Panel *panel = &panels[panel_i];
|
|
|
|
if (mx >= panel->x &&
|
|
mx < panel->w + panel->x &&
|
|
my >= panel->y &&
|
|
my < panel->h + panel->y){
|
|
|
|
app_result.mouse_cursor_type = APP_MOUSE_CURSOR_IBEAM;
|
|
|
|
if (mouse_press_event){
|
|
if (!panel->file->is_dummy){
|
|
app_result.redraw = 1;
|
|
|
|
Font *font = &vars->font;
|
|
|
|
i32 character_w = style_get_character_width(panel->file->style);
|
|
i32 character_h = font->line_skip;
|
|
i32 max_line_length = panel_compute_max_line_length(panel);
|
|
i32 max_lines = panel_compute_max_lines(panel);
|
|
|
|
real32 grid_x = ((real32)mx - panel->x) / character_w;
|
|
real32 grid_y = ((real32)my - panel->y) / character_h;
|
|
|
|
vars->last_click_x = grid_x;
|
|
vars->last_click_y = grid_y;
|
|
|
|
if (grid_x >= 0 && grid_x <= max_line_length &&
|
|
grid_y >= 0 && grid_y < max_lines){
|
|
|
|
i32 pos_x = (i32)(grid_x + active_panel->scroll_x);
|
|
i32 pos_y = (i32)(grid_y + active_panel->scroll_y);
|
|
|
|
panel_cursor_move(active_panel, pos_x, pos_y);
|
|
}
|
|
}
|
|
vars->layout.active_panel = panel_i;
|
|
active_panel = panel;
|
|
}
|
|
else{
|
|
// NOTE(allen): mouse inside editing area but no click
|
|
}
|
|
}
|
|
|
|
else if (mx >= panel->full_x &&
|
|
mx < panel->full_w + panel->full_x &&
|
|
my >= panel->full_y &&
|
|
my < panel->full_h + panel->full_y){
|
|
// NOTE(allen): not inside the editing area but within the margins
|
|
bool32 resize_area = 0;
|
|
app_result.mouse_cursor_type = APP_MOUSE_CURSOR_ARROW;
|
|
if (mx >= (panel->full_x+panel->full_w+panel->x+panel->w)/2){
|
|
if (panel_i != vars->layout.panel_count-1){
|
|
app_result.mouse_cursor_type = APP_MOUSE_CURSOR_LEFTRIGHT;
|
|
resize_area = 1;
|
|
}
|
|
}
|
|
else if (mx <= (panel->full_x+panel->x)/2){
|
|
if (panel_i != 0){
|
|
app_result.mouse_cursor_type = APP_MOUSE_CURSOR_LEFTRIGHT;
|
|
resize_area = -1;
|
|
}
|
|
}
|
|
|
|
if (resize_area != 0 && mouse_press_event){
|
|
vars->state = APP_STATE_RESIZING;
|
|
if (resize_area == 1){
|
|
vars->resizing.left = panel;
|
|
vars->resizing.right = panel+1;
|
|
}
|
|
else if (resize_area == -1){
|
|
vars->resizing.left = panel-1;
|
|
vars->resizing.right = panel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
i32 cursor_y = panel_get_cursor_y(active_panel);
|
|
real32 target_y = active_panel->target_y;
|
|
i32 max_lines = panel_compute_max_lines(active_panel);
|
|
|
|
bool32 wheel_used;
|
|
|
|
real32 delta_target_y = Max(1, max_lines / 3.f);
|
|
delta_target_y *= -mouse->wheel;
|
|
|
|
target_y += delta_target_y;
|
|
|
|
if (target_y < 0){
|
|
target_y = 0;
|
|
}
|
|
|
|
if (mouse->wheel == 0){
|
|
wheel_used = 0;
|
|
}
|
|
else{
|
|
wheel_used = 1;
|
|
|
|
if (cursor_y >= target_y + max_lines){
|
|
cursor_y = (i32)target_y + max_lines - 1;
|
|
}
|
|
if (cursor_y < target_y){
|
|
cursor_y = (i32)target_y + 1;
|
|
}
|
|
}
|
|
|
|
active_panel->target_y = target_y;
|
|
if (cursor_y != panel_get_cursor_y(active_panel)){
|
|
active_panel->cursor =
|
|
panel_compute_cursor_from_xy(active_panel,
|
|
active_panel->preferred_x,
|
|
cursor_y);
|
|
}
|
|
|
|
if (wheel_used){
|
|
app_result.redraw = 1;
|
|
}
|
|
}break;
|
|
|
|
case APP_STATE_RESIZING:
|
|
{
|
|
if (mouse->left_button){
|
|
Editing_Panel *left = vars->resizing.left;
|
|
Editing_Panel *right = vars->resizing.right;
|
|
i32 left_x = left->full_x;
|
|
i32 left_w = left->full_w;
|
|
i32 right_x = right->full_x;
|
|
i32 right_w = right->full_w;
|
|
|
|
AllowLocal(right_x);
|
|
|
|
i32 new_left_x = left_x;
|
|
i32 new_left_w = mx - left_x;
|
|
i32 new_right_w = right_w - (new_left_w - left_w);
|
|
i32 new_right_x = mx;
|
|
|
|
if (left_w != new_left_w){
|
|
app_result.redraw = 1;
|
|
|
|
Editing_Style *left_style = left->file->style;
|
|
Editing_Style *right_style = right->file->style;
|
|
|
|
i32 left_character_w = style_get_character_width(left_style);
|
|
i32 right_character_w = style_get_character_width(right_style);
|
|
|
|
i32 left_margin_width = left_style->margin_width;
|
|
i32 right_margin_width = right_style->margin_width;
|
|
if (new_left_w > left_margin_width*2 + left_character_w*6 &&
|
|
new_right_w > right_margin_width*2 + right_character_w*6){
|
|
left->full_x = new_left_x;
|
|
left->full_w = new_left_w;
|
|
right->full_x = new_right_x;
|
|
right->full_w = new_right_w;
|
|
|
|
panel_fix_internal_area(left);
|
|
panel_fix_internal_area(right);
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
app_result.redraw = 1;
|
|
vars->state = APP_STATE_EDIT;
|
|
}
|
|
}break;
|
|
}
|
|
}
|
|
ProfileSection(thread, app_step, "mouse input");
|
|
|
|
// NOTE(allen): fix scrolling on all panels
|
|
for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){
|
|
Editing_Panel *panel = &panels[panel_i];
|
|
i32 cursor_y;
|
|
if (panel->show_temp_highlight){
|
|
if (panel->unwrapped_lines){
|
|
cursor_y = panel->temp_highlight.unwrapped_y;
|
|
}
|
|
else{
|
|
cursor_y = panel->temp_highlight.wrapped_y;
|
|
}
|
|
}
|
|
else{
|
|
if (panel->unwrapped_lines){
|
|
cursor_y = panel->cursor.unwrapped_y;
|
|
}
|
|
else{
|
|
cursor_y = panel->cursor.wrapped_y;
|
|
}
|
|
}
|
|
real32 target_y = panel->target_y;
|
|
real32 original_target_y = target_y;
|
|
i32 max_lines = panel_compute_max_lines(panel);
|
|
|
|
while (cursor_y >= Floor(target_y) + max_lines){
|
|
target_y += 3.f;
|
|
}
|
|
|
|
while (cursor_y < target_y){
|
|
target_y -= 3.f;
|
|
}
|
|
|
|
if (target_y < 0){
|
|
target_y = 0;
|
|
}
|
|
|
|
panel->target_y = target_y;
|
|
|
|
i32 cursor_x = panel_get_cursor_x(panel);
|
|
real32 target_x = panel->target_x;
|
|
real32 original_target_x = target_x;
|
|
i32 max_x = panel_compute_max_line_length(panel);
|
|
if (cursor_x < target_x){
|
|
target_x = (real32)Max(0, cursor_x - max_x/2);
|
|
}
|
|
else if (cursor_x >= target_x + max_x){
|
|
target_x = (real32)(cursor_x - max_x/2);
|
|
}
|
|
|
|
panel->target_x = target_x;
|
|
|
|
if (original_target_y != panel->target_y ||
|
|
original_target_x != panel->target_x){
|
|
app_result.redraw;
|
|
}
|
|
}
|
|
ProfileSection(thread, app_step, "fix scrolling");
|
|
|
|
// NOTE(allen): execute animations
|
|
for (i32 i = 0; i < vars->layout.panel_count; ++i){
|
|
Editing_Panel *panel = vars->layout.panels + i;
|
|
|
|
// TODO(allen): Scrolling parameterization in style?
|
|
if (smooth_camera_step(&panel->target_y, &panel->scroll_y, &panel->vel_y, 2.f, 1.f/9.f)){
|
|
app_result.redraw = 1;
|
|
}
|
|
|
|
if (smooth_camera_step(&panel->target_x, &panel->scroll_x, &panel->vel_x, 2.f, 1.f/6.f)){
|
|
app_result.redraw = 1;
|
|
}
|
|
|
|
if (panel->paste_effect.tick_down > 0){
|
|
--panel->paste_effect.tick_down;
|
|
app_result.redraw = 1;
|
|
}
|
|
|
|
}
|
|
ProfileSection(thread, app_step, "execute animations");
|
|
|
|
if (app_result.redraw){
|
|
// NOTE(allen): render the panels
|
|
for (i32 panel_i = 0; panel_i < vars->layout.panel_count; ++panel_i){
|
|
Editing_Panel *panel = &panels[panel_i];
|
|
Editing_Style *style = panel->file->style;
|
|
|
|
i32 full_left = panel->full_x;
|
|
i32 full_top = panel->full_y;
|
|
i32 full_right = full_left + panel->full_w;
|
|
i32 full_bottom = full_top + panel->full_h;
|
|
|
|
u32 back_color = style->back_color;
|
|
draw_rectangle_2corner(target, full_left, full_top, full_right, full_bottom, back_color);
|
|
|
|
u32 side_margin_color = style->margin_color;
|
|
|
|
panel_draw(thread, target, panel, vars->layout.active_panel == panel_i);
|
|
|
|
// NOTE(allen): file info bar
|
|
{
|
|
Interactive_Bar bar;
|
|
bar.style = style->file_info_style;
|
|
bar.pos_x = panel->x;
|
|
bar.pos_y = full_top;
|
|
|
|
draw_rectangle(target, full_left, bar.pos_y,
|
|
panel->full_w, bar.style.height,
|
|
bar.style.bar_color);
|
|
|
|
Editing_File *file = panel->file;
|
|
if (!file->is_dummy){
|
|
intbar_draw_string(target, &bar, panel->file->live_name, bar.style.base_color);
|
|
intbar_draw_string(target, &bar, make_lit_string(" - "), bar.style.base_color);
|
|
|
|
u8 line_number_space[30];
|
|
String line_number = make_string(line_number_space, 0, 30);
|
|
append(&line_number, (u8*)"L#");
|
|
append_int_to_str(panel->cursor.line, &line_number);
|
|
|
|
intbar_draw_string(target, &bar, line_number, bar.style.base_color);
|
|
|
|
if (file->last_4ed_write_time != file->last_sys_write_time){
|
|
persist String out_of_sync = make_lit_string(" FILE SYNC");
|
|
intbar_draw_string(target, &bar, out_of_sync, bar.style.pop2_color);
|
|
}
|
|
}
|
|
}
|
|
|
|
// L
|
|
draw_rectangle_2corner(target, full_left, panel->y,
|
|
panel->x, full_bottom, side_margin_color);
|
|
|
|
// R
|
|
draw_rectangle_2corner(target, panel->x + panel->w, panel->y,
|
|
full_right, full_bottom, side_margin_color);
|
|
|
|
// B
|
|
draw_rectangle_2corner(target, full_left, panel->y + panel->h,
|
|
full_right, full_bottom, side_margin_color);
|
|
|
|
if (panel_i != 0){
|
|
draw_rectangle_2corner(target, panel->full_x-1, panel->full_y,
|
|
panel->full_x+1, panel->full_y+panel->full_h,
|
|
0xFFFFFFFF);
|
|
}
|
|
}
|
|
ProfileSection(thread, app_step, "render files");
|
|
|
|
// NOTE (allen): command bar
|
|
{
|
|
Interactive_Bar bar;
|
|
bar.style = vars->command_style;
|
|
bar.pos_x = 0;
|
|
bar.pos_y = 0;
|
|
|
|
draw_rectangle(target, 0, 0,
|
|
target->width, bar.style.height,
|
|
bar.style.bar_color);
|
|
|
|
switch (vars->state){
|
|
case APP_STATE_EDIT:
|
|
{
|
|
if (vars->request_count > 0){
|
|
Input_Request *request = vars->request_queue + vars->request_filled;
|
|
|
|
switch (request->type){
|
|
case REQUEST_SYS_FILE:
|
|
{
|
|
intbar_draw_string(target, &bar, request->sys_file.query, bar.style.pop2_color);
|
|
|
|
String *string = &request->sys_file.dest;
|
|
hot_directory_draw_helper(target, &vars->hot_directory, &bar, string, 1);
|
|
}break;
|
|
|
|
case REQUEST_LIVE_FILE:
|
|
{
|
|
intbar_draw_string(target, &bar, request->sys_file.query, bar.style.pop2_color);
|
|
|
|
String *string = &request->sys_file.dest;
|
|
live_file_draw_helper(target, &vars->working_set, &bar, string);
|
|
}break;
|
|
|
|
}
|
|
}
|
|
}break;
|
|
|
|
case APP_STATE_SEARCH:
|
|
{
|
|
persist String search_str = make_lit_string("I-Search: ");
|
|
persist String rsearch_str = make_lit_string("Reverse-I-Search: ");
|
|
if (vars->isearch.reverse){
|
|
intbar_draw_string(target, &bar, rsearch_str, bar.style.pop2_color);
|
|
}
|
|
else{
|
|
intbar_draw_string(target, &bar, search_str, bar.style.pop2_color);
|
|
}
|
|
|
|
intbar_draw_string(target, &bar, vars->isearch.str, bar.style.base_color);
|
|
}break;
|
|
|
|
case APP_STATE_RESIZING:
|
|
{
|
|
intbar_draw_string(target, &bar, make_lit_string("Resizing!"), bar.style.pop2_color);
|
|
}break;
|
|
}
|
|
}
|
|
ProfileSection(thread, app_step, "interaction bar");
|
|
|
|
}
|
|
|
|
ProfileEnd(thread, app_step, "total");
|
|
|
|
return app_result;
|
|
}
|
|
|
|
// BOTTOM
|
|
|