diff --git a/README.md b/README.md index 73631e9d..d4f2b5e2 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ be productive with cquery. Here's a list of implemented features: * diagnostics * code actions (clang FixIts) * darken/fade code disabled by preprocessor - * goto definition on include to jump to file + * goto definition, document links on include to jump to file # Setup - build cquery, install extension, setup project diff --git a/src/command_line.cc b/src/command_line.cc index f5c7bf9a..7c5677ac 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -914,6 +914,7 @@ void RegisterMessageTypes() { MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); + MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); @@ -1539,6 +1540,9 @@ bool QueryDbMainLoop( response.result.capabilities.documentSymbolProvider = true; response.result.capabilities.workspaceSymbolProvider = true; + response.result.capabilities.documentLinkProvider = lsDocumentLinkOptions(); + response.result.capabilities.documentLinkProvider->resolveProvider = false; + ipc->SendOutMessageToClient(IpcId::Initialize, response); break; } @@ -2097,6 +2101,68 @@ bool QueryDbMainLoop( break; } + case IpcId::TextDocumentDocumentLink: { + auto msg = static_cast(message.get()); + + Out_TextDocumentDocumentLink response; + response.id = msg->id; + + if (config->showDocumentLinksOnIncludes) { + QueryFile* file = FindFile(db, msg->params.textDocument.uri.GetPath()); + if (!file) { + std::cerr << "Unable to find file " << msg->params.textDocument.uri.GetPath() << std::endl; + break; + } + + WorkingFile* working_file = working_files->GetFileByFilename(msg->params.textDocument.uri.GetPath()); + if (!working_file) { + std::cerr << "Unable to find working file " << msg->params.textDocument.uri.GetPath() << std::endl; + break; + } + for (const IndexInclude& include : file->def.includes) { + optional buffer_line; + optional buffer_line_content = working_file->GetBufferLineContentFromIndexLine(include.line, &buffer_line); + if (!buffer_line || !buffer_line_content) + continue; + + // Find starting and ending quote. + int start = 0; + while (start < buffer_line_content->size()) { + char c = (*buffer_line_content)[start]; + ++start; + if (c == '"' || c == '<') + break; + } + if (start == buffer_line_content->size()) + continue; + int end = buffer_line_content->size(); + while (end > 0) { + char c = (*buffer_line_content)[end]; + if (c == '"' || c == '>') + break; + --end; + } + if (start >= end) + break; + + + lsDocumentLink link; + link.target = lsDocumentUri::FromPath(include.resolved_path); + // Subtract 1 from line because querydb stores 1-based lines but + // vscode expects 0-based lines. + link.range.start.line = *buffer_line - 1; + link.range.start.character = start; + link.range.end.line = *buffer_line - 1; + link.range.end.character = end; + response.result.push_back(link); + } + } + + response.Write(std::cerr); + ipc->SendOutMessageToClient(IpcId::TextDocumentDocumentLink, response); + break; + } + case IpcId::TextDocumentCodeAction: { // NOTE: This code snippet will generate some FixIts for testing: // @@ -2495,6 +2561,7 @@ void LanguageServerStdinLoop(IndexerConfig* config, std::unordered_map { + const static IpcId kIpcId = IpcId::TextDocumentDocumentLink; + + struct DocumentLinkParams { + // The document to provide document links for. + lsTextDocumentIdentifier textDocument; + }; + + lsRequestId id; + DocumentLinkParams params; +}; +MAKE_REFLECT_STRUCT(Ipc_TextDocumentDocumentLink::DocumentLinkParams, textDocument); +MAKE_REFLECT_STRUCT(Ipc_TextDocumentDocumentLink, id, params); +// A document link is a range in a text document that links to an internal or external resource, like another +// text document or a web site. +struct lsDocumentLink { + // The range this link applies to. + lsRange range; + // The uri this link points to. If missing a resolve request is sent later. + optional target; +}; +MAKE_REFLECT_STRUCT(lsDocumentLink, range, target); +struct Out_TextDocumentDocumentLink : public lsOutMessage { + lsRequestId id; + NonElidedVector result; +}; +MAKE_REFLECT_STRUCT(Out_TextDocumentDocumentLink, jsonrpc, id, result); + + // List code lens in a document. struct lsDocumentCodeLensParams { lsTextDocumentIdentifier textDocument; diff --git a/src/working_files.cc b/src/working_files.cc index 65220349..88a4be5c 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -164,6 +164,22 @@ optional WorkingFile::GetIndexLineFromBufferLine(int buffer_line) const { return closest_index_line; } +optional WorkingFile::GetBufferLineContentFromIndexLine(int indexed_line, optional* out_buffer_line) const { + optional buffer_line = GetBufferLineFromIndexLine(indexed_line); + if (out_buffer_line) + *out_buffer_line = buffer_line; + + if (!buffer_line) + return nullopt; + + if (*buffer_line < 1 || *buffer_line >= all_buffer_lines.size()) { + std::cerr << "GetBufferLineContentFromIndexLine buffer line lookup not in all_buffer_lines" << std::endl; + return nullopt; + } + + return all_buffer_lines[*buffer_line - 1]; +} + std::string WorkingFile::FindClosestCallNameInBuffer(lsPosition position, int* active_parameter, lsPosition* completion_position) const { *active_parameter = 0; diff --git a/src/working_files.h b/src/working_files.h index 26f321b0..c54f6bc9 100644 --- a/src/working_files.h +++ b/src/working_files.h @@ -44,6 +44,8 @@ struct WorkingFile { // accepts and returns 1-based lines. optional GetIndexLineFromBufferLine(int buffer_line) const; + optional GetBufferLineContentFromIndexLine(int indexed_line, optional* out_buffer_line) const; + // Finds the closest 'callable' name prior to position. This is used for // signature help to filter code completion results. //