// this file deals with ctrl+click "go to definition", and // the definitions menu (Ctrl+D) #include "ted.h" void definition_cancel_lookup(Ted *ted) { Definitions *defs = &ted->definitions; ted_cancel_lsp_request(ted, &defs->last_request); } static SymbolKind symbol_kind_to_ted(LSPSymbolKind kind) { switch (kind) { case LSP_SYMBOL_OTHER: case LSP_SYMBOL_FILE: case LSP_SYMBOL_MODULE: case LSB_SYMBOL_NAMESPACE: case LSP_SYMBOL_PACKAGE: return SYMBOL_OTHER; case LSP_SYMBOL_CLASS: case LSP_SYMBOL_TYPEPARAMETER: case LSP_SYMBOL_ENUM: case LSP_SYMBOL_INTERFACE: case LSP_SYMBOL_STRUCT: case LSP_SYMBOL_EVENT: // i have no clue what this is. let's say it's a type. return SYMBOL_TYPE; case LSP_SYMBOL_PROPERTY: case LSP_SYMBOL_FIELD: case LSP_SYMBOL_KEY: return SYMBOL_FIELD; case LSP_SYMBOL_CONSTRUCTOR: case LSP_SYMBOL_FUNCTION: case LSP_SYMBOL_OPERATOR: case LSP_SYMBOL_METHOD: return SYMBOL_FUNCTION; case LSP_SYMBOL_VARIABLE: return SYMBOL_VARIABLE; case LSP_SYMBOL_CONSTANT: case LSP_SYMBOL_STRING: case LSP_SYMBOL_NUMBER: case LSP_SYMBOL_BOOLEAN: case LSP_SYMBOL_ARRAY: case LSP_SYMBOL_OBJECT: case LSP_SYMBOL_ENUMMEMBER: case LSP_SYMBOL_NULL: return SYMBOL_CONSTANT; } return SYMBOL_OTHER; } void definition_goto(Ted *ted, LSP *lsp, const char *name, LSPDocumentPosition position, GotoType type) { Definitions *defs = &ted->definitions; if (lsp) { // cancel old request definition_cancel_lookup(ted); LSPRequestType request_type = LSP_REQUEST_DEFINITION; switch (type) { case GOTO_DEFINITION: request_type = LSP_REQUEST_DEFINITION; break; case GOTO_DECLARATION: request_type = LSP_REQUEST_DECLARATION; break; case GOTO_TYPE_DEFINITION: request_type = LSP_REQUEST_TYPE_DEFINITION; break; case GOTO_IMPLEMENTATION: request_type = LSP_REQUEST_IMPLEMENTATION; break; } // send that request LSPRequest request = {.type = request_type}; request.data.definition.position = position; LSPServerRequestID id = lsp_send_request(lsp, &request); if (id.id == 0 && request.type == LSP_REQUEST_IMPLEMENTATION) { // if we can't go to the implementation, try going to the definition request.type = LSP_REQUEST_DEFINITION; id = lsp_send_request(lsp, &request); } defs->last_request = id; defs->last_request_time = ted->frame_time; } else { // just go to the tag tag_goto(ted, name); } } void definitions_frame(Ted *ted) { Definitions *defs = &ted->definitions; if (defs->last_request.id && ted->frame_time - defs->last_request_time > 0.2) { ted->cursor = ted->cursor_wait; } } static void definitions_clear_entries(Definitions *defs) { arr_foreach_ptr(defs->all_definitions, SymbolInfo, def) { free(def->name); free(def->detail); } arr_clear(defs->all_definitions); arr_clear(defs->selector.entries); defs->selector.n_entries = 0; } static int definition_entry_qsort_cmp(const void *av, const void *bv) { const SymbolInfo *a = av, *b = bv; // first, sort by length size_t a_len = strlen(a->name), b_len = strlen(b->name); if (a_len < b_len) return -1; if (a_len > b_len) return 1; // then sort alphabetically int cmp = strcmp(a->name, b->name); if (cmp) return cmp; // then sort by detail if (!a->detail && b->detail) return -1; if (a->detail && !b->detail) return 1; if (!a->detail && !b->detail) return 0; return strcmp(a->detail, b->detail); } // put the entries matching the search term into the selector. static void definitions_selector_filter_entries(Ted *ted) { Definitions *defs = &ted->definitions; Selector *sel = &defs->selector; // create selector entries based on search term char *search_term = str32_to_utf8_cstr(buffer_get_line(&ted->line_buffer, 0)); arr_clear(sel->entries); for (u32 i = 0; i < arr_len(defs->all_definitions); ++i) { SymbolInfo *info = &defs->all_definitions[i]; if (!search_term || strstr_case_insensitive(info->name, search_term)) { SelectorEntry *entry = arr_addp(sel->entries); entry->name = info->name; entry->color = info->color; entry->detail = info->detail; // this isn't exactly ideal but we're sorting these entries so // it's probably the nicest way of keeping track of the definition // this corresponds to entry->userdata = i; } // don't try to display too many entries if (arr_len(sel->entries) >= 1000) break; } free(search_term); arr_qsort(sel->entries, definition_entry_qsort_cmp); sel->n_entries = arr_len(sel->entries); sel->cursor = clamp_u32(sel->cursor, 0, sel->n_entries); } void definitions_process_lsp_response(Ted *ted, LSP *lsp, const LSPResponse *response) { Definitions *defs = &ted->definitions; if (response->request.id != defs->last_request.id) { // response to an old/irrelevant request return; } defs->last_request.id = 0; switch (response->request.type) { case LSP_REQUEST_DEFINITION: case LSP_REQUEST_DECLARATION: case LSP_REQUEST_TYPE_DEFINITION: case LSP_REQUEST_IMPLEMENTATION: { // handle textDocument/definition or textDocument/declaration response const LSPResponseDefinition *response_def = &response->data.definition; if (!arr_len(response_def->locations)) { // no definition. do the error cursor. ted_flash_error_cursor(ted); return; } LSPLocation location = response_def->locations[0]; const char *path = lsp_document_path(lsp, location.document); if (!ted_open_file(ted, path)) { ted_flash_error_cursor(ted); return; } LSPDocumentPosition position = lsp_location_start_position(location); ted_go_to_lsp_document_position(ted, lsp, position); } break; case LSP_REQUEST_WORKSPACE_SYMBOLS: { // handle workspace/symbol response const LSPResponseWorkspaceSymbols *response_syms = &response->data.workspace_symbols; const LSPSymbolInformation *symbols = response_syms->symbols; const Settings *settings = ted_active_settings(ted); const u32 *colors = settings->colors; definitions_clear_entries(defs); arr_set_len(defs->all_definitions, arr_len(symbols)); for (size_t i = 0; i < arr_len(symbols); ++i) { const LSPSymbolInformation *symbol = &symbols[i]; SymbolInfo *def = &defs->all_definitions[i]; def->name = str_dup(lsp_response_string(response, symbol->name)); SymbolKind kind = symbol_kind_to_ted(symbol->kind); def->color = colors[color_for_symbol_kind(kind)]; def->from_lsp = true; def->position = lsp_location_start_position(symbol->location); const char *container_name = lsp_response_string(response, symbol->container); const char *filename = path_filename(lsp_document_path(lsp, def->position.document)); bool has_container = *container_name != 0; def->detail = a_sprintf("%s%s%s:%" PRIu32, container_name, has_container ? ", " : "", filename, def->position.pos.line + 1); } definitions_selector_filter_entries(ted); } break; default: debug_println("?? bad request type in %s : %u:%u", __func__, response->request.id, response->request.type); break; } } void definitions_send_request_if_needed(Ted *ted) { LSP *lsp = buffer_lsp(ted->prev_active_buffer); if (!lsp) return; Definitions *defs = &ted->definitions; char *query = buffer_contents_utf8_alloc(&ted->line_buffer); if (defs->last_request_query && strcmp(defs->last_request_query, query) == 0) { free(query); return; // no need to update symbols } LSPRequest request = {.type = LSP_REQUEST_WORKSPACE_SYMBOLS}; LSPRequestWorkspaceSymbols *syms = &request.data.workspace_symbols; syms->query = str_dup(query); // cancel old request definition_cancel_lookup(ted); defs->last_request = lsp_send_request(lsp, &request); defs->last_request_time = ted->frame_time; free(defs->last_request_query); defs->last_request_query = query; } void definitions_selector_open(Ted *ted) { Definitions *defs = &ted->definitions; definitions_clear_entries(defs); LSP *lsp = ted->prev_active_buffer ? buffer_lsp(ted->prev_active_buffer) : ted_active_lsp(ted); if (lsp) { definitions_send_request_if_needed(ted); } else { defs->all_definitions = tags_get_symbols(ted); } ted_switch_to_buffer(ted, &ted->line_buffer); buffer_select_all(ted->active_buffer); defs->selector.cursor = 0; } void definitions_selector_close(Ted *ted) { Definitions *defs = &ted->definitions; definitions_clear_entries(defs); ted_cancel_lsp_request(ted, &defs->last_request); free(defs->last_request_query); defs->last_request_query = NULL; } void definitions_selector_update(Ted *ted) { Definitions *defs = &ted->definitions; Selector *sel = &defs->selector; sel->enable_cursor = true; definitions_selector_filter_entries(ted); // send new request if search term has changed. // this is needed because e.g. clangd gives an incomplete list definitions_send_request_if_needed(ted); char *chosen = selector_update(ted, sel); if (chosen) { // for LSP go-to-definition, we ignore `chosen` and use the cursor instead. // this is because a single symbol can have multiple definitions, // e.g. with overloading. if (sel->cursor >= sel->n_entries) { assert(0); return; } u64 def_idx = sel->entries[sel->cursor].userdata; if (def_idx >= arr_len(defs->all_definitions)) { assert(0); return; } SymbolInfo *info = &defs->all_definitions[def_idx]; if (info->from_lsp) { // NOTE: we need to get this before calling menu_close, // since that clears selector_all_definitions LSPDocumentPosition position = info->position; menu_close(ted); ted_go_to_lsp_document_position(ted, NULL, position); } else { menu_close(ted); tag_goto(ted, chosen); } free(chosen); } } void definitions_selector_render(Ted *ted, Rect bounds) { Definitions *defs = &ted->definitions; Selector *sel = &defs->selector; sel->bounds = bounds; selector_render(ted, sel); }