IDEA-155362 Open In Browser : action should work for files versons from History or...
[idea/community.git] / xml / impl / src / com / intellij / ide / browsers / actions / BaseOpenInBrowserAction.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.ide.browsers.actions;
17
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.IdeBundle;
20 import com.intellij.ide.browsers.*;
21 import com.intellij.ide.browsers.impl.WebBrowserServiceImpl;
22 import com.intellij.internal.statistic.UsageTrigger;
23 import com.intellij.openapi.actionSystem.*;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Editor;
27 import com.intellij.openapi.keymap.KeymapManager;
28 import com.intellij.openapi.keymap.KeymapUtil;
29 import com.intellij.openapi.project.DumbAwareAction;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.ui.Messages;
32 import com.intellij.openapi.ui.popup.JBPopupFactory;
33 import com.intellij.openapi.vcs.vfs.ContentRevisionVirtualFile;
34 import com.intellij.openapi.vfs.VirtualFile;
35 import com.intellij.psi.PsiDocumentManager;
36 import com.intellij.psi.PsiElement;
37 import com.intellij.psi.PsiFile;
38 import com.intellij.psi.PsiManager;
39 import com.intellij.ui.ColoredListCellRenderer;
40 import com.intellij.ui.components.JBList;
41 import com.intellij.util.BitUtil;
42 import com.intellij.util.Consumer;
43 import com.intellij.util.ObjectUtils;
44 import com.intellij.util.Url;
45 import com.intellij.util.containers.ContainerUtil;
46 import com.intellij.xml.util.HtmlUtil;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49 import org.jetbrains.concurrency.AsyncPromise;
50 import org.jetbrains.concurrency.Promise;
51
52 import javax.swing.*;
53 import java.awt.event.InputEvent;
54 import java.util.Collection;
55
56 public abstract class BaseOpenInBrowserAction extends DumbAwareAction {
57   private static final Logger LOG = Logger.getInstance(BaseOpenInBrowserAction.class);
58
59   protected BaseOpenInBrowserAction(@NotNull WebBrowser browser) {
60     super(browser.getName(), null, browser.getIcon());
61   }
62
63   @SuppressWarnings("UnusedDeclaration")
64   protected BaseOpenInBrowserAction(@Nullable String text, @Nullable String description, @Nullable Icon icon) {
65     super(text, description, icon);
66   }
67
68   @Nullable
69   protected abstract WebBrowser getBrowser(@NotNull AnActionEvent event);
70
71   @Override
72   public final void update(AnActionEvent e) {
73     WebBrowser browser = getBrowser(e);
74     if (browser == null) {
75       e.getPresentation().setEnabledAndVisible(false);
76       return;
77     }
78
79     OpenInBrowserRequest result = doUpdate(e);
80     if (result == null) {
81       return;
82     }
83
84     String description = getTemplatePresentation().getText();
85     if (ActionPlaces.CONTEXT_TOOLBAR.equals(e.getPlace())) {
86       StringBuilder builder = new StringBuilder(description);
87       builder.append(" (");
88       Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts("WebOpenInAction");
89       boolean exists = shortcuts.length > 0;
90       if (exists) {
91         builder.append(KeymapUtil.getShortcutText(shortcuts[0]));
92       }
93
94       if (HtmlUtil.isHtmlFile(result.getFile())) {
95         builder.append(exists ? ", " : "").append("hold Shift to open URL of local file");
96       }
97       builder.append(')');
98       description = builder.toString();
99     }
100     e.getPresentation().setText(description);
101   }
102
103   @Override
104   public final void actionPerformed(AnActionEvent e) {
105     WebBrowser browser = getBrowser(e);
106     if (browser != null) {
107       UsageTrigger.trigger("OpenInBrowser." + browser.getName());
108       open(e, browser);
109     }
110   }
111
112   @Nullable
113   public static OpenInBrowserRequest createRequest(@NotNull DataContext context) {
114     final Editor editor = CommonDataKeys.EDITOR.getData(context);
115     if (editor != null) {
116       Project project = editor.getProject();
117       if (project != null && project.isInitialized()) {
118         PsiFile psiFile = CommonDataKeys.PSI_FILE.getData(context);
119         if (psiFile == null) {
120           psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
121         }
122         if (psiFile != null && !(psiFile.getVirtualFile() instanceof ContentRevisionVirtualFile)) {
123           return new OpenInBrowserRequest(psiFile) {
124             private PsiElement element;
125
126             @NotNull
127             @Override
128             public PsiElement getElement() {
129               if (element == null) {
130                 element = getFile().findElementAt(editor.getCaretModel().getOffset());
131               }
132               return ObjectUtils.chooseNotNull(element, getFile());
133             }
134           };
135         }
136       }
137     }
138     else {
139       PsiFile psiFile = CommonDataKeys.PSI_FILE.getData(context);
140       VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(context);
141       Project project = CommonDataKeys.PROJECT.getData(context);
142       if (virtualFile != null && !virtualFile.isDirectory() && virtualFile.isValid() && project != null && project.isInitialized()) {
143         psiFile = PsiManager.getInstance(project).findFile(virtualFile);
144       }
145
146       if (psiFile != null && !(psiFile.getVirtualFile() instanceof ContentRevisionVirtualFile)) {
147         return OpenInBrowserRequest.create(psiFile);
148       }
149     }
150     return null;
151   }
152
153   @Nullable
154   public static OpenInBrowserRequest doUpdate(@NotNull AnActionEvent event) {
155     OpenInBrowserRequest request = createRequest(event.getDataContext());
156     boolean applicable = request != null && WebBrowserServiceImpl.getProvider(request) != null;
157     event.getPresentation().setEnabledAndVisible(applicable);
158     return applicable ? request : null;
159   }
160
161   public static void open(@NotNull AnActionEvent event, @Nullable WebBrowser browser) {
162     open(createRequest(event.getDataContext()), BitUtil.isSet(event.getModifiers(), InputEvent.SHIFT_MASK), browser);
163   }
164
165   public static void open(@Nullable final OpenInBrowserRequest request, boolean preferLocalUrl, @Nullable final WebBrowser browser) {
166     if (request == null) {
167       return;
168     }
169
170     try {
171       Collection<Url> urls = WebBrowserService.getInstance().getUrlsToOpen(request, preferLocalUrl);
172       if (!urls.isEmpty()) {
173         chooseUrl(urls)
174           .done(new Consumer<Url>() {
175             @Override
176             public void consume(Url url) {
177               ApplicationManager.getApplication().saveAll();
178               BrowserLauncher.getInstance().browse(url.toExternalForm(), browser, request.getProject());
179             }
180           });
181       }
182     }
183     catch (WebBrowserUrlProvider.BrowserException e1) {
184       Messages.showErrorDialog(e1.getMessage(), IdeBundle.message("browser.error"));
185     }
186     catch (Exception e1) {
187       LOG.error(e1);
188     }
189   }
190
191   @NotNull
192   private static Promise<Url> chooseUrl(@NotNull Collection<Url> urls) {
193     if (urls.size() == 1) {
194       return Promise.resolve(ContainerUtil.getFirstItem(urls));
195     }
196
197     final JBList list = new JBList(urls);
198     list.setCellRenderer(new ColoredListCellRenderer() {
199       @Override
200       protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
201         // todo icons looks good, but is it really suitable for all URLs providers?
202         setIcon(AllIcons.Nodes.Servlet);
203         append(((Url)value).toDecodedForm());
204       }
205     });
206
207     final AsyncPromise<Url> result = new AsyncPromise<Url>();
208     JBPopupFactory.getInstance()
209       .createListPopupBuilder(list)
210       .setTitle("Choose Url")
211       .setItemChoosenCallback(new Runnable() {
212         @Override
213         public void run() {
214           Url value = (Url)list.getSelectedValue();
215           if (value == null) {
216             result.setError("selected value is null");
217           }
218           else {
219             result.setResult(value);
220           }
221         }
222       })
223       .createPopup()
224       .showInFocusCenter();
225     return result;
226   }
227 }