diff --git a/src/completion-provider/bazel_repository_completion_provider.ts b/src/completion-provider/bazel_repository_completion_provider.ts new file mode 100644 index 00000000..7dfda1d3 --- /dev/null +++ b/src/completion-provider/bazel_repository_completion_provider.ts @@ -0,0 +1,87 @@ +// Copyright 2023 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as vscode from "vscode"; +import { queryQuickPickTargets } from "../bazel"; + +function isCompletingInsideRepositoryLabel( + document: vscode.TextDocument, + position: vscode.Position, +) { + const linePrefix = document + .lineAt(position) + .text.substring(0, position.character); + const startOfRepo = linePrefix.lastIndexOf("@"); + const endOfRepo = linePrefix.lastIndexOf("//"); + return startOfRepo !== -1 && (endOfRepo === -1 || endOfRepo < startOfRepo); +} + +function getTargetName(label: string) { + const colonIndex = label.lastIndexOf(":"); + if (colonIndex === -1) { + return undefined; + } + return label.substring(colonIndex + 1); +} + +export class BazelRepositoryCompletionItemProvider + implements vscode.CompletionItemProvider { + private repositories?: Promise; + + /** + * Returns completion items matching the given prefix. + */ + public async provideCompletionItems( + document: vscode.TextDocument, + position: vscode.Position, + ) { + if (!isCompletingInsideRepositoryLabel(document, position)) { + return []; + } + + const repos = await this.getRepos(); + const completionItems = repos.map( + (repo) => + new vscode.CompletionItem(repo, vscode.CompletionItemKind.Folder), + ); + return completionItems; + } + + /** + * Runs a bazel query command to acquire all the repositories in the + * workspace. + */ + public async refresh(): Promise { + await this.queryAndCacheRepos(); + } + + private async getRepos(): Promise { + if (this.repositories) { + return await this.repositories; + } + return await this.queryAndCacheRepos(); + } + + private async queryAndCacheRepos(): Promise { + const queryRepos = async () => { + const targets = await queryQuickPickTargets( + "kind('.* rule', //external:*)", + ); + return targets.map((target) => getTargetName(target.label)); + }; + const deferred = queryRepos(); + this.repositories = deferred; + return await deferred; + } +} diff --git a/src/completion-provider/bazel_completion_provider.ts b/src/completion-provider/bazel_target_completion_provider.ts similarity index 99% rename from src/completion-provider/bazel_completion_provider.ts rename to src/completion-provider/bazel_target_completion_provider.ts index 1021a3a4..75fb868f 100644 --- a/src/completion-provider/bazel_completion_provider.ts +++ b/src/completion-provider/bazel_target_completion_provider.ts @@ -100,7 +100,7 @@ function getRepositoryName(target: string): string { return endOfRepo <= 0 ? "" : target.substring(1, endOfRepo); } -export class BazelCompletionItemProvider +export class BazelTargetCompletionItemProvider implements vscode.CompletionItemProvider { private readonly targetsInRepo = new Map>(); diff --git a/src/completion-provider/index.ts b/src/completion-provider/index.ts index c5c213a2..792b30e0 100644 --- a/src/completion-provider/index.ts +++ b/src/completion-provider/index.ts @@ -12,4 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -export * from "./bazel_completion_provider"; +export * from "./bazel_repository_completion_provider"; +export * from "./bazel_target_completion_provider"; diff --git a/src/extension/extension.ts b/src/extension/extension.ts index d4a6c566..ca82758d 100644 --- a/src/extension/extension.ts +++ b/src/extension/extension.ts @@ -35,7 +35,10 @@ import { checkBuildifierIsAvailable, } from "../buildifier"; import { BazelBuildCodeLensProvider } from "../codelens"; -import { BazelCompletionItemProvider } from "../completion-provider"; +import { + BazelRepositoryCompletionItemProvider, + BazelTargetCompletionItemProvider, +} from "../completion-provider"; import { BazelGotoDefinitionProvider } from "../definition/bazel_goto_definition_provider"; import { BazelTargetSymbolProvider } from "../symbols"; import { BazelWorkspaceTreeProvider } from "../workspace-tree"; @@ -51,15 +54,24 @@ export function activate(context: vscode.ExtensionContext) { const workspaceTreeProvider = new BazelWorkspaceTreeProvider(context); const codeLensProvider = new BazelBuildCodeLensProvider(context); const buildifierDiagnostics = new BuildifierDiagnosticsManager(); - const completionItemProvider = new BazelCompletionItemProvider(); + const repositoryCompletionItemProvider = + new BazelRepositoryCompletionItemProvider(); + const targetCompletionItemProvider = new BazelTargetCompletionItemProvider(); // tslint:disable-next-line:no-floating-promises - completionItemProvider.refresh(); + repositoryCompletionItemProvider.refresh(); + // tslint:disable-next-line:no-floating-promises + targetCompletionItemProvider.refresh(); context.subscriptions.push( vscode.languages.registerCompletionItemProvider( [{ pattern: "**/BUILD" }, { pattern: "**/BUILD.bazel" }], - completionItemProvider, + repositoryCompletionItemProvider, + "@", + ), + vscode.languages.registerCompletionItemProvider( + [{ pattern: "**/BUILD" }, { pattern: "**/BUILD.bazel" }], + targetCompletionItemProvider, "/", ":", ), @@ -88,7 +100,9 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("bazel.clean", bazelClean), vscode.commands.registerCommand("bazel.refreshBazelBuildTargets", () => { // tslint:disable-next-line:no-floating-promises - completionItemProvider.refresh(); + repositoryCompletionItemProvider.refresh(); + // tslint:disable-next-line:no-floating-promises + targetCompletionItemProvider.refresh(); workspaceTreeProvider.refresh(); }), vscode.commands.registerCommand(