action to switch to scientific view in pycharm

- part of PY-23595
- documentation docked on the right
- documentation is auto updated from source
- data view as toolwindow is shown only in scientific view
- console is opened
- restore scientific view after restart
This commit is contained in:
Ekaterina Tuzova
2017-09-01 13:30:04 +03:00
parent 0f2ac1a485
commit ff193191d3
7 changed files with 332 additions and 10 deletions
@@ -47,7 +47,6 @@ import java.awt.*;
public abstract class DockablePopupManager<T extends JComponent & Disposable> {
protected ToolWindow myToolWindow;
private boolean myAutoUpdateDocumentation = PropertiesComponent.getInstance().isTrueValue(getAutoUpdateEnabledProperty());
private Runnable myAutoUpdateRequest;
@NotNull protected final Project myProject;
@@ -119,7 +118,7 @@ public abstract class DockablePopupManager<T extends JComponent & Disposable> {
new UiNotifyConnector(component, new Activatable() {
@Override
public void showNotify() {
restartAutoUpdate(myAutoUpdateDocumentation);
restartAutoUpdate(PropertiesComponent.getInstance().getBoolean(getAutoUpdateEnabledProperty()));
}
@Override
@@ -144,13 +143,12 @@ public abstract class DockablePopupManager<T extends JComponent & Disposable> {
AllIcons.General.AutoscrollFromSource) {
@Override
public boolean isSelected(AnActionEvent e) {
return myAutoUpdateDocumentation;
return PropertiesComponent.getInstance().getBoolean(getAutoUpdateEnabledProperty());
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
PropertiesComponent.getInstance().setValue(getAutoUpdateEnabledProperty(), state);
myAutoUpdateDocumentation = state;
restartAutoUpdate(state);
}
};
@@ -222,7 +220,7 @@ public abstract class DockablePopupManager<T extends JComponent & Disposable> {
}
protected void restorePopupBehavior() {
public void restorePopupBehavior() {
if (myToolWindow != null) {
PropertiesComponent.getInstance().setValue(getShowInToolWindowProperty(), Boolean.FALSE.toString());
ToolWindowManagerEx toolWindowManagerEx = ToolWindowManagerEx.getInstanceEx(myProject);
@@ -145,7 +145,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
}
@Override
protected void restorePopupBehavior() {
public void restorePopupBehavior() {
if (myPreviouslyFocused != null) {
IdeFocusManager.getInstance(myProject).requestFocus(myPreviouslyFocused, true);
}
@@ -860,6 +860,10 @@
<add-to-group group-id="DebugMainMenu" relative-to-action="StepInto" anchor="after"/>
<add-to-group group-id="XDebugger.ToolWindow.TopToolbar" relative-to-action="StepInto" anchor="after"/>
</action>
<action id="PySciView" class="com.jetbrains.python.actions.PySciViewAction">
<add-to-group group-id="ViewMenu" anchor="before" relative-to-action="ToggleFullScreenGroup"/>
</action>
</actions>
<extensions defaultExtensionNs="com.intellij.spellchecker">
@@ -867,6 +871,12 @@
<bundledDictionaryProvider implementation="com.jetbrains.python.spellchecker.PythonBundledDictionaryProvider"/>
</extensions>
<project-components>
<component>
<implementation-class>com.jetbrains.python.actions.PySciProjectComponent</implementation-class>
</component>
</project-components>
<application-components>
<component>
<implementation-class>com.jetbrains.python.testing.VFSTestFrameworkListener</implementation-class>
@@ -0,0 +1,151 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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.jetbrains.python.actions;
import com.intellij.codeInsight.documentation.DocumentationManager;
import com.intellij.ide.DataManager;
import com.intellij.notification.NotificationDisplayType;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.jetbrains.python.actions.PySciViewAction.ACTION_ID;
@State(name = "PySciProjectComponent", storages = @Storage("other.xml"))
public class PySciProjectComponent extends AbstractProjectComponent implements PersistentStateComponent<PySciProjectComponent.State> {
private static final NotificationGroup BALLOON_NOTIFICATIONS = new NotificationGroup("Python Scientific View Advertiser",
NotificationDisplayType.STICKY_BALLOON,
false);
private State myState = new State();
protected PySciProjectComponent(Project project) {
super(project);
}
public static PySciProjectComponent getInstance(Project project) {
return project.getComponent(PySciProjectComponent.class);
}
public void useSciView(boolean useSciView) {
myState.PY_SCI_VIEW = useSciView;
}
public boolean useSciView() {
return myState.PY_SCI_VIEW;
}
@Override
public void projectOpened() {
if (myState.PY_SCI_VIEW) {
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> {
final PsiDirectory directory = PsiManager.getInstance(myProject).findDirectory(myProject.getBaseDir());
if (directory != null) {
DocumentationManager.getInstance(myProject).showJavaDocInfo(directory, directory);
}
});
}
else if (!myState.PY_SCI_VIEW_SUGGESTED) {
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> {
final PsiDirectory directory = PsiManager.getInstance(myProject).findDirectory(myProject.getBaseDir());
if (directory != null) {
final Module module = ModuleUtilCore.findModuleForPsiElement(directory);
if (module != null) {
final Sdk sdk = PythonSdkType.findPythonSdk(module);
if (sdk != null) {
final List<PyPackage> packages = PyPackageUtil.refreshAndGetPackagesModally(sdk);
final PyPackage numpy = PyPackageUtil.findPackage(packages, "numpy");
if (numpy != null) {
showInspectionAdvertisement(myProject);
}
}
}
}
});
}
}
private void showInspectionAdvertisement(@NotNull Project project) {
final String msg = "Your source code imports the 'numpy' package." +
"<br/>Would you like to enable Scientific View?<br/>" +
"<a href=\"#yes\">Yes</a>&nbsp;&nbsp;<a href=\"#no\">No</a>";
showSingletonNotification(project, msg, NotificationType.INFORMATION, (notification, event) -> {
myState.PY_SCI_VIEW_SUGGESTED = true;
final boolean enabled = "#yes".equals(event.getDescription());
if (enabled) {
final AnAction action = ActionManager.getInstance().getAction(ACTION_ID);
if (action instanceof PySciViewAction) {
final AnActionEvent anActionEvent = AnActionEvent.createFromDataContext(
ActionPlaces.UNKNOWN, null, DataManager.getInstance().getDataContextFromFocus().getResult());
((PySciViewAction)action).setSelected(anActionEvent, true);
}
}
});
}
private static void showSingletonNotification(@NotNull Project project,
@NotNull String htmlContent,
@NotNull NotificationType type,
@NotNull NotificationListener listener) {
BALLOON_NOTIFICATIONS.createNotification("Scientific View", htmlContent, type, (notification, event) -> {
try {
listener.hyperlinkUpdate(notification, event);
}
finally {
notification.expire();
}
}).notify(project);
}
@Nullable
@Override
public PySciProjectComponent.State getState() {
return myState;
}
@Override
public void loadState(PySciProjectComponent.State state) {
myState.PY_SCI_VIEW = state.PY_SCI_VIEW;
myState.PY_SCI_VIEW_SUGGESTED = state.PY_SCI_VIEW_SUGGESTED;
}
public static class State {
public boolean PY_SCI_VIEW = false;
public boolean PY_SCI_VIEW_SUGGESTED = false;
}
}
@@ -0,0 +1,151 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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.jetbrains.python.actions;
import com.intellij.codeInsight.documentation.DocumentationManager;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.wm.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.jetbrains.python.console.PythonConsoleToolWindow;
import org.jetbrains.annotations.NotNull;
import static com.jetbrains.python.debugger.containerview.PyDataView.DATA_VIEWER_ID;
public class PySciViewAction extends ToggleAction implements DumbAware {
private static final String TEXT_SCI_VIEW = "Scientific Mode";
private boolean wasConsoleVisible = false;
private ToolWindowType myDocumentationType = ToolWindowType.FLOATING;
private boolean myDocumentationAutoHide = true;
public static final String ACTION_ID = "PySciView";
private ToolWindowType myDataViewType = ToolWindowType.FLOATING;
public PySciViewAction() {
super(TEXT_SCI_VIEW);
}
@Override
public boolean isSelected(AnActionEvent e) {
return e.getProject() != null && PySciProjectComponent.getInstance(e.getProject()).useSciView();
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
final Project project = e.getProject();
if (project == null) return;
PySciProjectComponent.getInstance(project).useSciView(state);
final PsiElement element = getPsiElement(e, project);
if (state) {
showConsoleToolwindow(project);
showDocumentationToolwindow(project, element);
showDataViewAsToolwindow(project);
}
else {
hideConsoleToolwindow(project);
restoreDocumentationPopup(project);
hideDataViewer(project);
}
}
private void showConsoleToolwindow(@NotNull final Project project) {
final ToolWindow consoleToolWindow = PythonConsoleToolWindow.getInstance(project).getToolWindow();
wasConsoleVisible = consoleToolWindow.isVisible();
consoleToolWindow.show(null);
}
private void hideConsoleToolwindow(@NotNull final Project project) {
if (!wasConsoleVisible) {
PythonConsoleToolWindow.getInstance(project).getToolWindow().hide(null);
}
}
private void showDocumentationToolwindow(Project project, PsiElement element) {
final String showInToolWindowProperty = DocumentationManager.getInstance(project).getShowInToolWindowProperty();
PropertiesComponent.getInstance().setValue(showInToolWindowProperty, true);
PropertiesComponent.getInstance().setValue(DocumentationManager.getInstance(project).getAutoUpdateEnabledProperty(), true);
if (element != null) {
DocumentationManager.getInstance(project).showJavaDocInfo(element, element);
}
final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.DOCUMENTATION);
if (window != null) {
window.setAnchor(ToolWindowAnchor.RIGHT, null);
myDocumentationType = window.getType();
myDocumentationAutoHide = window.isAutoHide();
window.setType(ToolWindowType.DOCKED, null);
window.setAutoHide(false);
}
}
private void restoreDocumentationPopup(Project project) {
PropertiesComponent.getInstance().setValue(DocumentationManager.getInstance(project).getAutoUpdateEnabledProperty(), false);
final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.DOCUMENTATION);
if (window != null) {
window.setType(myDocumentationType, null);
window.setAutoHide(myDocumentationAutoHide);
DocumentationManager.getInstance(project).restorePopupBehavior();
final JBPopup hint = DocumentationManager.getInstance(project).getDocInfoHint();
if (hint != null) {
hint.cancel();
}
}
}
private void showDataViewAsToolwindow(@NotNull final Project project) {
ToolWindow dataViewToolWindow = ToolWindowManager.getInstance(project).getToolWindow(DATA_VIEWER_ID);
myDataViewType = dataViewToolWindow.getType();
dataViewToolWindow.setType(ToolWindowType.DOCKED, null);
dataViewToolWindow.setShowStripeButton(true);
}
private void hideDataViewer(Project project) {
final ToolWindow dataViewer = ToolWindowManager.getInstance(project).getToolWindow(DATA_VIEWER_ID);
dataViewer.setType(myDataViewType, null);
dataViewer.setShowStripeButton(false);
}
private static PsiElement getPsiElement(AnActionEvent e, Project project) {
PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(e.getDataContext());
if (element == null) {
element = CommonDataKeys.PSI_FILE.getData(e.getDataContext());
}
if (element == null) {
element = PsiManager.getInstance(project).findDirectory(project.getBaseDir());
}
return element;
}
@Override
public void update(@NotNull AnActionEvent e) {
super.update(e);
final Project project = e.getProject();
final Presentation presentation = e.getPresentation();
presentation.setEnabledAndVisible(project != null);
}
}
@@ -95,6 +95,7 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable
private XStandaloneVariablesView mySplitView;
private ActionCallback myInitialized = new ActionCallback();
private boolean isShowVars = true;
public PythonConsoleView(final Project project, final String title, final Sdk sdk) {
super(project, title, PythonLanguage.getInstance());
@@ -115,6 +116,10 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable
public void setConsoleCommunication(final ConsoleCommunication communication) {
getFile().putCopyableUserData(PydevConsoleRunner.CONSOLE_KEY, communication);
if (isShowVars && communication instanceof PydevConsoleCommunication) {
showVariables((PydevConsoleCommunication)communication);
}
}
private PyConsoleStartFolding createConsoleFolding() {
@@ -491,4 +496,12 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable
public void initialized() {
myInitialized.setDone();
}
public void setShowVars(boolean showVars) {
isShowVars = showVars;
}
public boolean isShowVars() {
return isShowVars;
}
}
@@ -26,16 +26,15 @@ import com.jetbrains.python.console.PythonConsoleView
* Created by Yuli Fiterman on 9/18/2016.
*/
class ShowVarsAction(private val consoleView: PythonConsoleView, private val consoleComm: PydevConsoleCommunication) : ToggleAction("Show Variables", "Shows active console variables", AllIcons.Debugger.Watches), DumbAware {
private var mySelected = false
override fun isSelected(e: AnActionEvent): Boolean {
return mySelected
return consoleView.isShowVars
}
override fun setSelected(e: AnActionEvent, state: Boolean) {
mySelected = state
consoleView.isShowVars = state
if (mySelected) {
if (state) {
consoleView.showVariables(consoleComm)
}
else {