Skip to content

Commit

Permalink
Enable External Workspace completions and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mtoader committed Sep 4, 2024
1 parent e622604 commit f8b4295
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public BuildLookupElement(String baseName, QuoteType quoteWrapping) {
this.wrapWithQuotes = quoteWrapping != QuoteType.NoQuotes;
}

private static boolean insertClosingQuotes() {
protected static boolean insertClosingQuotes() {
return CodeInsightSettings.getInstance().AUTOINSERT_PAIR_QUOTE;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.google.idea.blaze.base.lang.buildfile.completion;

import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
import com.google.idea.blaze.base.model.primitives.ExternalWorkspace;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.openapi.editor.Document;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;

public class ExternalWorkspaceLookupElement extends BuildLookupElement {
private final ExternalWorkspace workspace;

public ExternalWorkspaceLookupElement(ExternalWorkspace workspace) {
super('@' + workspace.repositoryName(), QuoteType.NoQuotes);
this.workspace = workspace;
}

@Override
public String getLookupString() {
return super.getItemText() + "//";
}

@Override
@Nullable
protected String getTypeText() {
return !workspace.repositoryName().equals(workspace.name()) ? '@' + workspace.name() : null;
}

@Override
@Nullable
protected String getTailText() {
return "//";
}

@Override
public @Nullable Icon getIcon() {
return PlatformIcons.LIBRARY_ICON;
}

@Override
public void handleInsert(InsertionContext context) {
Document document = context.getDocument();

StringLiteral literal = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), StringLiteral.class, false);
if (literal == null) {
super.handleInsert(context);
return;
}

// find an remove trailing package path after insert / replace.
// current element text looks like `@workspace//`. If this is complete inside an existing workspace name the
// result would look like: @workspace//old_workspace_path//. The following bit will remove `old_workspace_path//`
int indexOfPackageStart = literal.getText().lastIndexOf("//");
if (indexOfPackageStart != -1) {
// shift to be a document offset
indexOfPackageStart += literal.getTextOffset() + 2;
if (context.getSelectionEndOffset() < indexOfPackageStart) {
document.deleteString(context.getSelectionEndOffset(), indexOfPackageStart);
context.commitDocument();
}
}

// handle auto insertion of end quote. This will have to be if the completion is triggered inside a `"@<caret` bit
if (insertClosingQuotes()) {
QuoteType quoteType = literal.getQuoteType();
if (quoteType != QuoteType.NoQuotes && !literal.getText().endsWith(quoteType.quoteString)) {
document.insertString(literal.getTextOffset() + literal.getTextLength(), quoteType.quoteString);
context.commitDocument();
}
}

super.handleInsert(context);
}

@Override
public boolean requiresCommittedDocuments() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@
*/
package com.google.idea.blaze.base.lang.buildfile.references;

import com.google.idea.blaze.base.lang.buildfile.completion.BuildLookupElement;
import com.google.idea.blaze.base.lang.buildfile.completion.ExternalWorkspaceLookupElement;
import com.google.idea.blaze.base.lang.buildfile.psi.BuildElement;
import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.model.primitives.WorkspacePath;
import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
import com.google.idea.blaze.base.sync.workspace.WorkspaceHelper;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
Expand All @@ -30,6 +36,8 @@
import com.intellij.psi.PsiReferenceBase;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/** The external workspace component of a label (between '@' and '//') */
Expand All @@ -42,26 +50,45 @@ public ExternalWorkspaceReferenceFragment(LabelReference labelReference) {
@Override
public TextRange getRangeInElement() {
String rawText = myElement.getText();
boolean valid = LabelUtils.getExternalWorkspaceComponent(myElement.getStringContents()) != null;
if (!valid) {
String unquotedText = LabelUtils.trimToDummyIdentifier(myElement.getStringContents());
QuoteType quoteType = myElement.getQuoteType();

String externalWorkspace = LabelUtils.getExternalWorkspaceComponent(unquotedText);
if (!unquotedText.trim().isEmpty() && externalWorkspace == null) {
return TextRange.EMPTY_RANGE;
}

int endIndex = rawText.indexOf("//");
if (endIndex == -1) {
endIndex = rawText.length() - 1;
endIndex = rawText.length() - quoteType.quoteString.length();
} else {
endIndex += 2;
}
return new TextRange(1, endIndex);
}

@Nullable
@Override
public FuncallExpression resolve() {
public BuildElement resolve() {
String name = LabelUtils.getExternalWorkspaceComponent(myElement.getStringContents());
if (name == null) {
return null;
}

BuildFile workspaceFile = resolveProjectWorkspaceFile(myElement.getProject());
return workspaceFile != null ? workspaceFile.findRule(name) : null;
if (workspaceFile != null) {
FuncallExpression expression = workspaceFile.findRule(name);
if (expression != null) {
return expression;
}
};

WorkspaceRoot workspaceRoot = WorkspaceHelper.getExternalWorkspace(myElement.getProject(), name);
if (workspaceRoot != null) {
return BuildReferenceManager.getInstance(myElement.getProject()).findBuildFile(workspaceRoot.directory());
}

return null;
}

@Nullable
Expand All @@ -83,8 +110,16 @@ private static BuildFile resolveProjectWorkspaceFile(Project project) {
}

@Override
public Object[] getVariants() {
return EMPTY_ARRAY;
@Nonnull
public BuildLookupElement[] getVariants() {
BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(myElement.getProject()).getBlazeProjectData();
if (blazeProjectData == null) {
return BuildLookupElement.EMPTY_ARRAY;
}

return blazeProjectData.getExternalWorkspaceData().workspaces.values().stream()
.map(ExternalWorkspaceLookupElement::new)
.toArray(BuildLookupElement[]::new);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,9 @@
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceBase;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;

import javax.annotation.Nullable;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.google.common.annotations.VisibleForTesting;
import com.google.idea.blaze.base.bazel.BuildSystemProvider;
import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
import com.google.idea.blaze.base.model.BlazeProjectData;
import com.google.idea.blaze.base.model.primitives.ExternalWorkspace;
import com.google.idea.blaze.base.model.primitives.Label;
Expand Down Expand Up @@ -68,8 +69,8 @@ private static synchronized BlazeProjectData getBlazeProjectData(Project project
}

@Nullable
public static WorkspaceRoot resolveExternalWorkspace(Project project, String workspaceName) {
return getExternalWorkspaceRootsFile(workspaceName, project);
public static WorkspaceRoot getExternalWorkspace(Project project, String workspaceName) {
return resolveExternalWorkspaceRoot(project, workspaceName, null);
}

/**
Expand All @@ -84,7 +85,7 @@ public static File resolveBlazePackage(Project project, Label label) {
return pathResolver != null ? pathResolver.resolveToFile(label.blazePackage()) : null;
}

WorkspaceRoot root = getExternalWorkspaceRootsFile(label.externalWorkspaceName(), project);
WorkspaceRoot root = resolveExternalWorkspaceRoot(project, label.externalWorkspaceName(), null);
return root != null ? root.fileForPath(label.blazePackage()) : null;
}

Expand Down Expand Up @@ -127,9 +128,7 @@ private static Workspace resolveWorkspace(Project project, File absoluteFile) {
}

BlazeProjectData blazeProjectData = getBlazeProjectData(project);
Path bazelRootPath = Paths.get(
blazeProjectData.getBlazeInfo().getOutputBase().getAbsolutePath(),
"external").normalize();
Path bazelRootPath = getExternalSourceRoot(blazeProjectData);

logger.debug("the bazelRootPath is " + bazelRootPath);
Path path = Paths.get(absoluteFile.getAbsolutePath()).normalize();
Expand Down Expand Up @@ -187,16 +186,21 @@ private static WorkspacePath getPackagePath(
}

@VisibleForTesting
public static File getExternalSourceRoot(BlazeProjectData projectData) {
return new File(projectData.getBlazeInfo().getOutputBase(), "external");
public static Path getExternalSourceRoot(BlazeProjectData projectData) {
return Paths.get(projectData.getBlazeInfo().getOutputBase().getAbsolutePath(), "external").normalize();
}

@Nullable
private static synchronized WorkspaceRoot getExternalWorkspaceRootsFile(String workspaceName,
Project project) {
private static synchronized WorkspaceRoot resolveExternalWorkspaceRoot(
Project project, String workspaceName, @Nullable BuildFile buildFile) {
if (Blaze.getBuildSystemName(project) == BuildSystemName.Blaze) {
return null;
}

if (workspaceName == null || workspaceName.isEmpty()) {
return WorkspaceRoot.fromProjectSafe(project);
}

logger.debug("getExternalWorkspaceRootsFile for " + workspaceName);
Map<String, WorkspaceRoot> workspaceRootCache =
SyncCache.getInstance(project)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.google.idea.blaze.base.lang.buildfile.completion;

import com.google.common.collect.ImmutableList;
import com.google.idea.blaze.base.ExternalWorkspaceFixture;
import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
import com.google.idea.blaze.base.model.ExternalWorkspaceData;
import com.google.idea.blaze.base.model.primitives.ExternalWorkspace;
import com.google.idea.blaze.base.model.primitives.WorkspacePath;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiFile;
import org.junit.Ignore;
import org.junit.Test;

import static com.google.common.truth.Truth.assertThat;

public class ExternalWorkspaceCompletionTest extends BuildFileIntegrationTestCase {
protected ExternalWorkspaceFixture workspaceOne;
protected ExternalWorkspaceFixture workspaceTwoMapped;

@Override
protected ExternalWorkspaceData mockExternalWorkspaceData() {
workspaceOne = new ExternalWorkspaceFixture(
ExternalWorkspace.create("workspace_one", "workspace_one"), fileSystem);

workspaceTwoMapped = new ExternalWorkspaceFixture(
ExternalWorkspace.create("workspace_two", "com_workspace_two"), fileSystem);

return ExternalWorkspaceData.create(
ImmutableList.of(workspaceOne.w, workspaceTwoMapped.w));
}

@Test
public void testEmptyLabelCompletion() throws Throwable {
PsiFile file = testFixture.configureByText("BUILD", "'<caret>'");

String[] strings = editorTest.getCompletionItemsAsStrings();
assertThat(strings).hasLength(2);
assertThat(strings)
.asList()
.containsExactly("@com_workspace_two//", "@workspace_one//");
}

@Test
public void testCompleteWillIncludeSlashes () {
PsiFile file = testFixture.configureByText("BUILD", "'@com<caret>'");
assertThat(editorTest.completeIfUnique()).isTrue();

assertFileContents(file, "'@com_workspace_two//'");
}

@Test
public void testCompleteWillFixUpRemainingSlashed () {
PsiFile file = testFixture.configureByText("BUILD", "'@com<caret>//'");
assertThat(editorTest.completeIfUnique()).isTrue();

assertFileContents(file, "'@com_workspace_two//'");
}

@Test
public void testCompleteWillAlwaysReplaceWorkspace() {
PsiFile file = testFixture.configureByText("BUILD", "'@com<caret>xxxx//'");
assertThat(editorTest.completeIfUnique()).isTrue();

assertFileContents(file, "'@com_workspace_two//'");
}

@Test
public void testCompleteWillRespectAutoQuoting() {
boolean old = CodeInsightSettings.getInstance().AUTOINSERT_PAIR_QUOTE;
try {
CodeInsightSettings.getInstance().AUTOINSERT_PAIR_QUOTE = false;
PsiFile file = testFixture.configureByText("BUILD", "'@com<caret>");

assertThat(editorTest.completeIfUnique()).isTrue();
assertFileContents(file, "'@com_workspace_two//");

CodeInsightSettings.getInstance().AUTOINSERT_PAIR_QUOTE = true;
file = testFixture.configureByText("BUILD", "'@com<caret>");

assertThat(editorTest.completeIfUnique()).isTrue();
assertFileContents(file, "'@com_workspace_two//'");

} finally {
CodeInsightSettings.getInstance().AUTOINSERT_PAIR_QUOTE = old;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,6 @@ protected File getExternalSourceRoot() {
BlazeProjectData blazeProjectData = BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData();
assertThat(blazeProjectData).isNotNull();

return WorkspaceHelper.getExternalSourceRoot(blazeProjectData);
return WorkspaceHelper.getExternalSourceRoot(blazeProjectData).toFile();
}
}

0 comments on commit f8b4295

Please sign in to comment.