/* * Copyright 2015-present Facebook, Inc. * * 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. */ package com.facebook.buck.intellij.ideabuck.format; import com.facebook.buck.intellij.ideabuck.build.BuckBuildUtil; import com.facebook.buck.intellij.ideabuck.lang.BuckFile; import com.facebook.buck.intellij.ideabuck.lang.psi.BuckPsiUtils; import com.facebook.buck.intellij.ideabuck.lang.psi.BuckTypes; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.intellij.codeInsight.editorActions.CopyPastePreProcessor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RawText; import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiPackage; import com.intellij.psi.TokenType; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiUtilCore; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jetbrains.annotations.Nullable; public class BuckCopyPasteProcessor implements CopyPastePreProcessor { private static final Pattern UNSOLVED_DEPENDENCY_PATTERN = Pattern.compile("^(package|import)\\s*([\\w\\.]*);?\\s*$"); private static final Pattern SOLVED_DEPENDENCY_PATTERN = Pattern.compile("^\\s*[\\w/]*:[\\w]+\\s*$"); @Nullable @Override public String preprocessOnCopy(PsiFile psiFile, int[] ints, int[] ints1, String s) { return null; } @Override public String preprocessOnPaste( Project project, PsiFile psiFile, Editor editor, String text, RawText rawText) { if (!(psiFile instanceof BuckFile)) { return text; } final Document document = editor.getDocument(); PsiDocumentManager.getInstance(project).commitDocument(document); final SelectionModel selectionModel = editor.getSelectionModel(); // Pastes in block selection mode (column mode) are not handled by a CopyPasteProcessor. final int selectionStart = selectionModel.getSelectionStart(); final PsiElement element = psiFile.findElementAt(selectionStart); if (element == null) { return text; } if (BuckPsiUtils.hasElementType( element.getNode(), TokenType.WHITE_SPACE, BuckTypes.SINGLE_QUOTED_STRING, BuckTypes.DOUBLE_QUOTED_STRING)) { PsiElement property = BuckPsiUtils.findAncestorWithType(element, BuckTypes.PROPERTY); if (checkPropertyName(property)) { return formatPasteText(text, element, project); } } return text; } protected boolean checkPropertyName(PsiElement property) { if (property == null) { return false; } PsiElement leftValue = property.getFirstChild(); if (leftValue == null || leftValue.getNode().getElementType() != BuckTypes.PROPERTY_LVALUE) { return false; } leftValue = leftValue.getFirstChild(); if (leftValue == null || leftValue.getNode().getElementType() != BuckTypes.IDENTIFIER) { return false; } if (leftValue.getText().equals("deps") || leftValue.getText().equals("visibility")) { return true; } return false; } /** * Automatically convert to buck dependency pattern Example 1: "import * com.example.activity.MyFirstActivity" -> "//java/com/example/activity:activity" * * <p>Example 2: "package com.example.activity;" -> "//java/com/example/activity:activity" * * <p>Example 3: "com.example.activity.MyFirstActivity" -> "//java/com/example/activity:activity" * * <p>Example 4: "/Users/tim/tb/java/com/example/activity/BUCK" -> * "//java/com/example/activity:activity" * * <p>Example 5 //apps/myapp:app -> "//apps/myapp:app", */ private String formatPasteText(String text, PsiElement element, Project project) { Iterable<String> paths = Splitter.on('\n').trimResults().omitEmptyStrings().split(text); List<String> results = new ArrayList<>(); for (String path : paths) { Matcher matcher = UNSOLVED_DEPENDENCY_PATTERN.matcher(path); if (matcher.matches()) { results.add(resolveUnsolvedBuckDependency(element, project, matcher.group(2))); } else if (SOLVED_DEPENDENCY_PATTERN.matcher(path).matches()) { results.add(buildSolvedBuckDependency(path)); } else { // Any non-target results in no formatting return text; } } return Joiner.on('\n').skipNulls().join(results); } private String buildSolvedBuckDependency(String path) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append('"'); if (!(path.startsWith("//") || path.startsWith(":"))) { if (path.startsWith("/")) { stringBuilder.append('/'); } else { stringBuilder.append("//"); } } return stringBuilder.append(path).append("\",").toString(); } private String resolveUnsolvedBuckDependency(PsiElement element, Project project, String path) { String original = path; VirtualFile buckFile = referenceNameToBuckFile(project, path); if (buckFile != null) { path = buckFile.getPath().replaceFirst(project.getBasePath(), ""); path = "/" + path.replace('.', '/'); path = path.substring(0, path.lastIndexOf("/")); String target = BuckBuildUtil.extractBuckTarget(project, buckFile); if (target != null) { path += target; } else { String lastWord = path.substring(path.lastIndexOf("/") + 1, path.length()); path += ":" + lastWord; } if (element.getNode().getElementType() == TokenType.WHITE_SPACE) { path = "\"" + path + "\","; } return path; } else { return original; } } private VirtualFile referenceNameToBuckFile(Project project, String reference) { // First test if it is a absolute path of a file. File tryFile = new File(reference); if (tryFile != null) { VirtualFile file = VfsUtil.findFileByIoFile(tryFile, true); if (file != null) { return BuckBuildUtil.getBuckFileFromDirectory(file.getParent()); } } // Try class firstly. PsiClass classElement = JavaPsiFacade.getInstance(project) .findClass(reference, GlobalSearchScope.allScope(project)); if (classElement != null) { VirtualFile file = PsiUtilCore.getVirtualFile(classElement); return BuckBuildUtil.getBuckFileFromDirectory(file.getParent()); } // Then try package. PsiPackage packageElement = JavaPsiFacade.getInstance(project).findPackage(reference); if (packageElement != null) { PsiDirectory directory = packageElement.getDirectories()[0]; return BuckBuildUtil.getBuckFileFromDirectory(directory.getVirtualFile()); } // Extract the package from the reference. int index = reference.lastIndexOf("."); if (index == -1) { return null; } reference = reference.substring(0, index); // Try to find the package again. packageElement = JavaPsiFacade.getInstance(project).findPackage(reference); if (packageElement != null) { PsiDirectory directory = packageElement.getDirectories()[0]; return BuckBuildUtil.getBuckFileFromDirectory(directory.getVirtualFile()); } return null; } }