531 lines
12 KiB
C++
531 lines
12 KiB
C++
|
/*
|
||
|
* Mr. 4th Dimention - Allen Webster
|
||
|
*
|
||
|
* 20.02.2016
|
||
|
*
|
||
|
* File editing view for 4coder
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
// TOP
|
||
|
|
||
|
#include "buffer/4coder_shared.cpp"
|
||
|
|
||
|
#if BUFFER_EXPERIMENT_SCALPEL == 0
|
||
|
#include "buffer/4coder_golden_array.cpp"
|
||
|
#define Buffer_Type Buffer
|
||
|
#elif BUFFER_EXPERIMENT_SCALPEL == 1
|
||
|
#include "buffer/4coder_gap_buffer.cpp"
|
||
|
#define Buffer_Type Gap_Buffer
|
||
|
#elif BUFFER_EXPERIMENT_SCALPEL == 2
|
||
|
#include "buffer/4coder_multi_gap_buffer.cpp"
|
||
|
#define Buffer_Type Multi_Gap_Buffer
|
||
|
#else
|
||
|
#include "buffer/4coder_rope_buffer.cpp"
|
||
|
#define Buffer_Type Rope_Buffer
|
||
|
#endif
|
||
|
|
||
|
#include "buffer/4coder_buffer_abstract.cpp"
|
||
|
|
||
|
enum Edit_Type{
|
||
|
ED_NORMAL,
|
||
|
ED_REVERSE_NORMAL,
|
||
|
ED_UNDO,
|
||
|
ED_REDO,
|
||
|
};
|
||
|
|
||
|
struct Edit_Step{
|
||
|
Edit_Type type;
|
||
|
union{
|
||
|
struct{
|
||
|
b32 can_merge;
|
||
|
Buffer_Edit edit;
|
||
|
i32 pre_pos;
|
||
|
i32 post_pos;
|
||
|
i32 next_block, prev_block;
|
||
|
};
|
||
|
struct{
|
||
|
i32 first_child;
|
||
|
i32 inverse_first_child;
|
||
|
i32 inverse_child_count;
|
||
|
i32 special_type;
|
||
|
};
|
||
|
};
|
||
|
i32 child_count;
|
||
|
};
|
||
|
|
||
|
struct Edit_Stack{
|
||
|
u8 *strings;
|
||
|
i32 size, max;
|
||
|
|
||
|
Edit_Step *edits;
|
||
|
i32 edit_count, edit_max;
|
||
|
};
|
||
|
|
||
|
struct Small_Edit_Stack{
|
||
|
u8 *strings;
|
||
|
i32 size, max;
|
||
|
|
||
|
Buffer_Edit *edits;
|
||
|
i32 edit_count, edit_max;
|
||
|
};
|
||
|
|
||
|
struct Undo_Data{
|
||
|
Edit_Stack undo;
|
||
|
Edit_Stack redo;
|
||
|
Edit_Stack history;
|
||
|
Small_Edit_Stack children;
|
||
|
|
||
|
i32 history_block_count, history_head_block;
|
||
|
i32 edit_history_cursor;
|
||
|
b32 current_block_normal;
|
||
|
};
|
||
|
|
||
|
struct Text_Effect{
|
||
|
i32 start, end;
|
||
|
u32 color;
|
||
|
i32 tick_down, tick_max;
|
||
|
};
|
||
|
|
||
|
// NOTE(allen): The Editing_File struct is now divided into two
|
||
|
// parts. Variables in the Settings part can be set even when the
|
||
|
// file is still streaming in, and all operations except for the
|
||
|
// initial allocation of the file.
|
||
|
struct Editing_File_Settings{
|
||
|
i32 base_map_id;
|
||
|
i32 dos_write_mode;
|
||
|
b32 unwrapped_lines;
|
||
|
b8 tokens_exist;
|
||
|
b8 is_initialized;
|
||
|
b8 unimportant;
|
||
|
b8 read_only;
|
||
|
};
|
||
|
|
||
|
// NOTE(allen): This part of the Editing_File is cleared whenever
|
||
|
// the contents of the file is set.
|
||
|
struct Editing_File_State{
|
||
|
b32 is_dummy;
|
||
|
b32 is_loading;
|
||
|
|
||
|
i16 font_id;
|
||
|
Buffer_Type buffer;
|
||
|
|
||
|
i32 cursor_pos;
|
||
|
|
||
|
Undo_Data undo;
|
||
|
|
||
|
Cpp_Token_Stack token_stack;
|
||
|
Cpp_Token_Stack swap_stack;
|
||
|
u32 lex_job;
|
||
|
b32 tokens_complete;
|
||
|
b32 still_lexing;
|
||
|
|
||
|
Text_Effect paste_effect;
|
||
|
|
||
|
u64 last_4ed_write_time;
|
||
|
u64 last_4ed_edit_time;
|
||
|
u64 last_sys_write_time;
|
||
|
};
|
||
|
|
||
|
struct Editing_File_Preload{
|
||
|
i32 start_line;
|
||
|
};
|
||
|
|
||
|
struct Editing_File_Name{
|
||
|
char live_name_[256];
|
||
|
String live_name;
|
||
|
|
||
|
char source_path_[256];
|
||
|
char extension_[16];
|
||
|
String source_path;
|
||
|
String extension;
|
||
|
};
|
||
|
|
||
|
struct File_Node{
|
||
|
File_Node *next, *prev;
|
||
|
};
|
||
|
|
||
|
struct Editing_File{
|
||
|
File_Node node;
|
||
|
Editing_File_Settings settings;
|
||
|
union{
|
||
|
Editing_File_State state;
|
||
|
Editing_File_Preload preload;
|
||
|
};
|
||
|
Editing_File_Name name;
|
||
|
};
|
||
|
|
||
|
struct File_Table_Entry{
|
||
|
String name;
|
||
|
u32 hash;
|
||
|
i32 id;
|
||
|
};
|
||
|
|
||
|
struct File_Table{
|
||
|
File_Table_Entry *table;
|
||
|
i32 count, max;
|
||
|
};
|
||
|
|
||
|
internal u32
|
||
|
get_file_hash(String name){
|
||
|
u32 x = 5381;
|
||
|
int i = 0;
|
||
|
char c;
|
||
|
while (i < name.size){
|
||
|
c = name.str[i++];
|
||
|
x = ((x << 5) + x) + c;
|
||
|
}
|
||
|
return x;
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
table_add(File_Table *table, String name, i32 id){
|
||
|
Assert(table->count * 3 < table->max * 2);
|
||
|
|
||
|
File_Table_Entry entry, e;
|
||
|
i32 i;
|
||
|
|
||
|
entry.name = name;
|
||
|
entry.id = id;
|
||
|
entry.hash = get_file_hash(name);
|
||
|
i = entry.hash % table->max;
|
||
|
while ((e = table->table[i]).name.str){
|
||
|
if (e.hash == entry.hash && match(e.name, entry.name)){
|
||
|
return 1;
|
||
|
}
|
||
|
i = (i + 1) % table->max;
|
||
|
}
|
||
|
table->table[i] = entry;
|
||
|
++table->count;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
table_find_pos(File_Table *table, String name, i32 *index){
|
||
|
File_Table_Entry e;
|
||
|
i32 i;
|
||
|
u32 hash;
|
||
|
|
||
|
hash = get_file_hash(name);
|
||
|
i = hash % table->max;
|
||
|
while ((e = table->table[i]).name.size){
|
||
|
if (e.name.str && e.hash == hash && match(e.name, name)){
|
||
|
*index = i;
|
||
|
return 1;
|
||
|
}
|
||
|
i = (i + 1) % table->max;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
inline b32
|
||
|
table_find(File_Table *table, String name, i32 *id){
|
||
|
i32 pos;
|
||
|
b32 r = table_find_pos(table, name, &pos);
|
||
|
if (r) *id = table->table[pos].id;
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
inline b32
|
||
|
table_remove(File_Table *table, String name){
|
||
|
i32 pos;
|
||
|
b32 r = table_find_pos(table, name, &pos);
|
||
|
if (r){
|
||
|
table->table[pos].name.str = 0;
|
||
|
--table->count;
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
struct Working_Set{
|
||
|
Editing_File *files;
|
||
|
i32 file_count, file_max;
|
||
|
File_Node free_sentinel;
|
||
|
File_Node used_sentinel;
|
||
|
|
||
|
File_Table table;
|
||
|
|
||
|
String clipboards[64];
|
||
|
i32 clipboard_size, clipboard_max_size;
|
||
|
i32 clipboard_current, clipboard_rolling;
|
||
|
};
|
||
|
|
||
|
// Hot Directory
|
||
|
|
||
|
struct Hot_Directory{
|
||
|
String string;
|
||
|
File_List file_list;
|
||
|
char slash;
|
||
|
};
|
||
|
|
||
|
internal void
|
||
|
hot_directory_clean_end(Hot_Directory *hot_directory){
|
||
|
String *str = &hot_directory->string;
|
||
|
if (str->size != 0 && str->str[str->size-1] != hot_directory->slash){
|
||
|
str->size = reverse_seek_slash(*str) + 1;
|
||
|
str->str[str->size] = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal i32
|
||
|
hot_directory_quick_partition(File_Info *infos, i32 start, i32 pivot){
|
||
|
File_Info *p = infos + pivot;
|
||
|
File_Info *a = infos + start;
|
||
|
for (i32 i = start; i < pivot; ++i, ++a){
|
||
|
i32 comp = 0;
|
||
|
comp = p->folder - a->folder;
|
||
|
if (comp == 0) comp = compare(a->filename, p->filename);
|
||
|
if (comp < 0){
|
||
|
Swap(*a, infos[start]);
|
||
|
++start;
|
||
|
}
|
||
|
}
|
||
|
Swap(*p, infos[start]);
|
||
|
return start;
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
hot_directory_quick_sort(File_Info *infos, i32 start, i32 pivot){
|
||
|
i32 mid = hot_directory_quick_partition(infos, start, pivot);
|
||
|
if (start < mid-1) hot_directory_quick_sort(infos, start, mid-1);
|
||
|
if (mid+1 < pivot) hot_directory_quick_sort(infos, mid+1, pivot);
|
||
|
}
|
||
|
|
||
|
inline void
|
||
|
hot_directory_fixup(Hot_Directory *hot_directory, Working_Set *working_set){
|
||
|
File_List *files = &hot_directory->file_list;
|
||
|
if (files->count >= 2)
|
||
|
hot_directory_quick_sort(files->infos, 0, files->count - 1);
|
||
|
}
|
||
|
|
||
|
inline void
|
||
|
hot_directory_set(System_Functions *system, Hot_Directory *hot_directory,
|
||
|
String str, Working_Set *working_set){
|
||
|
b32 success = copy_checked(&hot_directory->string, str);
|
||
|
terminate_with_null(&hot_directory->string);
|
||
|
if (success){
|
||
|
if (str.size > 0){
|
||
|
system->set_file_list(&hot_directory->file_list, str);
|
||
|
}
|
||
|
else{
|
||
|
system->set_file_list(&hot_directory->file_list, make_string((char*)1, 0));
|
||
|
}
|
||
|
}
|
||
|
hot_directory_fixup(hot_directory, working_set);
|
||
|
}
|
||
|
|
||
|
inline void
|
||
|
hot_directory_reload(System_Functions *system, Hot_Directory *hot_directory, Working_Set *working_set){
|
||
|
system->set_file_list(&hot_directory->file_list, hot_directory->string);
|
||
|
hot_directory_fixup(hot_directory, working_set);
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
hot_directory_init(Hot_Directory *hot_directory, String base, String dir, char slash){
|
||
|
hot_directory->string = base;
|
||
|
hot_directory->string.str[255] = 0;
|
||
|
hot_directory->string.size = 0;
|
||
|
copy(&hot_directory->string, dir);
|
||
|
append(&hot_directory->string, slash);
|
||
|
hot_directory->slash = slash;
|
||
|
}
|
||
|
|
||
|
struct Hot_Directory_Match{
|
||
|
String filename;
|
||
|
b32 is_folder;
|
||
|
};
|
||
|
|
||
|
internal b32
|
||
|
filename_match(String query, Absolutes *absolutes, String filename, b32 case_sensitive){
|
||
|
b32 result;
|
||
|
result = (query.size == 0);
|
||
|
replace_char(query, '\\', '/');
|
||
|
replace_char(filename, '\\', '/');
|
||
|
if (!result) result = wildcard_match(absolutes, filename, case_sensitive);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
internal Hot_Directory_Match
|
||
|
hot_directory_first_match(Hot_Directory *hot_directory,
|
||
|
String str,
|
||
|
b32 include_files,
|
||
|
b32 exact_match,
|
||
|
b32 case_sensitive){
|
||
|
Hot_Directory_Match result = {};
|
||
|
|
||
|
replace_char(str, '\\', '/');
|
||
|
|
||
|
Absolutes absolutes;
|
||
|
if (!exact_match)
|
||
|
get_absolutes(str, &absolutes, 1, 1);
|
||
|
|
||
|
File_List *files = &hot_directory->file_list;
|
||
|
File_Info *info, *end;
|
||
|
end = files->infos + files->count;
|
||
|
for (info = files->infos; info != end; ++info){
|
||
|
String filename = info->filename;
|
||
|
b32 is_match = 0;
|
||
|
if (exact_match){
|
||
|
if (case_sensitive){
|
||
|
if (match(filename, str)) is_match = 1;
|
||
|
}
|
||
|
else{
|
||
|
if (match_unsensitive(filename, str)) is_match = 1;
|
||
|
}
|
||
|
}
|
||
|
else{
|
||
|
if (filename_match(str, &absolutes, filename, case_sensitive)) is_match = 1;
|
||
|
}
|
||
|
|
||
|
if (is_match){
|
||
|
result.is_folder = info->folder;
|
||
|
result.filename = filename;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
enum File_Sync_State{
|
||
|
SYNC_GOOD,
|
||
|
SYNC_BEHIND_OS,
|
||
|
SYNC_UNSAVED
|
||
|
};
|
||
|
|
||
|
inline File_Sync_State
|
||
|
buffer_get_sync(Editing_File *file){
|
||
|
File_Sync_State result = SYNC_GOOD;
|
||
|
if (file->state.last_4ed_write_time != file->state.last_sys_write_time)
|
||
|
result = SYNC_BEHIND_OS;
|
||
|
else if (file->state.last_4ed_edit_time > file->state.last_sys_write_time)
|
||
|
result = SYNC_UNSAVED;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
inline b32
|
||
|
buffer_needs_save(Editing_File *file){
|
||
|
b32 result = 0;
|
||
|
if (file->settings.unimportant == 0)
|
||
|
if (buffer_get_sync(file) == SYNC_UNSAVED)
|
||
|
result = 1;
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
inline b32
|
||
|
file_is_ready(Editing_File *file){
|
||
|
b32 result = 0;
|
||
|
if (file && file->state.is_loading == 0){
|
||
|
result = 1;
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
inline Editing_File*
|
||
|
working_set_contains(Working_Set *working, String filename){
|
||
|
Editing_File *result = 0;
|
||
|
i32 id;
|
||
|
replace_char(filename, '\\', '/');
|
||
|
if (table_find(&working->table, filename, &id)){
|
||
|
if (id >= 0 && id <= working->file_max){
|
||
|
result = working->files + id;
|
||
|
}
|
||
|
}
|
||
|
return (result);
|
||
|
}
|
||
|
|
||
|
// TODO(allen): Pick better first options.
|
||
|
internal Editing_File*
|
||
|
working_set_lookup_file(Working_Set *working_set, String string){
|
||
|
Editing_File *file = 0;
|
||
|
|
||
|
replace_char(string, '\\', '/');
|
||
|
|
||
|
{
|
||
|
File_Node *node, *used_nodes;
|
||
|
used_nodes = &working_set->used_sentinel;
|
||
|
for (dll_items(node, used_nodes)){
|
||
|
file = (Editing_File*)node;
|
||
|
if (string.size == 0 || match(string, file->name.live_name)){
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (node == used_nodes) file = 0;
|
||
|
}
|
||
|
|
||
|
if (!file){
|
||
|
File_Node *node, *used_nodes;
|
||
|
used_nodes = &working_set->used_sentinel;
|
||
|
for (dll_items(node, used_nodes)){
|
||
|
file = (Editing_File*)node;
|
||
|
if (string.size == 0 || has_substr(file->name.live_name, string)){
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (node == used_nodes) file = 0;
|
||
|
}
|
||
|
|
||
|
return (file);
|
||
|
}
|
||
|
|
||
|
struct Get_File_Result{
|
||
|
Editing_File *file;
|
||
|
i32 index;
|
||
|
};
|
||
|
|
||
|
internal Get_File_Result
|
||
|
working_set_get_available_file(Working_Set *working_set){
|
||
|
Get_File_Result result = {};
|
||
|
File_Node *node;
|
||
|
|
||
|
if (working_set->file_count < working_set->file_max){
|
||
|
node = working_set->free_sentinel.next;
|
||
|
Assert(node != &working_set->free_sentinel);
|
||
|
|
||
|
result.file = (Editing_File*)node;
|
||
|
result.index = (i32)(result.file - working_set->files);
|
||
|
|
||
|
++working_set->file_count;
|
||
|
|
||
|
dll_remove(node);
|
||
|
*result.file = {};
|
||
|
dll_insert(&working_set->used_sentinel, node);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
inline void
|
||
|
working_set_free_file(Working_Set *working_set, Editing_File *file){
|
||
|
file->state.is_dummy = 1;
|
||
|
dll_remove(&file->node);
|
||
|
dll_insert(&working_set->free_sentinel, &file->node);
|
||
|
--working_set->file_count;
|
||
|
}
|
||
|
|
||
|
inline Get_File_Result
|
||
|
working_set_get_file(Working_Set *working_set, i32 id, b32 require_active){
|
||
|
Get_File_Result result = {};
|
||
|
if (id > 0 && id <= working_set->file_max){
|
||
|
result.file = working_set->files + id;
|
||
|
result.index = id;
|
||
|
if (result.file->state.is_dummy && require_active){
|
||
|
result.file = 0;
|
||
|
result.index = 0;
|
||
|
}
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
inline void
|
||
|
file_set_to_loading(Editing_File *file){
|
||
|
file->state = {};
|
||
|
file->settings = {};
|
||
|
file->state.is_loading = 1;
|
||
|
}
|
||
|
|
||
|
// BOTTOM
|
||
|
|