commitCharacters editRange insertTextFormat insertTextMode data

namespace type class enum interface struct typeParameter parameter variable property enumMember event function method macro keyword modifier comment string number regexp operator decorator

declaration definition readonly static deprecated abstract async modification documentation defaultLibrary


#include <iostream>

int add(int a, int b) {
    return a + b;
}

int main() {
    std::cout << "hello world" << std::endl;

    int result = add(1, 2);
    return result;
}


#include "compile/implement.h"
#include "index/usr.h"
#include "semantic/ast_utility.h"

namespace clice {

CompilationKind CompilationUnitRef::kind() {
    return self->kind;
}

CompilationStatus CompilationUnitRef::status() {
    return self->status;
}

auto CompilationUnitRef::file_id(clang::FileEntryRef entry) -> clang::FileID {
    return self->SM().translateFile(entry);
}

auto CompilationUnitRef::file_id(llvm::StringRef file) -> clang::FileID {
    auto entry = self->SM().getFileManager().getFileRef(file);
    if(entry) {
        return file_id(*entry);
    }

    return clang::FileID();
}

auto CompilationUnitRef::decompose_location(clang::SourceLocation location)
    -> std::pair<clang::FileID, std::uint32_t> {
    assert(location.isFileID() && "Decompose macro location is meaningless!");
    return self->SM().getDecomposedLoc(location);
}

auto CompilationUnitRef::decompose_range(clang::SourceRange range)
    -> std::pair<clang::FileID, LocalSourceRange> {
    auto [begin, end] = range;
    assert(begin.isValid() && end.isValid() && "Invalid source range");
    assert(begin.isFileID() && end.isValid() && "Input source range should be a file range");

    if(begin == end) {
        auto [fid, offset] = decompose_location(begin);
        return {
            fid,
            {offset, offset + token_length(end)}
        };
    } else {
        auto [begin_fid, begin_offset] = decompose_location(begin);
        auto [end_fid, end_offset] = decompose_location(end);

        if(begin_fid == end_fid) {
            end_offset += token_length(end);
        } else {
            auto content = file_content(begin_fid);
            end_offset = content.size();
        }

        return {
            begin_fid,
            {begin_offset, end_offset}
        };
    }
}

auto CompilationUnitRef::decompose_expansion_range(clang::SourceRange range)
    -> std::pair<clang::FileID, LocalSourceRange> {
    auto [begin, end] = range;
    if(begin == end) {
        return decompose_range(expansion_location(begin));
    } else {
        return decompose_range(
            clang::SourceRange(expansion_location(begin), expansion_location(end)));
    }
}

auto CompilationUnitRef::file_id(clang::SourceLocation location) -> clang::FileID {
    return self->SM().getFileID(location);
}

auto CompilationUnitRef::file_offset(clang::SourceLocation location) -> std::uint32_t {
    return self->SM().getFileOffset(location);
}

auto CompilationUnitRef::file_path(clang::FileID fid) -> llvm::StringRef {
    assert(fid.isValid() && "Invalid fid");
    if(auto it = self->path_cache.find(fid); it != self->path_cache.end()) {
        return it->second;
    }

    auto entry = self->SM().getFileEntryRefForID(fid);
    if(!entry) {
        return {};
    }

    llvm::SmallString<128> path;

    /// Try to get the real path of the file.
    auto name = entry->getName();
    if(auto error = llvm::sys::fs::real_path(name, path)) {
        /// If failed, use the virtual path.
        path = name;
    }
    assert(!path.empty() && "Invalid file path");

    /// Allocate the path in the storage.
    auto size = path.size();
    auto data = self->path_storage.Allocate<char>(size + 1);
    memcpy(data, path.data(), size);
    data[size] = '\0';

    auto [it, inserted] = self->path_cache.try_emplace(fid, llvm::StringRef(data, size));
    assert(inserted && "File path already exists");
    return it->second;
}

auto CompilationUnitRef::file_content(clang::FileID fid) -> llvm::StringRef {
    return self->SM().getBufferData(fid);
}

auto CompilationUnitRef::interested_file() -> clang::FileID {
    return self->SM().getMainFileID();
}

auto CompilationUnitRef::interested_content() -> llvm::StringRef {
    return file_content(interested_file());
}

bool CompilationUnitRef::is_builtin_file(clang::FileID fid) {
    // No FileEntryRef => built-in/command line/scratch.
    if(!self->SM().getFileEntryRefForID(fid)) {
        if(auto buffer = self->SM().getBufferOrNone(fid)) {
            auto name = buffer->getBufferIdentifier();
            return name == "<built-in>" || name == "<command line>" || name == "<scratch space>";
        }
    }

    return false;
}

auto CompilationUnitRef::start_location(clang::FileID fid) -> clang::SourceLocation {
    return self->SM().getLocForStartOfFile(fid);
}

auto CompilationUnitRef::end_location(clang::FileID fid) -> clang::SourceLocation {
    return self->SM().getLocForEndOfFile(fid);
}

auto CompilationUnitRef::spelling_location(clang::SourceLocation loc) -> clang::SourceLocation {
    return self->SM().getSpellingLoc(loc);
}

auto CompilationUnitRef::expansion_location(clang::SourceLocation location)
    -> clang::SourceLocation {
    return self->SM().getExpansionLoc(location);
}

auto CompilationUnitRef::file_location(clang::SourceLocation location) -> clang::SourceLocation {
    return self->SM().getFileLoc(location);
}

auto CompilationUnitRef::include_location(clang::FileID fid) -> clang::SourceLocation {
    return self->SM().getIncludeLoc(fid);
}

auto CompilationUnitRef::presumed_location(clang::SourceLocation location) -> clang::PresumedLoc {
    return self->SM().getPresumedLoc(location, false);
}

auto CompilationUnitRef::create_location(clang::FileID fid, std::uint32_t offset)
    -> clang::SourceLocation {
    return self->SM().getComposedLoc(fid, offset);
}

auto CompilationUnitRef::spelled_tokens(clang::FileID fid) -> TokenRange {
    return self->buffer->spelledTokens(fid);
}

auto CompilationUnitRef::spelled_tokens(clang::SourceRange range) -> TokenRange {
    auto tokens = self->buffer->spelledForExpanded(self->buffer->expandedTokens(range));
    if(!tokens) {
        return {};
    }

    return *tokens;
}

auto CompilationUnitRef::spelled_tokens_touch(clang::SourceLocation location) -> TokenRange {
    return clang::syntax::spelledTokensTouching(location, *self->buffer);
}

auto CompilationUnitRef::expanded_tokens() -> TokenRange {
    return self->buffer->expandedTokens();
}

auto CompilationUnitRef::expanded_tokens(clang::SourceRange range) -> TokenRange {
    return self->buffer->expandedTokens(range);
}

auto CompilationUnitRef::expansions_overlapping(TokenRange spelled_tokens)
    -> std::vector<clang::syntax::TokenBuffer::Expansion> {
    return self->buffer->expansionsOverlapping(spelled_tokens);
}

auto CompilationUnitRef::token_length(clang::SourceLocation location) -> std::uint32_t {
    return clang::Lexer::MeasureTokenLength(location, self->SM(), self->instance->getLangOpts());
}

auto CompilationUnitRef::token_spelling(clang::SourceLocation location) -> llvm::StringRef {
    return llvm::StringRef(self->SM().getCharacterData(location), token_length(location));
}

auto CompilationUnitRef::module_name() -> llvm::StringRef {
    return self->instance->getPreprocessor().getNamedModuleName();
}

bool CompilationUnitRef::is_module_interface_unit() {
    return self->instance->getPreprocessor().isInNamedInterfaceUnit();
}

auto CompilationUnitRef::diagnostics() -> std::vector<Diagnostic>& {
    return self->diagnostics;
}

auto CompilationUnitRef::top_level_decls() -> llvm::ArrayRef<clang::Decl*> {
    return self->top_level_decls;
}

std::chrono::milliseconds CompilationUnitRef::build_at() {
    return self->build_at;
}

std::chrono::milliseconds CompilationUnitRef::build_duration() {
    return self->build_duration;
}

clang::LangOptions& CompilationUnitRef::lang_options() {
    return self->instance->getLangOpts();
}

std::vector<std::string> CompilationUnitRef::deps() {
    llvm::StringSet<> deps;

    /// FIXME: consider `#embed` and `__has_embed`.

    for(auto& [fid, directive]: directives()) {
        for(auto& include: directive.includes) {
            if(!include.skipped) {
                auto path = file_path(include.fid);
                if(!path.empty()) {
                    deps.try_emplace(path);
                }
            }
        }

        for(auto& has_include: directive.has_includes) {
            if(has_include.fid.isValid()) {
                auto path = file_path(has_include.fid);
                if(!path.empty()) {
                    deps.try_emplace(path);
                }
            }
        }
    }

    std::vector<std::string> result;

    for(auto& deps: deps) {
        result.emplace_back(deps.getKey().str());
    }

    return result;
}

index::SymbolID CompilationUnitRef::getSymbolID(const clang::NamedDecl* decl) {
    uint64_t hash;
    auto iter = self->symbol_hash_cache.find(decl);
    if(iter != self->symbol_hash_cache.end()) {
        hash = iter->second;
    } else {
        llvm::SmallString<128> usr;
        index::generateUSRForDecl(decl, usr);
        hash = llvm::xxh3_64bits(usr);
        self->symbol_hash_cache.try_emplace(decl, hash);
    }
    return index::SymbolID{hash, ast::name_of(decl)};
}

index::SymbolID CompilationUnitRef::getSymbolID(const clang::MacroInfo* macro) {
    std::uint64_t hash;
    auto name = token_spelling(macro->getDefinitionLoc());
    auto iter = self->symbol_hash_cache.find(macro);
    if(iter != self->symbol_hash_cache.end()) {
        hash = iter->second;
    } else {
        llvm::SmallString<128> usr;
        index::generateUSRForMacro(name, macro->getDefinitionLoc(), self->SM(), usr);
        hash = llvm::xxh3_64bits(usr);
        self->symbol_hash_cache.try_emplace(macro, hash);
    }
    return index::SymbolID{hash, name.str()};
}

const llvm::DenseSet<clang::FileID>& CompilationUnitRef::files() {
    if(self->all_files.empty()) {
        /// FIXME: handle preamble and embed file id.
        for(auto& [fid, directive]: directives()) {
            for(auto& include: directive.includes) {
                if(!include.skipped && include.fid.isValid()) {
                    self->all_files.insert(include.fid);
                }
            }
        }
        self->all_files.insert(self->SM().getMainFileID());
    }
    return self->all_files;
}

clang::TranslationUnitDecl* CompilationUnitRef::tu() {
    return self->instance->getASTContext().getTranslationUnitDecl();
}

llvm::DenseMap<clang::FileID, Directive>& CompilationUnitRef::directives() {
    return self->directives;
}

TemplateResolver& CompilationUnitRef::resolver() {
    assert(self->resolver && "Template resolver is not available");
    return *self->resolver;
}

clang::ASTContext& CompilationUnitRef::context() {
    return self->instance->getASTContext();
}

clang::syntax::TokenBuffer& CompilationUnitRef::token_buffer() {
    return *self->buffer;
}

CompilationUnit::~CompilationUnit() {
    delete self;
}

}  // namespace clice


#include "server/config.h"

#include <algorithm>
#include <thread>

#include "eventide/serde/toml.h"
#include "support/filesystem.h"
#include "support/logging.h"

namespace clice {

/// Replace all occurrences of ${workspace} with the workspace root.
static void substitute_workspace(std::string& value, const std::string& workspace_root) {
    constexpr std::string_view placeholder = "${workspace}";
    std::string::size_type pos = 0;
    while((pos = value.find(placeholder, pos)) != std::string::npos) {
        value.replace(pos, placeholder.size(), workspace_root);
        pos += workspace_root.size();
    }
}

void CliceConfig::apply_defaults(const std::string& workspace_root) {
    auto cpu_count = std::thread::hardware_concurrency();
    if(cpu_count == 0)
        cpu_count = 4;

    if(stateful_worker_count == 0) {
        stateful_worker_count = 2;
    }
    if(stateless_worker_count == 0) {
        stateless_worker_count = 3;
    }
    if(worker_memory_limit == 0) {
        worker_memory_limit = 4ULL * 1024 * 1024 * 1024;  // 4GB default
    }
    if(cache_dir.empty() && !workspace_root.empty()) {
        cache_dir = path::join(workspace_root, ".clice");
    }

    if(index_dir.empty() && !cache_dir.empty()) {
        index_dir = path::join(cache_dir, "index");
    }

    if(logging_dir.empty() && !cache_dir.empty()) {
        logging_dir = path::join(cache_dir, "logs");
    }

    // Apply variable substitution to string fields
    substitute_workspace(compile_commands_path, workspace_root);
    substitute_workspace(cache_dir, workspace_root);
    substitute_workspace(index_dir, workspace_root);
    substitute_workspace(logging_dir, workspace_root);
}

std::optional<CliceConfig> CliceConfig::load(const std::string& path,
                                             const std::string& workspace_root) {
    auto content = fs::read(path);
    if(!content) {
        return std::nullopt;
    }

    auto result = eventide::serde::toml::parse<CliceConfig>(*content);
    if(!result) {
        LOG_WARN("Failed to parse config file {}", path);
        return std::nullopt;
    }

    auto config = std::move(*result);
    config.apply_defaults(workspace_root);

    LOG_INFO("Loaded config from {}", path);
    return config;
}

CliceConfig CliceConfig::load_from_workspace(const std::string& workspace_root) {
    if(!workspace_root.empty()) {
        // Try standard config file locations
        for(auto* name: {"clice.toml", ".clice/config.toml"}) {
            auto config_path = path::join(workspace_root, name);
            if(llvm::sys::fs::exists(config_path)) {
                auto config = load(config_path, workspace_root);
                if(config)
                    return std::move(*config);
            }
        }
    }

    // No config file found; use defaults
    CliceConfig config;
    config.apply_defaults(workspace_root);
    LOG_INFO(
        "No clice.toml found, using default configuration " "(stateful={}, stateless={}, memory_limit={}MB)",
        config.stateful_worker_count,
        config.stateless_worker_count,
        config.worker_memory_limit / (1024 * 1024));
    return config;
}

}  // namespace clice