@@ -574,20 +574,37 @@ std::optional<HeaderFileContext>
574574 return std::nullopt ;
575575 }
576576
577- // Pick the first available host that has a CDB entry .
577+ // If there's an active context override, prefer that host .
578578 std::uint32_t host_path_id = 0 ;
579579 std::vector<std::uint32_t > chain;
580- for (auto candidate: hosts) {
581- auto candidate_path = path_pool.resolve (candidate);
582- auto results = cdb.lookup (candidate_path, {.suppress_logging = true });
583- if (results.empty ())
584- continue ;
585- auto c = dependency_graph.find_include_chain (candidate, header_path_id);
586- if (c.empty ())
587- continue ;
588- host_path_id = candidate;
589- chain = std::move (c);
590- break ;
580+ auto active_it = active_contexts.find (header_path_id);
581+ if (active_it != active_contexts.end ()) {
582+ auto preferred = active_it->second ;
583+ auto preferred_path = path_pool.resolve (preferred);
584+ auto results = cdb.lookup (preferred_path, {.suppress_logging = true });
585+ if (!results.empty ()) {
586+ auto c = dependency_graph.find_include_chain (preferred, header_path_id);
587+ if (!c.empty ()) {
588+ host_path_id = preferred;
589+ chain = std::move (c);
590+ }
591+ }
592+ }
593+
594+ // Fall back to the first available host that has a CDB entry.
595+ if (chain.empty ()) {
596+ for (auto candidate: hosts) {
597+ auto candidate_path = path_pool.resolve (candidate);
598+ auto results = cdb.lookup (candidate_path, {.suppress_logging = true });
599+ if (results.empty ())
600+ continue ;
601+ auto c = dependency_graph.find_include_chain (candidate, header_path_id);
602+ if (c.empty ())
603+ continue ;
604+ host_path_id = candidate;
605+ chain = std::move (c);
606+ break ;
607+ }
591608 }
592609
593610 if (chain.empty ()) {
@@ -687,26 +704,38 @@ std::optional<HeaderFileContext>
687704bool MasterServer::fill_compile_args (llvm::StringRef path,
688705 std::string& directory,
689706 std::vector<std::string>& arguments) {
690- auto results = cdb.lookup (path, {.query_toolchain = true });
691- if (!results.empty ()) {
692- auto & ctx = results.front ();
693- directory = ctx.directory .str ();
694- arguments.clear ();
695- for (auto * arg: ctx.arguments ) {
696- arguments.emplace_back (arg);
707+ if (cdb.has_entry (path)) {
708+ auto results = cdb.lookup (path, {.query_toolchain = true });
709+ if (!results.empty ()) {
710+ auto & ctx = results.front ();
711+ directory = ctx.directory .str ();
712+ arguments.clear ();
713+ for (auto * arg: ctx.arguments ) {
714+ arguments.emplace_back (arg);
715+ }
716+ return true ;
697717 }
698- return true ;
699718 }
700719
701720 // No direct CDB entry — try to compile the header in context of a host source.
702721 auto path_id = path_pool.intern (path);
703722
704723 // Use cached context if available; otherwise resolve.
724+ // If an active context override exists, invalidate cache if it points to
725+ // a different host so we re-resolve with the correct one.
705726 const HeaderFileContext* ctx_ptr = nullptr ;
706727 auto ctx_it = header_file_contexts.find (path_id);
728+ auto active_it = active_contexts.find (path_id);
707729 if (ctx_it != header_file_contexts.end ()) {
708- ctx_ptr = &ctx_it->second ;
709- } else {
730+ // Check if the cached context matches the active context override.
731+ if (active_it != active_contexts.end () && ctx_it->second .host_path_id != active_it->second ) {
732+ header_file_contexts.erase (ctx_it);
733+ ctx_it = header_file_contexts.end ();
734+ } else {
735+ ctx_ptr = &ctx_it->second ;
736+ }
737+ }
738+ if (!ctx_ptr) {
710739 auto resolved = resolve_header_context (path_id);
711740 if (!resolved) {
712741 LOG_WARN (" No CDB entry and no header context for {}" , path);
@@ -727,9 +756,8 @@ bool MasterServer::fill_compile_args(llvm::StringRef path,
727756 directory = host_ctx.directory .str ();
728757 arguments.clear ();
729758
730- // Copy host arguments, skipping the last element if it looks like a source
731- // file path (i.e. not a flag). The header path is used as the main file by
732- // the caller via CompileParams::path.
759+ // Copy host arguments, replacing the source file path (last non-flag arg)
760+ // with the header file path, so the compiler processes the header in context.
733761 auto num_args = host_ctx.arguments .size ();
734762 std::size_t copy_count = num_args;
735763 if (copy_count > 0 ) {
@@ -740,11 +768,19 @@ bool MasterServer::fill_compile_args(llvm::StringRef path,
740768 for (std::size_t i = 0 ; i < copy_count; ++i) {
741769 arguments.emplace_back (host_ctx.arguments [i]);
742770 }
771+ // Append the header file path as the source file.
772+ arguments.emplace_back (path);
743773
744- // Inject the preamble before the header so the compiler sees all context
745- // code that normally precedes this header in the host translation unit.
746- arguments.insert (arguments.begin () + 1 , ctx_ptr->preamble_path );
747- arguments.insert (arguments.begin () + 1 , " -include" );
774+ // Inject the preamble so the compiler sees all context code that normally
775+ // precedes this header in the host translation unit.
776+ // For cc1 args (["clang++", "-cc1", ...]), insert after "-cc1" at position 2.
777+ // For driver args, insert after the driver binary at position 1.
778+ std::size_t inject_pos = 1 ;
779+ if (arguments.size () >= 2 && arguments[1 ] == " -cc1" ) {
780+ inject_pos = 2 ;
781+ }
782+ arguments.insert (arguments.begin () + inject_pos, ctx_ptr->preamble_path );
783+ arguments.insert (arguments.begin () + inject_pos, " -include" );
748784
749785 LOG_INFO (" fill_compile_args: using header context for {} (host={}, preamble={})" ,
750786 path,
@@ -932,7 +968,9 @@ et::task<bool> MasterServer::ensure_deps(std::uint32_t path_id,
932968et::task<bool > MasterServer::ensure_compiled (std::uint32_t path_id) {
933969 auto it = documents.find (path_id);
934970 if (it == documents.end ()) {
935- LOG_DEBUG (" ensure_compiled: doc not found for path_id={}" , path_id);
971+ LOG_WARN (" ensure_compiled: doc not found for path_id={} path={}" ,
972+ path_id,
973+ path_pool.resolve (path_id));
936974 co_return false ;
937975 }
938976
@@ -2705,6 +2743,107 @@ void MasterServer::register_handlers() {
27052743 co_return serde_raw{" null" };
27062744 co_return to_raw (results);
27072745 });
2746+
2747+ // === clice/ Extension Commands ===
2748+
2749+ // --- clice/queryContext ---
2750+ peer.on_request (
2751+ " clice/queryContext" ,
2752+ [this ](RequestContext& ctx, const ext::QueryContextParams& params) -> RawResult {
2753+ auto path = uri_to_path (params.uri );
2754+ auto path_id = path_pool.intern (path);
2755+ int offset_val = params.offset .value_or (0 );
2756+ constexpr int page_size = 10 ;
2757+
2758+ ext::QueryContextResult result;
2759+
2760+ // Find source files that transitively include this file.
2761+ // For source files (roots in the include graph) this returns empty.
2762+ auto hosts = dependency_graph.find_host_sources (path_id);
2763+ std::vector<ext::ContextItem> all_items;
2764+ for (auto host_id: hosts) {
2765+ auto host_path = path_pool.resolve (host_id);
2766+ auto host_cdb = cdb.lookup (host_path, {.suppress_logging = true });
2767+ if (host_cdb.empty ())
2768+ continue ;
2769+ auto host_uri_opt = lsp::URI::from_file_path (std::string (host_path));
2770+ if (!host_uri_opt)
2771+ continue ;
2772+ ext::ContextItem item;
2773+ item.label = llvm::sys::path::filename (host_path).str ();
2774+ item.description = std::string (host_path);
2775+ item.uri = host_uri_opt->str ();
2776+ all_items.push_back (std::move (item));
2777+ }
2778+
2779+ result.total = static_cast <int >(all_items.size ());
2780+ int end = std::min (offset_val + page_size, static_cast <int >(all_items.size ()));
2781+ for (int i = offset_val; i < end; ++i) {
2782+ result.contexts .push_back (std::move (all_items[i]));
2783+ }
2784+ co_return to_raw (result);
2785+ });
2786+
2787+ // --- clice/currentContext ---
2788+ peer.on_request (
2789+ " clice/currentContext" ,
2790+ [this ](RequestContext& ctx, const ext::CurrentContextParams& params) -> RawResult {
2791+ auto path = uri_to_path (params.uri );
2792+ auto path_id = path_pool.intern (path);
2793+
2794+ ext::CurrentContextResult result;
2795+
2796+ auto it = active_contexts.find (path_id);
2797+ if (it != active_contexts.end ()) {
2798+ auto ctx_path = path_pool.resolve (it->second );
2799+ auto ctx_uri_opt = lsp::URI::from_file_path (std::string (ctx_path));
2800+ if (ctx_uri_opt) {
2801+ ext::ContextItem item;
2802+ item.label = llvm::sys::path::filename (ctx_path).str ();
2803+ item.description = std::string (ctx_path);
2804+ item.uri = ctx_uri_opt->str ();
2805+ result.context = std::move (item);
2806+ }
2807+ }
2808+ co_return to_raw (result);
2809+ });
2810+
2811+ // --- clice/switchContext ---
2812+ peer.on_request (
2813+ " clice/switchContext" ,
2814+ [this ](RequestContext& ctx, const ext::SwitchContextParams& params) -> RawResult {
2815+ auto path = uri_to_path (params.uri );
2816+ auto path_id = path_pool.intern (path);
2817+ auto context_path = uri_to_path (params.context_uri );
2818+ auto context_path_id = path_pool.intern (context_path);
2819+
2820+ ext::SwitchContextResult result;
2821+
2822+ // Verify the context file has a CDB entry.
2823+ auto context_cdb = cdb.lookup (context_path, {.suppress_logging = true });
2824+ if (context_cdb.empty ()) {
2825+ result.success = false ;
2826+ co_return to_raw (result);
2827+ }
2828+
2829+ // Set active context and invalidate cached header context so
2830+ // resolve_header_context will pick the new host on next compile.
2831+ active_contexts[path_id] = context_path_id;
2832+ header_file_contexts.erase (path_id);
2833+
2834+ // Also invalidate the PCH for the old context (if any) so it
2835+ // gets rebuilt with the new host's preamble.
2836+ pch_states.erase (path_id);
2837+
2838+ // Mark the document as dirty so it gets recompiled.
2839+ auto doc_it = documents.find (path_id);
2840+ if (doc_it != documents.end ()) {
2841+ doc_it->second .ast_dirty = true ;
2842+ }
2843+
2844+ result.success = true ;
2845+ co_return to_raw (result);
2846+ });
27082847}
27092848
27102849} // namespace clice
0 commit comments