blob: b0f5c3adb85119c84bd5a2ba0f6f4bd3add9a909 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.android.tools.idea.rendering;
import com.android.ide.common.rendering.RenderSecurityManager;
import com.android.resources.ResourceType;
import com.android.tools.idea.configurations.RenderContext;
import com.android.tools.idea.gradle.project.GradleProjectImporter;
import com.android.tools.idea.gradle.util.ProjectBuilder;
import com.android.tools.idea.gradle.variant.view.BuildVariantView;
import com.android.tools.idea.model.ManifestInfo;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.utils.SdkUtils;
import com.android.utils.SparseArray;
import com.intellij.codeInsight.daemon.impl.quickfix.CreateClassKind;
import com.intellij.codeInsight.intention.impl.CreateClassDialog;
import com.intellij.ide.browsers.BrowserLauncher;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.ClassUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.PsiNavigateUtil;
import org.jetbrains.android.inspections.lint.SuppressLintIntentionAction;
import org.jetbrains.android.uipreview.ChooseClassDialog;
import org.jetbrains.android.uipreview.ChooseResourceDialog;
import org.jetbrains.android.uipreview.ModuleClassLoader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.File;
import java.net.MalformedURLException;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.android.SdkConstants.*;
public class HtmlLinkManager {
private static final String URL_EDIT_CLASSPATH = "action:classpath";
private static final String URL_BUILD = "action:build";
private static final String URL_SYNC = "action:sync";
private static final String URL_SHOW_XML = "action:showXml";
private static final String URL_ACTION_IGNORE_FRAGMENTS = "action:ignoreFragment";
private static final String URL_RUNNABLE = "runnable:";
private static final String URL_COMMAND = "command:";
private static final String URL_REPLACE_TAGS = "replaceTags:";
private static final String URL_SHOW_TAG = "showTag:";
private static final String URL_OPEN = "open:";
private static final String URL_CREATE_CLASS = "createClass:";
private static final String URL_OPEN_CLASS = "openClass:";
private static final String URL_ASSIGN_FRAGMENT_URL = "assignFragmentUrl:";
private static final String URL_ASSIGN_LAYOUT_URL = "assignLayoutUrl:";
private static final String URL_EDIT_ATTRIBUTE = "editAttribute:";
private static final String URL_REPLACE_ATTRIBUTE_VALUE = "replaceAttributeValue:";
private static final String URL_DISABLE_SANDBOX = "disableSandbox:";
private static final String URL_REFRESH_RENDER = "refreshRender";
static final String URL_ACTION_CLOSE = "action:close";
private SparseArray<Runnable> myLinkRunnables;
private SparseArray<WriteCommandAction> myLinkCommands;
private int myNextLinkId = 0;
public HtmlLinkManager() {
}
public void handleUrl(@NotNull String url, @Nullable Module module, @Nullable PsiFile file, @Nullable DataContext dataContext,
@Nullable RenderResult result) {
if (url.startsWith("http:") || url.startsWith("https:")) {
BrowserLauncher.getInstance().browse(url, null, module == null ? null : module.getProject());
}
else if (url.startsWith("file:")) {
assert module != null;
handleFileUrl(url, module);
}
else if (url.startsWith(URL_REPLACE_TAGS)) {
assert module != null;
assert file != null;
handleReplaceTagsUrl(url, module, file);
}
else if (url.equals(URL_BUILD)) {
assert dataContext != null;
assert module != null;
handleCompileModuleUrl(url, module);
}
else if (url.equals(URL_SYNC)) {
assert dataContext != null;
assert module != null;
handleSyncProjectUrl(url, module.getProject());
}
else if (url.equals(URL_EDIT_CLASSPATH)) {
assert module != null;
handleEditClassPathUrl(url, module);
}
else if (url.startsWith(URL_CREATE_CLASS)) {
assert module != null && file != null;
handleNewClassUrl(url, module);
}
else if (url.startsWith(URL_OPEN)) {
assert module != null;
handleOpenStackUrl(url, module);
}
else if (url.startsWith(URL_OPEN_CLASS)) {
assert module != null;
handleOpenClassUrl(url, module);
}
else if (url.equals(URL_SHOW_XML)) {
assert module != null && file != null;
handleShowXmlUrl(url, module, file);
}
else if (url.startsWith(URL_SHOW_TAG)) {
assert module != null && file != null;
handleShowTagUrl(url, module, file);
}
else if (url.startsWith(URL_ASSIGN_FRAGMENT_URL)) {
assert module != null && file != null;
handleAssignFragmentUrl(url, module, file);
}
else if (url.startsWith(URL_ASSIGN_LAYOUT_URL)) {
assert module != null && file != null;
handleAssignLayoutUrl(url, module, file);
}
else if (url.equals(URL_ACTION_IGNORE_FRAGMENTS)) {
assert result != null;
handleIgnoreFragments(url, result);
}
else if (url.startsWith(URL_EDIT_ATTRIBUTE)) {
assert result != null;
if (module != null && file != null) {
handleEditAttribute(url, module, file);
}
}
else if (url.startsWith(URL_REPLACE_ATTRIBUTE_VALUE)) {
assert result != null;
if (module != null && file != null) {
handleReplaceAttributeValue(url, module, file);
}
}
else if (url.startsWith(URL_DISABLE_SANDBOX)) {
assert module != null;
handleDisableSandboxUrl(module, result);
}
else if (url.startsWith(URL_RUNNABLE)) {
Runnable linkRunnable = getLinkRunnable(url);
if (linkRunnable != null) {
linkRunnable.run();
}
}
else if (url.startsWith(URL_COMMAND)) {
WriteCommandAction command = getLinkCommand(url);
if (command != null) {
command.execute();
}
} else if (url.startsWith(URL_REFRESH_RENDER)) {
handleRefreshRenderUrl(result);
}
else {
assert false : "Unexpected URL: " + url;
}
}
/** Creates a file url for the given file and line position
*
* @param file the file
* @param line the line, or -1 if not known
* @param column the column, or 0 if not known
* @return a URL which points to a given position in a file
*/
@Nullable
public static String createFilePositionUrl(@NotNull File file, int line, int column) {
try {
String fileUrl = SdkUtils.fileToUrlString(file);
if (line != -1) {
if (column > 0) {
return fileUrl + ':' + line + ':' + column;
} else {
return fileUrl + ':' + line;
}
}
return fileUrl;
}
catch (MalformedURLException e) {
// Ignore
Logger.getInstance(HtmlLinkManager.class).error(e);
return null;
}
}
private static void handleFileUrl(@NotNull String url, @NotNull Module module) {
Project project = module.getProject();
try {
// Allow line numbers and column numbers to be tacked on at the end of
// the file URL:
// file:<path>:<line>:<column>
// file:<path>:<line>
int line = -1;
int column = 0;
Pattern pattern = Pattern.compile(".*:(\\d+)(:(\\d+))");
Matcher matcher = pattern.matcher(url);
if (matcher.matches()) {
line = Integer.parseInt(matcher.group(1));
column = Integer.parseInt(matcher.group(3));
url = url.substring(0, matcher.start(1) - 1);
} else {
matcher = Pattern.compile(".*:(\\d+)").matcher(url);
if (matcher.matches()) {
line = Integer.parseInt(matcher.group(1));
url = url.substring(0, matcher.start(1) - 1);
}
}
File ioFile = SdkUtils.urlToFile(url);
VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(ioFile);
if (file != null) {
openEditor(project, file, line, column);
}
}
catch (MalformedURLException e) {
// Ignore
}
}
public String createCommandLink(@NotNull WriteCommandAction command) {
String url = URL_COMMAND + myNextLinkId;
if (myLinkCommands == null) {
myLinkCommands = new SparseArray<WriteCommandAction>(5);
}
myLinkCommands.put(myNextLinkId, command);
myNextLinkId++;
return url;
}
@Nullable
private WriteCommandAction getLinkCommand(String url) {
if (myLinkCommands != null && url.startsWith(URL_COMMAND)) {
String idString = url.substring(URL_COMMAND.length());
int id = Integer.decode(idString);
return myLinkCommands.get(id);
}
return null;
}
public String createRunnableLink(@NotNull Runnable runnable) {
String url = URL_RUNNABLE + myNextLinkId;
if (myLinkRunnables == null) {
myLinkRunnables = new SparseArray<Runnable>(5);
}
myLinkRunnables.put(myNextLinkId, runnable);
myNextLinkId++;
return url;
}
@Nullable
private Runnable getLinkRunnable(String url) {
if (myLinkRunnables != null && url.startsWith(URL_RUNNABLE)) {
String idString = url.substring(URL_RUNNABLE.length());
int id = Integer.decode(idString);
return myLinkRunnables.get(id);
}
return null;
}
public String createReplaceTagsUrl(String from, String to) {
return URL_REPLACE_TAGS + from + '/' + to;
}
private static void handleReplaceTagsUrl(@NotNull String url, @NotNull Module module, @NotNull PsiFile file) {
assert url.startsWith(URL_REPLACE_TAGS) : url;
int start = URL_REPLACE_TAGS.length();
int delimiterPos = url.indexOf('/', start);
if (delimiterPos != -1) {
String wrongTag = url.substring(start, delimiterPos);
String rightTag = url.substring(delimiterPos + 1);
ReplaceTagFix fix = new ReplaceTagFix(module.getProject(), (XmlFile)file, wrongTag, rightTag);
fix.execute();
}
}
public String createCompileModuleUrl() {
return URL_BUILD;
}
private static void handleCompileModuleUrl(@NotNull String url, @NotNull Module module) {
assert url.equals(URL_BUILD) : url;
ProjectBuilder.getInstance(module.getProject()).compileJava();
}
public String createSyncProjectUrl() {
return URL_SYNC;
}
private static void handleSyncProjectUrl(@NotNull String url, @NotNull Project project) {
assert url.equals(URL_SYNC) : url;
BuildVariantView.getInstance(project).projectImportStarted();
GradleProjectImporter.getInstance().requestProjectSync(project, null);
}
public String createEditClassPathUrl() {
return URL_EDIT_CLASSPATH;
}
private static void handleEditClassPathUrl(@NotNull String url, @NotNull Module module) {
assert url.equals(URL_EDIT_CLASSPATH) : url;
ProjectSettingsService.getInstance(module.getProject()).openModuleSettings(module);
}
public String createOpenClassUrl(@NotNull String className) {
return URL_OPEN_CLASS + className;
}
private static void handleOpenClassUrl(@NotNull String url, @NotNull Module module) {
assert url.startsWith(URL_OPEN_CLASS) : url;
String className = url.substring(URL_OPEN_CLASS.length());
Project project = module.getProject();
PsiClass clz = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project));
if (clz != null) {
PsiFile containingFile = clz.getContainingFile();
if (containingFile != null) {
openEditor(project, containingFile, clz.getTextOffset());
}
}
}
public String createShowXmlUrl() {
return URL_SHOW_XML;
}
private static void handleShowXmlUrl(@NotNull String url, @NotNull Module module, @NotNull PsiFile file) {
assert url.equals(URL_SHOW_XML) : url;
openEditor(module.getProject(), file, 0, -1);
}
public String createShowTagUrl(String tag) {
return URL_SHOW_TAG + tag;
}
private static void handleShowTagUrl(@NotNull String url, @NotNull Module module, @NotNull final PsiFile file) {
assert url.startsWith(URL_SHOW_TAG) : url;
final String tagName = url.substring(URL_SHOW_TAG.length());
XmlTag first = ApplicationManager.getApplication().runReadAction(new Computable<XmlTag>() {
@Override
@Nullable
public XmlTag compute() {
Collection<XmlTag> xmlTags = PsiTreeUtil.findChildrenOfType(file, XmlTag.class);
for (XmlTag tag : xmlTags) {
if (tagName.equals(tag.getName())) {
return tag;
}
}
return null;
}
});
if (first != null) {
PsiNavigateUtil.navigate(first);
} else {
// Fall back to just opening the editor
openEditor(module.getProject(), file, 0, -1);
}
}
public String createNewClassUrl(String className) {
return URL_CREATE_CLASS + className;
}
private static void handleNewClassUrl(@NotNull String url, @NotNull Module module) {
assert url.startsWith(URL_CREATE_CLASS) : url;
String s = url.substring(URL_CREATE_CLASS.length());
final Project project = module.getProject();
String title = "Create Custom View";
final String className;
final String packageName;
int index = s.lastIndexOf('.');
if (index == -1) {
className = s;
packageName = ManifestInfo.get(module, false).getPackage();
if (packageName == null) {
return;
}
} else {
packageName = s.substring(0, index);
className = s.substring(index + 1);
}
CreateClassDialog dialog = new CreateClassDialog(project, title, className, packageName, CreateClassKind.CLASS, true, module) {
@Override
protected boolean reportBaseInSourceSelectionInTest() {
return true;
}
};
dialog.show();
if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
final PsiDirectory targetDirectory = dialog.getTargetDirectory();
if (targetDirectory != null) {
PsiClass newClass = new WriteCommandAction<PsiClass>(project, "Create Class") {
@Override
protected void run(@NotNull Result<PsiClass> result) throws Throwable {
PsiClass targetClass = JavaDirectoryService.getInstance().createClass(targetDirectory, className);
PsiManager manager = PsiManager.getInstance(project);
final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
final PsiElementFactory factory = facade.getElementFactory();
// Extend android.view.View
PsiJavaCodeReferenceElement superclassReference =
factory.createReferenceElementByFQClassName(CLASS_VIEW, targetClass.getResolveScope());
PsiReferenceList extendsList = targetClass.getExtendsList();
if (extendsList != null) {
extendsList.add(superclassReference);
}
// Add constructor
GlobalSearchScope scope = GlobalSearchScope.allScope(project);
PsiJavaFile javaFile = (PsiJavaFile)targetClass.getContainingFile();
PsiImportList importList = javaFile.getImportList();
if (importList != null) {
PsiClass contextClass = JavaPsiFacade.getInstance(project).findClass(CLASS_CONTEXT, scope);
if (contextClass != null) {
importList.add(factory.createImportStatement(contextClass));
}
PsiClass attributeSetClass = JavaPsiFacade.getInstance(project).findClass(CLASS_ATTRIBUTE_SET, scope);
if (attributeSetClass != null) {
importList.add(factory.createImportStatement(attributeSetClass));
}
}
PsiMethod constructor = factory.createMethodFromText(
"public " + className + "(Context context, AttributeSet attrs, int defStyle) {\n" +
" super(context, attrs, defStyle);\n" +
"}\n", targetClass);
targetClass.add(constructor);
// Format class
CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(manager.getProject());
PsiFile containingFile = targetClass.getContainingFile();
if (containingFile != null) {
codeStyleManager.reformat(javaFile);
}
result.setResult(targetClass);
}
}.execute().getResultObject();
if (newClass != null) {
PsiFile file = newClass.getContainingFile();
if (file != null) {
openEditor(project, file, newClass.getTextOffset());
}
}
}
}
}
public String createOpenStackUrl(@NotNull String className, @NotNull String methodName, @NotNull String fileName, int lineNumber) {
return URL_OPEN + className + '#' + methodName + ';' + fileName + ':' + lineNumber;
}
private static void handleOpenStackUrl(@NotNull String url, @NotNull Module module) {
assert url.startsWith(URL_OPEN) : url;
// Syntax: URL_OPEN + className + '#' + methodName + ';' + fileName + ':' + lineNumber;
int start = URL_OPEN.length();
int semi = url.indexOf(';', start);
String className;
String fileName;
int line;
if (semi != -1) {
className = url.substring(start, semi);
int colon = url.indexOf(':', semi + 1);
if (colon != -1) {
fileName = url.substring(semi + 1, colon);
line = Integer.decode(url.substring(colon + 1));
} else {
fileName = url.substring(semi + 1);
line = -1;
}
// Attempt to open file
} else {
className = url.substring(start);
fileName = null;
line = -1;
}
String method = null;
int hash = className.indexOf('#');
if (hash != -1) {
method = className.substring(hash + 1);
className = className.substring(0, hash);
}
Project project = module.getProject();
PsiClass clz = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project));
if (clz != null) {
PsiFile containingFile = clz.getContainingFile();
if (fileName != null && containingFile != null && line != -1) {
VirtualFile virtualFile = containingFile.getVirtualFile();
if (virtualFile != null) {
String name = virtualFile.getName();
if (fileName.equals(name)) {
// Use the line number rather than the methodName
openEditor(project, containingFile, line - 1, -1);
return;
}
}
}
if (method != null) {
PsiMethod[] methodsByName = clz.findMethodsByName(method, true);
for (PsiMethod m : methodsByName) {
PsiFile psiFile = m.getContainingFile();
if (psiFile != null) {
VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile != null) {
OpenFileDescriptor descriptor = new OpenFileDescriptor(project, virtualFile, m.getTextOffset());
FileEditorManager.getInstance(project).openEditor(descriptor, true);
return;
}
}
}
}
if (fileName != null) {
PsiFile[] files = FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.allScope(project));
for (PsiFile psiFile : files) {
if (openEditor(project, psiFile, line != -1 ? line - 1 : -1, -1)) {
break;
}
}
}
}
}
private static boolean openEditor(@NotNull Project project, @NotNull PsiFile psiFile, int line, int column) {
VirtualFile file = psiFile.getVirtualFile();
if (file != null) {
return openEditor(project, file, line, column);
}
return false;
}
private static boolean openEditor(@NotNull Project project, @NotNull VirtualFile file, int line, int column) {
OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file, line, column);
return !FileEditorManager.getInstance(project).openEditor(descriptor, true).isEmpty();
}
private static boolean openEditor(@NotNull Project project, @NotNull PsiFile psiFile, int offset) {
VirtualFile file = psiFile.getVirtualFile();
if (file != null) {
return openEditor(project, file, offset);
}
return false;
}
private static boolean openEditor(@NotNull Project project, @NotNull VirtualFile file, int offset) {
OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file, offset);
return !FileEditorManager.getInstance(project).openEditor(descriptor, true).isEmpty();
}
public String createAssignFragmentUrl(@Nullable String id) {
return URL_ASSIGN_FRAGMENT_URL + (id != null ? id : "");
}
/**
* Converts a (possibly ambiguous) class name like A.B.C.D into an Android-style class name
* like A.B$C$D where package names are separated by dots and inner classes are separated by dollar
* signs (similar to how the internal JVM format uses dollar signs for inner classes but slashes
* as package separators)
*/
@NotNull
private static String getFragmentClass(@NotNull final Module module, @NotNull final String fqcn) {
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Override
@NotNull
public String compute() {
Project project = module.getProject();
JavaPsiFacade finder = JavaPsiFacade.getInstance(project);
PsiClass psiClass = finder.findClass(fqcn, module.getModuleScope());
if (psiClass == null) {
psiClass = finder.findClass(fqcn, GlobalSearchScope.allScope(project));
}
if (psiClass != null) {
String jvmClassName = ClassUtil.getJVMClassName(psiClass);
if (jvmClassName != null) {
return jvmClassName.replace('/', '.');
}
}
return fqcn;
}
});
}
private static void handleAssignFragmentUrl(@NotNull String url, @NotNull Module module, @NotNull final PsiFile file) {
assert url.startsWith(URL_ASSIGN_FRAGMENT_URL) : url;
ChooseClassDialog dialog = new ChooseClassDialog(module, "Fragments", true, CLASS_FRAGMENT, CLASS_V4_FRAGMENT);
if (!dialog.showAndGet()) {
return;
}
final String fragmentClass = getFragmentClass(module, dialog.getClassName());
int start = URL_ASSIGN_FRAGMENT_URL.length();
final String id;
if (start == url.length()) {
// No specific fragment identified; use the first one
id = null;
}
else {
id = LintUtils.stripIdPrefix(url.substring(start));
}
WriteCommandAction<Void> action = new WriteCommandAction<Void>(module.getProject(), "Assign Fragment", file) {
@Override
protected void run(@NotNull Result<Void> result) throws Throwable {
Collection<XmlTag> tags = PsiTreeUtil.findChildrenOfType(file, XmlTag.class);
for (XmlTag tag : tags) {
if (!tag.getName().equals(VIEW_FRAGMENT)) {
continue;
}
if (id != null) {
String tagId = tag.getAttributeValue(ATTR_ID, ANDROID_URI);
if (tagId == null || !tagId.endsWith(id) || !id.equals(LintUtils.stripIdPrefix(tagId))) {
continue;
}
}
if (tag.getAttribute(ATTR_NAME, ANDROID_URI) == null && tag.getAttribute(ATTR_CLASS) == null) {
tag.setAttribute(ATTR_NAME, ANDROID_URI, fragmentClass);
return;
}
}
if (id == null) {
for (XmlTag tag : tags) {
if (!tag.getName().equals(VIEW_FRAGMENT)) {
continue;
}
tag.setAttribute(ATTR_NAME, ANDROID_URI, fragmentClass);
break;
}
}
}
};
action.execute();
}
public String createPickLayoutUrl(@NotNull String activityName) {
return URL_ASSIGN_LAYOUT_URL + activityName;
}
public String createAssignLayoutUrl(@NotNull String activityName, @NotNull String layout) {
return URL_ASSIGN_LAYOUT_URL + activityName + ':' + layout;
}
private static void handleAssignLayoutUrl(@NotNull String url, @NotNull final Module module, @NotNull final PsiFile file) {
assert url.startsWith(URL_ASSIGN_LAYOUT_URL) : url;
int start = URL_ASSIGN_LAYOUT_URL.length();
int layoutStart = url.indexOf(':', start + 1);
Project project = module.getProject();
XmlFile xmlFile = (XmlFile)file;
if (layoutStart == -1) {
// Only specified activity; pick it
String activityName = url.substring(start);
pickLayout(module, xmlFile, activityName);
} else {
// Set directory to specified layoutName
final String activityName = url.substring(start, layoutStart);
final String layoutName = url.substring(layoutStart + 1);
final String layout = LAYOUT_RESOURCE_PREFIX + layoutName;
assignLayout(project, xmlFile, activityName, layout);
}
}
private static void pickLayout(
@NotNull final Module module,
@NotNull final XmlFile file,
@NotNull final String activityName) {
ChooseResourceDialog dialog = new ChooseResourceDialog(module, new ResourceType[]{ResourceType.LAYOUT}, null, null) {
@NotNull
@Override
protected Action[] createLeftSideActions() {
return new Action[0];
}
};
if (dialog.showAndGet()) {
String layout = dialog.getResourceName();
if (!layout.equals(LAYOUT_RESOURCE_PREFIX + file.getName())) {
assignLayout(module.getProject(), file, activityName, layout);
}
}
}
private static void assignLayout(
@NotNull final Project project,
@NotNull final XmlFile file,
@NotNull final String activityName,
@NotNull final String layout) {
WriteCommandAction<Void> action = new WriteCommandAction<Void>(project, "Assign Preview Layout", file) {
@Override
protected void run(@NotNull Result<Void> result) throws Throwable {
SuppressLintIntentionAction.ensureNamespaceImported(getProject(), file, TOOLS_URI);
Collection<XmlTag> xmlTags = PsiTreeUtil.findChildrenOfType(file, XmlTag.class);
for (XmlTag tag : xmlTags) {
if (tag.getName().equals(VIEW_FRAGMENT) ) {
String name = tag.getAttributeValue(ATTR_CLASS);
if (name == null || name.isEmpty()) {
name = tag.getAttributeValue(ATTR_NAME, ANDROID_URI);
}
if (activityName.equals(name)) {
tag.setAttribute(ATTR_LAYOUT, TOOLS_URI, layout);
}
}
}
}
};
action.execute();
}
public String createIgnoreFragmentsUrl() {
return URL_ACTION_IGNORE_FRAGMENTS;
}
private static void handleIgnoreFragments(@NotNull String url, @NotNull RenderResult result) {
assert url.equals(URL_ACTION_IGNORE_FRAGMENTS);
RenderLogger.ignoreFragments();
requestRender(result);
}
public String createEditAttributeUrl(String attribute, String value) {
return URL_EDIT_ATTRIBUTE + attribute + '/' + value;
}
private static void handleEditAttribute(@NotNull String url, @NotNull Module module, @NotNull final PsiFile file) {
assert url.startsWith(URL_EDIT_ATTRIBUTE);
int attributeStart = URL_EDIT_ATTRIBUTE.length();
int valueStart = url.indexOf('/');
final String attributeName = url.substring(attributeStart, valueStart);
final String value = url.substring(valueStart + 1);
XmlAttribute first = ApplicationManager.getApplication().runReadAction(new Computable<XmlAttribute>() {
@Override
@Nullable
public XmlAttribute compute() {
Collection<XmlAttribute> attributes = PsiTreeUtil.findChildrenOfType(file, XmlAttribute.class);
for (XmlAttribute attribute : attributes) {
if (attributeName.equals(attribute.getLocalName()) && value.equals(attribute.getValue())) {
return attribute;
}
}
return null;
}
});
if (first != null) {
PsiNavigateUtil.navigate(first.getValueElement());
} else {
// Fall back to just opening the editor
openEditor(module.getProject(), file, 0, -1);
}
}
public String createReplaceAttributeValueUrl(String attribute, String oldValue, String newValue) {
return URL_REPLACE_ATTRIBUTE_VALUE + attribute + '/' + oldValue + '/' + newValue;
}
private static void handleReplaceAttributeValue(@NotNull String url, @NotNull Module module, @NotNull final PsiFile file) {
assert url.startsWith(URL_REPLACE_ATTRIBUTE_VALUE);
int attributeStart = URL_REPLACE_ATTRIBUTE_VALUE.length();
int valueStart = url.indexOf('/');
int newValueStart = url.indexOf('/', valueStart + 1);
final String attributeName = url.substring(attributeStart, valueStart);
final String oldValue = url.substring(valueStart + 1, newValueStart);
final String newValue = url.substring(newValueStart + 1);
WriteCommandAction<Void> action = new WriteCommandAction<Void>(module.getProject(), "Set Attribute Value", file) {
@Override
protected void run(@NotNull Result<Void> result) throws Throwable {
Collection<XmlAttribute> attributes = PsiTreeUtil.findChildrenOfType(file, XmlAttribute.class);
int oldValueLen = oldValue.length();
for (XmlAttribute attribute : attributes) {
if (attributeName.equals(attribute.getLocalName())) {
String attributeValue = attribute.getValue();
if (attributeValue == null) {
continue;
}
if (oldValue.equals(attributeValue)) {
attribute.setValue(newValue);
} else {
int index = attributeValue.indexOf(oldValue);
if (index != -1) {
if ((index == 0 || attributeValue.charAt(index - 1) == '|') &&
(index + oldValueLen == attributeValue.length() || attributeValue.charAt(index + oldValueLen) == '|')) {
attributeValue = attributeValue.substring(0, index) + newValue + attributeValue.substring(index + oldValueLen);
attribute.setValue(attributeValue);
}
}
}
}
}
}
};
action.execute();
}
public String createDisableSandboxUrl() {
return URL_DISABLE_SANDBOX;
}
private static void handleDisableSandboxUrl(@NotNull Module module, @Nullable RenderResult result) {
RenderSecurityManager.sEnabled = false;
requestRender(result);
Messages.showInfoMessage(module.getProject(),
"The custom view rendering sandbox was disabled for this session.\n\n" +
"You can turn it off permanently by adding\n" +
RenderSecurityManager.ENABLED_PROPERTY + "=" + VALUE_FALSE + "\n" +
"to {install}/bin/idea.properties.",
"Disabled Rendering Sandbox");
}
public String createRefreshRenderUrl() {
return URL_REFRESH_RENDER;
}
private static void handleRefreshRenderUrl(@Nullable RenderResult result) {
if (result != null) {
RenderTask renderTask = result.getRenderTask();
if (renderTask != null) {
RenderContext renderContext = renderTask.getRenderContext();
if (renderContext != null) {
RefreshRenderAction.clearCache(renderContext);
}
}
}
}
private static void requestRender(@Nullable RenderResult result) {
if (result != null) {
RenderTask renderTask = result.getRenderTask();
if (renderTask != null) {
RenderContext renderContext = renderTask.getRenderContext();
if (renderContext != null) {
renderContext.requestRender();
}
}
}
}
}