static Content_Index *global_content_index = 0; internal String get_name_of_index_type(Indexed_Content_Element_Type content_type) { String result = {}; switch(content_type) { case ContentType_Unspecified: {result = make_lit_string("Unspecified");} break; case ContentType_Function: {result = make_lit_string("Function");} break; case ContentType_Type: {result = make_lit_string("Type");} break; case ContentType_Macro: {result = make_lit_string("Macro");} break; case ContentType_EnumValue: {result = make_lit_string("Enum Value");} break; case ContentType_ForwardDeclaration: {result = make_lit_string("Forward Declaration");} break; case ContentType_TODO: {result = make_lit_string("TODO");} break; case ContentType_NOTE: {result = make_lit_string("Note");} break; case ContentType_IMPORTANT: {result = make_lit_string("Important");} break; case ContentType_STUDY: {result = make_lit_string("Study");} break; } return(result); } static Content_Index * create_content_index(Application_Links *app) { // TODO(casey): I really just want this to be its own allocating arena, but I can't find anything like that in 4coder? // All arenas seem to be fixed size and they can't get more memory if they run out :( Content_Index *index = (Content_Index *)malloc(sizeof(Content_Index)); memset(index, 0, sizeof(*index)); return(index); } static Indexed_Content_Element ** get_element_slot_for_name(Content_Index *index, String name) { Indexed_Content_Element **slot = &index->name_hash[table_hash_u8((uint8_t *)name.str, name.size) % ArrayCount(index->name_hash)]; return(slot); } static Indexed_Buffer * get_or_create_indexed_buffer(Content_Index *index, Buffer_ID buffer_id) { Indexed_Buffer *result = 0; Indexed_Buffer **slot = &index->buffer_hash[buffer_id % ArrayCount(index->buffer_hash)]; for(Indexed_Buffer *search = *slot; search; search = search->next_in_hash) { if(search->buffer_id == buffer_id) { result = search; break; } } if(!result) { result = (Indexed_Buffer *)malloc(sizeof(Indexed_Buffer)); result->buffer_id = buffer_id; result->first_tag = 0; result->next_in_hash = *slot; *slot = result; } return(result); } static color_pair get_color_pair_for(Indexed_Content_Element_Type type) { color_pair result = {}; switch(type) { case ContentType_Function: { result.fore = CC_Function; } break; case ContentType_Type: { result.fore = CC_Type; } break; case ContentType_Macro: { result.fore = CC_Macro; } break; case ContentType_EnumValue: { result.fore = CC_EnumValue; } break; case ContentType_ForwardDeclaration: { result.fore = CC_ForwardDeclaration; } break; case ContentType_TODO: { result.fore = CC_TODO; } break; case ContentType_NOTE: { result.fore = CC_NOTE; } break; case ContentType_IMPORTANT: { result.fore = CC_IMPORTANT; } break; case ContentType_STUDY: { result.fore = CC_STUDY; } break; } return(result); } static Indexed_Content_Element * begin_element_in_index(Content_Index *index, Indexed_Content_Element_Type type, Buffer_ID buffer_id, int32_t location, int32_t name_size, int32_t content_size) { Indexed_Content_Element *elem = 0; // TODO(casey): I really just want this to be its own allocating arena, but I can't find anything like that in 4coder? // All arenas seem to be fixed size and they can't get more memory if they run out :( uint8_t *mem = (uint8_t *)malloc(sizeof(Indexed_Content_Element) + name_size + content_size); if(mem) { elem = (Indexed_Content_Element *)mem; elem->next_in_hash = 0; elem->type = type; elem->name.memory_size = elem->name.size = name_size; elem->name.str = (char *)(elem + 1); elem->content.memory_size = elem->content.size = content_size; elem->content.str = elem->name.str + name_size; elem->color = get_color_pair_for(type); elem->buffer_id = buffer_id; elem->last_known_location = location; index->total_string_space += name_size + content_size; ++index->element_count; } return(elem); } static void end_element_in_index(Content_Index *index, Indexed_Content_Element *elem, u32 flags) { if(elem) { // NOTE(casey): By convention, we make content into a single line, and remove leading/trailing whitespace condense_whitespace(&elem->name); condense_whitespace(&elem->content); Indexed_Content_Element **slot = &index->unhashed; if(!(flags & IndexFlag_NoNameLookup)) { slot = get_element_slot_for_name(index, elem->name); } elem->next_in_hash = *slot; *slot = elem; Indexed_Buffer *buffer = get_or_create_indexed_buffer(index, elem->buffer_id); ++buffer->element_type_counts[elem->type]; ++index->element_type_counts[elem->type]; } } static Indexed_Content_Element * add_element_to_index(Application_Links *app, Content_Index *index, Indexed_Content_Element_Type type, Buffer_Summary *buffer, int32_t location, int32_t name_start, int32_t name_end, int32_t content_start, int32_t content_end, u32 flags = 0) { Indexed_Content_Element *elem = 0; if((name_start <= name_end) && (content_start <= content_end)) { elem = begin_element_in_index(index, type, buffer->buffer_id, location, name_end - name_start, content_end - content_start); if(elem) { buffer_read_range(app, buffer, name_start, name_end, elem->name.str); buffer_read_range(app, buffer, content_start, content_end, elem->content.str); end_element_in_index(index, elem, flags); } } return(elem); } static Indexed_Content_Element * add_element_to_index(Content_Index *index, Indexed_Content_Element_Type type, Buffer_ID buffer_id, int32_t location, String name, String content, u32 flags = 0) { Indexed_Content_Element *elem = begin_element_in_index(index, type, buffer_id, location, name.size, content.size); if(elem) { copy_ss(&elem->name, name); copy_ss(&elem->content, content); end_element_in_index(index, elem, flags); } return(elem); } static Indexed_Content_Tag * add_tag_to_index(Content_Index *index, Buffer_Summary *buffer, uint32_t type, int32_t start, int32_t end, String content) { Indexed_Content_Tag *tag = 0; if(start <= end) { Indexed_Buffer *index_buffer = get_or_create_indexed_buffer(index, buffer->buffer_id); // TODO(casey): I really just want this to be its own allocating arena, but I can't find anything like that in 4coder? // All arenas seem to be fixed size and they can't get more memory if they run out :( uint8_t *mem = (uint8_t *)malloc(sizeof(Indexed_Content_Tag) + content.size); if(mem) { tag = (Indexed_Content_Tag *)mem; tag->content.size = tag->content.memory_size = content.size; tag->content.str = (char *)(tag + 1); copy_ss(&tag->content, content); tag->type = type; tag->start = start; tag->end = end; tag->next_in_buffer = index_buffer->first_tag; index_buffer->first_tag = tag; } } return(tag); } static Indexed_Content_Element * get_element_by_name(Content_Index *index, String name) { Indexed_Content_Element *result = 0; if(index) { for(Indexed_Content_Element *elem = *get_element_slot_for_name(index, name); elem; elem = elem->next_in_hash) { if(compare_ss(elem->name, name) == 0) { result = elem; break; } } } return(result); } static void remove_buffer_from_index(Application_Links *app, Content_Index *index, Buffer_ID buffer_id) { for(int hash_index = 0; hash_index <= ArrayCount(index->name_hash); ++hash_index) { for(Indexed_Content_Element **elem_ptr = &index->name_hash[hash_index]; *elem_ptr; ) { Indexed_Content_Element *elem = *elem_ptr; if(elem->buffer_id == buffer_id) { *elem_ptr = elem->next_in_hash; free(elem); } else { elem_ptr = &elem->next_in_hash; } } } Indexed_Buffer *index_buffer = get_or_create_indexed_buffer(index, buffer_id); while(index_buffer->first_tag) { Indexed_Content_Tag *tag = index_buffer->first_tag; index_buffer->first_tag = tag->next_in_buffer; free(tag); } for(u32 type_index = 0; type_index < ContentType_Count; ++type_index) { index->element_type_counts[type_index] -= index_buffer->element_type_counts[type_index]; index_buffer->element_type_counts[type_index] = 0; } } static void add_buffer_to_index(Application_Links *app, Content_Index *index, Buffer_Summary *buffer) { ++index->buffer_count; Token_Iterator iter = iterate_tokens(app, buffer, 0); int32_t paren_level = 0; int32_t brace_level = 0; while(iter.valid) { Cpp_Token token = get_next_token(app, &iter); if(token.flags & CPP_TFLAG_PP_BODY) { // TODO(casey): Do we need to pick up macros here? } else { switch(token.type) { case CPP_TOKEN_COMMENT: { // NOTE(casey): Comments can contain special operations that we want to perform, so we thunk to a special parser to handle those parse_comment(app, index, buffer, token); } break; case CPP_PP_DEFINE: { int32_t content_start = token.start; token = get_next_token(app, &iter); // TODO(casey): Allen, how do I scan for the next "not continued newline"? int32_t content_end = token.start + token.size; add_element_to_index(app, index, ContentType_Macro, buffer, token.start, token.start, token.start + token.size, content_start, content_end); } break; case CPP_TOKEN_KEY_TYPE_DECLARATION: { bool32 forward_declaration = true; int32_t content_start = token.start; Cpp_Token element_name = {}; Cpp_Token last_identifier = {}; String typedef_keyword = make_lit_string("typedef"); if(token_text_is(app, buffer, token, typedef_keyword)) { // NOTE(casey): Typedefs can't be "forward declared", so we always record their location. forward_declaration = false; // NOTE(casey): This is a simple "usually works" parser for typedefs. It is well-known that C grammar // doesn't actually allow you to parse typedefs properly without actually knowing the declared types // at the time, which of course we cannot do since we parse per-file, and don't even know things we // would need (like -D switches, etc.) // TODO(casey): If eventually this were upgraded to a more thoughtful parser, struct/union/enum defs // would be parsed from brace to brace, so they could be handled recursively inside of a typedef for // the pattern "typedef struct {} foo;", which currently we do not handle (we ignore the foo). int typedef_paren_level = 0; while(iter.valid) { token = get_next_token(app, &iter); if(token.type == CPP_TOKEN_IDENTIFIER) { last_identifier = token; } else if(token.type == CPP_TOKEN_PARENTHESE_OPEN) { if(typedef_paren_level == 0) { // NOTE(casey): If we are going back into a parenthetical, it means that whatever the last // identifier we saw is our best candidate. element_name = last_identifier; } ++typedef_paren_level; } else if(token.type == CPP_TOKEN_PARENTHESE_CLOSE) { --typedef_paren_level; } else if(token.type == CPP_TOKEN_KEY_TYPE_DECLARATION) { // TODO(casey): If we _really_ wanted to, we could parse the type here recursively, // and then we could capture things that were named differently a la "typedef struct foo {...} bar;" // but I think that should wait until the 4coder parser is more structural, because I don't // know how much parsing we _really_ want to do here. forward_declaration = true; break; } else if(token.type == CPP_TOKEN_SEMICOLON) { break; } } if(element_name.size == 0) { element_name = last_identifier; } } if(!element_name.size) { while(iter.valid) { token = peek_token(app, &iter); if(token.type == CPP_TOKEN_IDENTIFIER) { element_name = token; get_next_token(app, &iter); } else if((token.type == CPP_TOKEN_KEY_MODIFIER) || (token.type == CPP_TOKEN_KEY_QUALIFIER) || (token.type == CPP_TOKEN_KEY_ACCESS) || (token.type == CPP_TOKEN_KEY_LINKAGE)) { // NOTE(casey): Let it go. get_next_token(app, &iter); } else if(token.type == CPP_TOKEN_BRACE_OPEN) { // NOTE(casey): It's probably type definition forward_declaration = false; break; } else { // NOTE(casey): It's probably a forward declaration break; } } } int32_t content_end = token.start; if(element_name.size) { Indexed_Content_Element_Type type = ContentType_Type; if(forward_declaration) { type = ContentType_ForwardDeclaration; } add_element_to_index(app, index, type, buffer, element_name.start, element_name.start, element_name.start + element_name.size, content_start, content_end); } } break; case CPP_TOKEN_PARENTHESE_OPEN: { ++paren_level; } break; case CPP_TOKEN_PARENTHESE_CLOSE: { --paren_level; } break; case CPP_TOKEN_BRACE_OPEN: { if((paren_level == 0) && (brace_level == 0)) { // NOTE(casey): This is presumably a function, see if we can find it's name. int32_t content_end = token.start; int32_t content_start = content_end; int32_t name_start = 0; int32_t name_end = 0; Token_Iterator back = iter; get_prev_token(app, &back); get_prev_token(app, &back); Cpp_Token comment_pass = get_prev_token(app, &back); while(comment_pass.type == CPP_TOKEN_COMMENT) { comment_pass = get_prev_token(app, &back); } if(comment_pass.type == CPP_TOKEN_PARENTHESE_CLOSE) { paren_level = -1; while(back.valid) { if(token.type != CPP_TOKEN_COMMENT) { content_start = token.start; } token = get_prev_token(app, &back); if((paren_level == 0) && (brace_level == 0) && ((token.flags & CPP_TFLAG_PP_BODY) || (token.type == CPP_TOKEN_BRACE_CLOSE) || (token.type == CPP_TOKEN_SEMICOLON) || (token.type == CPP_TOKEN_EOF))) { break; } else { switch(token.type) { case CPP_TOKEN_PARENTHESE_OPEN: { ++paren_level; } break; case CPP_TOKEN_PARENTHESE_CLOSE: { --paren_level; } break; case CPP_TOKEN_BRACE_OPEN: { ++brace_level; } break; case CPP_TOKEN_BRACE_CLOSE: { --brace_level; } break; case CPP_TOKEN_IDENTIFIER: { if((paren_level == 0) && (brace_level == 0) && (name_start == 0)) { name_start = token.start; name_end = token.start + token.size; Token_Iterator probe = back; get_next_token(app, &probe); for(;;) { Cpp_Token test = get_next_token(app, &probe); if((test.type == 0) || (test.type == CPP_TOKEN_EOF) || (test.type == CPP_TOKEN_PARENTHESE_CLOSE) || (test.type == CPP_TOKEN_PARENTHESE_OPEN)) { name_end = test.start; break; } } } } break; } } } if(name_start < name_end) { Indexed_Content_Element_Type type = ContentType_Function; add_element_to_index(app, index, type, buffer, name_start, name_start, name_end, content_start, content_end); } brace_level = 0; paren_level = 0; } } ++brace_level; } break; case CPP_TOKEN_BRACE_CLOSE: { --brace_level; } break; } } } } static void add_all_buffers_to_index(Application_Links *app, Content_Index *index) { for(Buffer_Summary buffer = get_buffer_first(app, AccessAll); buffer.exists; get_buffer_next(app, &buffer, AccessAll)) { int32_t Unimportant = true; buffer_get_setting(app, &buffer, BufferSetting_Unimportant, &Unimportant); if(buffer.tokens_are_ready && !Unimportant) { add_buffer_to_index(app, index, &buffer); } } } static void update_index_for_buffer(Application_Links *app, Content_Index *index, Buffer_Summary *buffer) { remove_buffer_from_index(app, index, buffer->buffer_id); add_buffer_to_index(app, index, buffer); } static Content_Index * get_global_content_index(Application_Links *app) { Content_Index *result = global_content_index; if(!result) { global_content_index = create_content_index(app); add_all_buffers_to_index(app, global_content_index); result = global_content_index; } return(result); } static void jump_to_element(Application_Links *app, View_Summary *view, Indexed_Content_Element *elem) { if (view->buffer_id != elem->buffer_id) { view_set_buffer(app, view, elem->buffer_id, 0); } Buffer_Seek seek; seek.type = buffer_seek_pos; seek.pos = elem->last_known_location;; view_set_cursor(app, view, seek, true); } static void jump_to_element_activate(Application_Links *app, Partition *scratch, Heap *heap, View_Summary *view, Lister_State *state, String text_field, void *user_data, bool32 activated_by_mouse) { lister_default(app, scratch, heap, view, state, ListerActivation_Finished); if(user_data) { jump_to_element(app, view, (Indexed_Content_Element *)user_data); } } typedef bool32 element_type_predicate(Application_Links *app, Content_Index *index, Indexed_Content_Element *elem, void *user_data); static bool32 element_is_definition(Application_Links *app, Content_Index *index, Indexed_Content_Element *elem, void *user_data) { bool32 result = ((elem->type == ContentType_Function) || (elem->type == ContentType_Type) || (elem->type == ContentType_Macro) || (elem->type == ContentType_EnumValue)); return(result); } static bool32 element_type_equals(Application_Links *app, Content_Index *index, Indexed_Content_Element *elem, void *user_data) { bool32 result = (elem->type == (uint64_t)user_data); return(result); } static void jump_to_element_lister(Application_Links *app, char *Label, element_type_predicate *predicate, void *user_data = 0) { Partition *arena = &global_part; View_Summary view = get_active_view(app, AccessAll); view_end_ui_mode(app, &view); Temp_Memory temp = begin_temp_memory(arena); Content_Index *index = get_global_content_index(app); Lister_Option *options = push_array(arena, Lister_Option, index->element_count); int32_t option_index = 0; for(int hash_index = 0; hash_index <= ArrayCount(index->name_hash); ++hash_index) { for(Indexed_Content_Element *elem = index->name_hash[hash_index]; elem; elem = elem->next_in_hash) { if(predicate(app, index, elem, user_data)) { options[option_index].text_color = get_color(elem->color.fore); options[option_index].pop_color = get_color(CC_DefaultText); options[option_index].back_color = get_color(elem->color.back); options[option_index].string = elem->name; options[option_index].status = elem->content; options[option_index].user_data = elem; ++option_index; } } } begin_integrated_lister__basic_list(app, Label, jump_to_element_activate, 0, 0, options, option_index, index->total_string_space, &view); end_temp_memory(temp); }