SemanticCodeSearchQuery.java

/*******************************************************************************
 * Copyright (c) 2026 Carsten Hammer.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Carsten Hammer
 *******************************************************************************/
package org.sandbox.jdt.internal.ui.search;

import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.search.ui.ISearchQuery;
import org.eclipse.search.ui.ISearchResult;
import org.sandbox.jdt.internal.ui.search.gitindex.EmbeddedSearchService;
import org.sandbox.jdt.internal.ui.search.gitindex.SearchHit;
import org.sandbox.jdt.internal.ui.search.gitindex.SemanticSearchClient;

/**
 * Search query that executes a semantic or hybrid code search.
 *
 * <p>
 * Delegates to {@link SemanticSearchClient#semanticSearch(String, String, int)}
 * or {@link SemanticSearchClient#hybridSearch(String, String, int)} via
 * {@link EmbeddedSearchService}.
 * </p>
 */
public class SemanticCodeSearchQuery implements ISearchQuery {

	/** Search mode: semantic vector search, hybrid (fused), or similar code. */
	public enum SearchMode {
		/** Pure semantic (vector) search. */
		SEMANTIC,
		/** Hybrid: full-text + semantic, fused via RRF. */
		HYBRID,
		/** Find code similar to a given blob. */
		SIMILAR
	}

	private final String queryText;
	private final String repoName;
	private final int maxResults;
	private final SearchMode mode;
	private final SemanticCodeSearchResult result;

	/**
	 * Creates a new semantic code search query.
	 *
	 * @param queryText
	 *            the natural language query text
	 * @param repoName
	 *            the repository name to search (required — the backend rejects
	 *            empty values)
	 * @param maxResults
	 *            maximum number of results to return
	 * @param mode
	 *            the search mode
	 */
	public SemanticCodeSearchQuery(String queryText, String repoName,
			int maxResults, SearchMode mode) {
		this.queryText= queryText;
		this.repoName= repoName;
		this.maxResults= maxResults;
		this.mode= mode;
		this.result= new SemanticCodeSearchResult(this);
	}

	@Override
	public IStatus run(IProgressMonitor monitor) {
		monitor.beginTask(getLabel(), IProgressMonitor.UNKNOWN);
		try {
			if (monitor.isCanceled()) {
				return Status.CANCEL_STATUS;
			}
			SemanticSearchClient client= EmbeddedSearchService.getInstance().getSearchClient();
			if (client == null) {
				return Status.error("Semantic search service is not available. " //$NON-NLS-1$
						+ "Please ensure the search backend is running."); //$NON-NLS-1$
			}
			List<SearchHit> hits;
			switch (mode) {
				case HYBRID:
					hits= client.hybridSearch(repoName, queryText, maxResults);
					break;
				case SIMILAR:
					// queryText is treated as a blob object ID for similar code search
					hits= client.findSimilarCode(repoName, queryText, maxResults);
					break;
				case SEMANTIC:
				default:
					hits= client.semanticSearch(repoName, queryText, maxResults);
					break;
			}
			if (monitor.isCanceled()) {
				return Status.CANCEL_STATUS;
			}
			result.setMatches(hits);
			return Status.OK_STATUS;
		} finally {
			monitor.done();
		}
	}

	@Override
	public String getLabel() {
		return "Semantic Search: '" + queryText + "'"; //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Override
	public boolean canRerun() {
		return true;
	}

	@Override
	public boolean canRunInBackground() {
		return true;
	}

	@Override
	public ISearchResult getSearchResult() {
		return result;
	}

	/**
	 * Returns the query text.
	 *
	 * @return the natural language query text
	 */
	public String getQueryText() {
		return queryText;
	}
}