blob: 244abd8e1c2b15ba775d946f0223ab1744e6eb7d [file] [log] [blame]
/*
* Copyright (C) 2016 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.google.devrel.cluestick.studioclient;
import com.google.devrel.cluestick.searchservice.CluestickSearch;
import com.appspot.cluestick_server.search.model.Result;
import com.intellij.codeInsight.CodeInsightActionHandler;
import com.intellij.codeInsight.TargetElementUtilBase;
import com.intellij.codeInsight.actions.CodeInsightAction;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.find.actions.FindUsagesInFileAction;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
/**
* Action to provide searching of the currently selected text in samples.
*/
public class FindSampleUsageAction extends CodeInsightAction implements CodeInsightActionHandler {
// TODO(thorogood): Don't inline strings in this class. Viva la i18n!
private static final Logger LOG = Logger
.getInstance("#com.google.devrel.cluestick.studioclient.FindSampleUsageAction");
public static final String PLUGIN_ID = "com.google.cluestick.studioclient";
@Override
public void update(AnActionEvent event) {
FindUsagesInFileAction.updateFindUsagesAction(event);
}
@Override
public void invoke(@NotNull final Project project, @NotNull final Editor editor,
@NotNull PsiFile psiFile) {
PsiDocumentManager.getInstance(project).commitAllDocuments();
int offset = editor.getCaretModel().getOffset();
final Symbol symbol = findSymbol(editor, offset);
if (symbol == null) {
showMessage(editor, "Please highlight a variable, type or method");
return;
}
IdeaPluginDescriptor pluginDescriptor = PluginManager.getPlugin(PluginId.getId(PLUGIN_ID));
final String userAgent = pluginDescriptor.getName() + "/" + pluginDescriptor.getVersion();
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
List<Result> results;
try {
results = ServiceManager.getService(CluestickSearch.class).performSearch(
symbol, userAgent);
} catch (IOException ex) {
LOG.warn("Couldn't perform Cluestick search", ex);
showMessage(editor, String.format("Samples are currently unavailable for: %s", symbol));
return;
}
if (results.isEmpty()) {
showMessage(editor, String.format("No samples found for: %s", symbol));
} else {
showSamplesToolWindow(project, symbol, results);
}
}
});
}
@Override
public boolean startInWriteAction() {
return false;
}
@NotNull
@Override
protected CodeInsightActionHandler getHandler() {
return this;
}
/**
* Finds the symbol name under the given position. This is similar to the behavior of the
* standard "Find Usages" action, except that-
* - this returns a binary/internal symbol, rather than the ref in user code
* - it falls back to returning an unmatched string
*
* @param editor To look within
* @param offset To search at
* @return The name of a matched symbol, or null
*/
@Nullable
private Symbol findSymbol(@NotNull Editor editor, int offset) {
TargetElementUtilBase util = TargetElementUtilBase.getInstance();
PsiElement targetElement = util.findTargetElement(editor, util.getAllAccepted(), offset);
Collection<Symbol> out = PsiHelpers.findSymbols(targetElement);
if (out.isEmpty()) {
// This could be an unindexed element - aka appearing in red. Fallback to a traversal,
// just to try to get a basic symbol out.
PsiReference reference = TargetElementUtilBase.findReference(editor, offset);
if (reference instanceof PsiJavaCodeReferenceElement) {
String qualifiedName = ((PsiJavaCodeReferenceElement) reference).getQualifiedName();
if (qualifiedName != null) {
return new Symbol(qualifiedName);
}
}
return null;
}
LOG.info("Symbols under cursor: " + out);
// Find the first symbol with a package name, or find the first fallback without one.
// TODO(thorogood): Order these at some point (unqualified last) and search for all.
Symbol fallback = null;
for (Symbol symbol : out) {
if (symbol.isQualified()) {
return symbol;
}
if (fallback == null) {
fallback = symbol;
}
}
return fallback;
}
/**
* Shows the list of results in a toolwindow panel.
*
* @param project The project.
* @param symbol The symbol selected in IntelliJ.
* @param results List of SearchResult objects from cloud endpoint generated lib.
*/
private void showSamplesToolWindow(@NotNull final Project project, final Symbol symbol,
final List<Result> results) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
DynamicToolWindowWrapper toolWindowWrapper = DynamicToolWindowWrapper.getInstance(project);
ToolWindow toolWindow = toolWindowWrapper.getToolWindow(symbol, results);
toolWindow.show((Runnable) null);
}
});
}
/**
* Shows an error message.
*
* @param editor The editor to show a message on.
* @param message The error message.
*/
private void showMessage(final Editor editor, final String message) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
HintManager.getInstance().showErrorHint(editor, message);
}
});
}
}