Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/index/merged_index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "index/serialization.h"
#include "support/filesystem.h"
#include "syntax/lexer.h"

#include "llvm/ADT/DenseSet.h"
#include "llvm/Support/raw_os_ostream.h"
Expand Down Expand Up @@ -443,6 +444,44 @@ void MergedIndex::lookup(this const Self& self,
}
}

std::optional<std::uint32_t> MergedIndex::find_include_definition(this const Self& self,
std::uint32_t offset) {
if(self.impl) {
auto& index = *self.impl;
auto argument = find_directive_argument_at(index.content, offset);
if(!argument) {
return std::nullopt;
}
for(auto& [_, context]: index.compilation_contexts) {
for(auto& location: context.include_locations) {
if(location.include == static_cast<std::uint32_t>(-1) &&
location.line == argument->line) {
return location.path_id;
}
}
}
} else if(self.buffer) {
auto index = fbs::GetRoot<binary::MergedIndex>(self.buffer->getBufferStart());
llvm::StringRef content;
if(auto* stored_content = index->content()) {
content = stored_content->string_view();
}
auto argument = find_directive_argument_at(content, offset);
if(!argument) {
return std::nullopt;
}
for(auto context: *index->compilation_contexts()) {
for(auto location: *context->include_locations()) {
if(location->include_id() == static_cast<std::uint32_t>(-1) &&
location->line() == argument->line) {
return location->path_id();
}
}
}
}
return std::nullopt;
}

void MergedIndex::lookup(this const Self& self,
SymbolHash symbol,
RelationKind kind,
Expand Down
5 changes: 5 additions & 0 deletions src/index/merged_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <chrono>
#include <cstdint>
#include <memory>
#include <optional>
#include <vector>

#include "index/tu_index.h"
Expand Down Expand Up @@ -48,6 +49,10 @@ class MergedIndex {
std::uint32_t offset,
llvm::function_ref<bool(const Occurrence&)> callback);

/// Find the included path id for an include directive argument at `offset`.
std::optional<std::uint32_t> find_include_definition(this const Self& self,
std::uint32_t offset);

/// Lookup the relations of given symbol.
void lookup(this const Self& self,
SymbolHash symbol,
Expand Down
2 changes: 2 additions & 0 deletions src/server/compiler/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,8 @@ kota::task<> Compiler::run_compile(std::uint32_t pid, std::shared_ptr<Session::P
OpenFileIndex ofi;
ofi.file_index = std::move(tu_index.main_file_index);
ofi.symbols = std::move(tu_index.symbols);
ofi.include_paths = std::move(tu_index.graph.paths);
ofi.include_locations = std::move(tu_index.graph.locations);
ofi.content = sess->text;
ofi.mapper.emplace(ofi.content, lsp::PositionEncoding::UTF16);
sess->file_index = std::move(ofi);
Expand Down
64 changes: 64 additions & 0 deletions src/server/compiler/indexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ namespace clice {

namespace lsp = kota::ipc::lsp;

namespace {

auto zero_range() -> protocol::Range {
auto pos = protocol::Position{.line = 0, .character = 0};
return protocol::Range{.start = pos, .end = pos};
}

auto file_location(llvm::StringRef path) -> std::optional<protocol::Location> {
auto uri = lsp::URI::from_file_path(std::string(path));
if(!uri) {
return std::nullopt;
}
return protocol::Location{.uri = uri->str(), .range = zero_range()};
}

} // namespace

void Indexer::merge(const void* tu_index_data, std::size_t size) {
auto tu_index = index::TUIndex::from(tu_index_data);
if(tu_index.graph.paths.empty()) {
Expand Down Expand Up @@ -243,10 +260,57 @@ Indexer::CursorHit Indexer::resolve_cursor(llvm::StringRef path,
return {};
}

std::optional<protocol::Location>
Indexer::find_include_definition(llvm::StringRef path,
const protocol::Position& position,
Session* session) {
if(session && session->file_index && session->file_index->mapper) {
auto offset = session->file_index->mapper->to_offset(position);
if(!offset) {
return std::nullopt;
}
auto target = session->file_index->find_include_definition(*offset);
if(target && *target < session->file_index->include_paths.size()) {
return file_location(session->file_index->include_paths[*target]);
}
}

const std::string* doc_text = session ? &session->text : nullptr;
if(!doc_text) {
return std::nullopt;
}
lsp::PositionMapper doc_mapper(*doc_text, lsp::PositionEncoding::UTF16);
auto offset = doc_mapper.to_offset(position);
if(!offset) {
return std::nullopt;
}

auto proj_it = workspace.project_index.path_pool.find(path);
if(proj_it == workspace.project_index.path_pool.cache.end()) {
return std::nullopt;
}
auto shard_it = workspace.merged_indices.find(proj_it->second);
if(shard_it == workspace.merged_indices.end()) {
return std::nullopt;
}

auto target = shard_it->second.find_include_definition(*offset);
if(!target || *target >= workspace.project_index.path_pool.paths.size()) {
return std::nullopt;
}
return file_location(workspace.project_index.path_pool.path(*target));
}

std::vector<protocol::Location> Indexer::query_relations(llvm::StringRef path,
const protocol::Position& position,
RelationKind kind,
Session* session) {
if(kind.value() == RelationKind::Definition) {
if(auto include = find_include_definition(path, position, session)) {
return {*include};
}
}

auto hit = resolve_cursor(path, position, session);
if(hit.hash == 0)
return {};
Expand Down
5 changes: 5 additions & 0 deletions src/server/compiler/indexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ class Indexer {
const protocol::Position& position,
Session* session);

/// Resolve an include directive argument at (position), if any.
std::optional<protocol::Location> find_include_definition(llvm::StringRef path,
const protocol::Position& position,
Session* session);

/// Collect relations grouped by target symbol, across all index sources.
void collect_grouped_relations(
index::SymbolHash hash,
Expand Down
27 changes: 27 additions & 0 deletions src/server/workspace/workspace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "support/filesystem.h"
#include "support/logging.h"
#include "syntax/lexer.h"
#include "syntax/scan.h"

#include "kota/codec/json/json.h"
Expand Down Expand Up @@ -36,6 +37,24 @@ const static index::Occurrence* lookup_occurrence(const std::vector<index::Occur
return best;
}

std::optional<std::uint32_t>
lookup_include_definition(llvm::StringRef content,
llvm::ArrayRef<index::IncludeLocation> includes,
std::uint32_t offset) {
auto argument = find_directive_argument_at(content, offset);
if(!argument) {
return std::nullopt;
}

for(auto& include: includes) {
if(include.include == static_cast<std::uint32_t>(-1) &&
include.line == argument->line) {
return include.path_id;
}
}
return std::nullopt;
}

std::optional<std::pair<index::SymbolHash, protocol::Range>>
OpenFileIndex::find_occurrence(std::uint32_t offset) const {
if(!mapper)
Expand All @@ -53,6 +72,10 @@ std::optional<std::pair<index::SymbolHash, protocol::Range>>
};
}

std::optional<std::uint32_t> OpenFileIndex::find_include_definition(std::uint32_t offset) const {
return lookup_include_definition(content, include_locations, offset);
}

std::optional<std::pair<index::SymbolHash, protocol::Range>>
MergedIndexShard::find_occurrence(std::uint32_t offset) const {
auto* m = mapper();
Expand All @@ -73,6 +96,10 @@ std::optional<std::pair<index::SymbolHash, protocol::Range>>
return result;
}

std::optional<std::uint32_t> MergedIndexShard::find_include_definition(std::uint32_t offset) const {
return index.find_include_definition(offset);
}

llvm::SmallVector<std::uint32_t> Workspace::on_file_saved(std::uint32_t path_id) {
llvm::SmallVector<std::uint32_t> dirtied;

Expand Down
9 changes: 9 additions & 0 deletions src/server/workspace/workspace.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "command/command.h"
#include "command/toolchain.h"
Expand Down Expand Up @@ -56,6 +57,8 @@ struct HeaderFileContext {
struct OpenFileIndex {
index::FileIndex file_index;
index::SymbolTable symbols;
std::vector<std::string> include_paths;
std::vector<index::IncludeLocation> include_locations;
std::string content; ///< Buffer text at index time (for position mapping).

/// Cached PositionMapper built from `content`. Avoids re-scanning line
Expand All @@ -67,6 +70,9 @@ struct OpenFileIndex {
std::optional<std::pair<index::SymbolHash, protocol::Range>>
find_occurrence(std::uint32_t offset) const;

/// Find an include definition target path id at byte offset.
std::optional<std::uint32_t> find_include_definition(std::uint32_t offset) const;

/// Iterate relations matching `kind`, calling back with pre-converted ranges.
/// Callback: (const index::Relation&, protocol::Range) -> bool (true = continue).
template <typename Fn>
Expand Down Expand Up @@ -115,6 +121,9 @@ struct MergedIndexShard {
std::optional<std::pair<index::SymbolHash, protocol::Range>>
find_occurrence(std::uint32_t offset) const;

/// Find an include definition target path id at byte offset.
std::optional<std::uint32_t> find_include_definition(std::uint32_t offset) const;

/// Iterate relations matching `kind`, calling back with pre-converted ranges.
/// Callback: (const index::Relation&, protocol::Range) -> bool (true = continue).
template <typename Fn>
Expand Down
29 changes: 29 additions & 0 deletions src/syntax/lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,33 @@ std::optional<LocalSourceRange> find_directive_argument(llvm::StringRef content,
return std::nullopt;
}

std::optional<DirectiveArgument> find_directive_argument_at(llvm::StringRef content,
std::uint32_t offset,
const clang::LangOptions* lang_opts) {
if(offset >= content.size()) {
return std::nullopt;
}

std::uint32_t line_start = 0;
if(offset > 0) {
auto pos = content.rfind('\n', offset - 1);
if(pos != llvm::StringRef::npos) {
line_start = static_cast<std::uint32_t>(pos + 1);
}
}

auto range = find_directive_argument(content, line_start, lang_opts);
if(!range || !range->contains(offset)) {
return std::nullopt;
}

std::uint32_t line = 1;
for(char c: content.take_front(line_start)) {
if(c == '\n') {
++line;
}
}
return DirectiveArgument{*range, line};
}

} // namespace clice
13 changes: 13 additions & 0 deletions src/syntax/lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,17 @@ std::optional<LocalSourceRange>
std::uint32_t offset,
const clang::LangOptions* lang_opts = nullptr);

struct DirectiveArgument {
LocalSourceRange range;
std::uint32_t line = 0;
};

/// Find the directive argument containing `offset`.
/// Returns its source range and 1-based line number, or nullopt if `offset`
/// is not inside a directive argument.
std::optional<DirectiveArgument>
find_directive_argument_at(llvm::StringRef content,
std::uint32_t offset,
const clang::LangOptions* lang_opts = nullptr);

} // namespace clice
11 changes: 11 additions & 0 deletions tests/corpus/definition/include_definition.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#[test.h]
#pragma once
int from_test;

#[macro.h]
int from_macro;

#[main.cpp]
#define HEADER "macro.h"
#$(outside)include "$(quoted)test.h"
#include $(macro)HEADER
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: include_definition_tests.cpp
created_at: 2026-06-18
input_file: definition/include_definition.test
---
- { point: "macro", target: "macro.h", range: "0:0-0:0" }
- { point: "outside" }
- { point: "quoted", target: "test.h", range: "0:0-0:0" }
Loading
Loading