| /*=============================================================================# |
| # Copyright (c) 2009, 2020 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.internal.r.ui.rhelp; |
| |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.core.commands.AbstractHandler; |
| import org.eclipse.core.commands.ExecutionEvent; |
| import org.eclipse.core.commands.ExecutionException; |
| import org.eclipse.core.commands.IHandler2; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jface.action.IStatusLineManager; |
| import org.eclipse.jface.action.IToolBarManager; |
| import org.eclipse.jface.text.ITextSelection; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.IActionBars; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IPartListener2; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkbenchPartReference; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.browser.IWebBrowser; |
| import org.eclipse.ui.handlers.IHandlerService; |
| import org.eclipse.ui.menus.CommandContributionItem; |
| import org.eclipse.ui.menus.CommandContributionItemParameter; |
| import org.eclipse.ui.part.IPage; |
| import org.eclipse.ui.part.IShowInTarget; |
| import org.eclipse.ui.part.ShowInContext; |
| import org.eclipse.ui.progress.IProgressService; |
| import org.eclipse.ui.services.IServiceLocator; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| |
| import org.eclipse.statet.jcommons.lang.NonNull; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.status.ProgressMonitor; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| |
| import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils; |
| import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils; |
| import org.eclipse.statet.ecommons.text.TextUtil; |
| import org.eclipse.statet.ecommons.ui.SharedUIResources; |
| import org.eclipse.statet.ecommons.ui.actions.HandlerCollection; |
| import org.eclipse.statet.ecommons.ui.actions.SimpleContributionItem; |
| import org.eclipse.statet.ecommons.ui.mpbv.BookmarkCollection; |
| import org.eclipse.statet.ecommons.ui.mpbv.BrowserBookmark; |
| import org.eclipse.statet.ecommons.ui.mpbv.BrowserHandler; |
| import org.eclipse.statet.ecommons.ui.mpbv.BrowserSession; |
| import org.eclipse.statet.ecommons.ui.mpbv.PageBookBrowserPage; |
| import org.eclipse.statet.ecommons.ui.mpbv.PageBookBrowserView; |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| import org.eclipse.statet.ecommons.ui.workbench.ContextHandlers; |
| |
| import org.eclipse.statet.internal.r.debug.ui.RLaunchingMessages; |
| import org.eclipse.statet.internal.r.ui.rhelp.RequestSync.RequestKey; |
| import org.eclipse.statet.ltk.ast.core.util.AstSelection; |
| import org.eclipse.statet.ltk.ui.ISelectionWithElementInfoListener; |
| import org.eclipse.statet.ltk.ui.LTKInputData; |
| import org.eclipse.statet.ltk.ui.sourceediting.SourceEditor1; |
| import org.eclipse.statet.ltk.ui.util.LTKSelectionUtils; |
| import org.eclipse.statet.r.core.RCore; |
| import org.eclipse.statet.r.core.model.IRSourceUnit; |
| import org.eclipse.statet.r.core.model.RElementName; |
| import org.eclipse.statet.r.core.model.RModel; |
| import org.eclipse.statet.r.core.rsource.ast.RAstNode; |
| import org.eclipse.statet.r.launching.RCodeLaunching; |
| import org.eclipse.statet.r.ui.RUI; |
| import org.eclipse.statet.rhelp.core.REnvHelp; |
| import org.eclipse.statet.rhelp.core.RHelpPage; |
| import org.eclipse.statet.rhelp.core.RPkgHelp; |
| import org.eclipse.statet.rhelp.core.http.RHelpHttpService; |
| import org.eclipse.statet.rj.renv.core.REnv; |
| |
| |
| @NonNullByDefault |
| public class RHelpView extends PageBookBrowserView |
| implements ISelectionWithElementInfoListener, IShowInTarget { |
| |
| |
| public class RunCode extends AbstractHandler { |
| |
| private final boolean gotoConsole; |
| |
| public RunCode(final boolean gotoConsole) { |
| this.gotoConsole= gotoConsole; |
| } |
| |
| @Override |
| public void setEnabled(final @Nullable Object evaluationContext) { |
| setBaseEnabled(getCurrentBrowserPage() != null); |
| } |
| |
| @Override |
| public @Nullable Object execute(final ExecutionEvent event) throws ExecutionException { |
| final PageBookBrowserPage browserPage= getCurrentBrowserPage(); |
| if (browserPage != null) { |
| final String selectedText= browserPage.getSelectedText(); |
| if (selectedText != null && selectedText.length() > 0) { |
| try { |
| final List<String> lines= TextUtil.toLines(selectedText); |
| RCodeLaunching.runRCodeDirect(lines, this.gotoConsole, null); |
| } |
| catch (final CoreException e) { |
| final IStatus causeStatus= e.getStatus(); |
| final Status status= new Status(causeStatus.getSeverity(), RUI.BUNDLE_ID, 0, |
| RLaunchingMessages.RSelectionLaunch_error_message, e ); |
| StatusManager.getManager().handle(status); |
| final IStatusLineManager manager= getViewSite().getActionBars().getStatusLineManager(); |
| if (manager != null) { |
| if (causeStatus.getSeverity() == IStatus.ERROR) { |
| manager.setErrorMessage(causeStatus.getMessage()); |
| } |
| else { |
| manager.setMessage(causeStatus.getMessage()); |
| } |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| } |
| |
| private class LinkEditorHandler extends SimpleContributionItem { |
| |
| public LinkEditorHandler() { |
| super("Link with Editor", null, |
| SharedUIResources.getImages().getDescriptor(SharedUIResources.LOCTOOL_SYNCHRONIZED_IMAGE_ID), null, |
| STYLE_CHECK ); |
| } |
| |
| @Override |
| protected void execute() throws ExecutionException { |
| setLinkingWithEditor(!RHelpView.this.isLinkingWithEditor); |
| } |
| |
| } |
| |
| |
| private boolean isLinkingWithEditor; |
| private final LinkEditorHandler linkingWithEditorHandler= new LinkEditorHandler(); |
| private @Nullable SourceEditor1 linkedEditor; |
| |
| private @Nullable IPartListener2 partListener; |
| |
| private final RequestSync requestSync= new RequestSync(); |
| |
| |
| public RHelpView() { |
| super(); |
| } |
| |
| |
| @Override |
| public void dispose() { |
| if (this.partListener != null) { |
| getSite().getPage().removePartListener(this.partListener); |
| this.partListener= null; |
| } |
| if (this.linkedEditor != null) { |
| this.linkedEditor.removePostSelectionWithElementInfoListener(this); |
| this.linkedEditor= null; |
| } |
| |
| super.dispose(); |
| } |
| |
| @Override |
| public void createPartControl(final Composite parent) { |
| super.createPartControl(parent); |
| |
| if (!PlatformUI.getWorkbench().isStarting()) { |
| final Job job= new Job("Initial R Help Page") { //$NON-NLS-1$ |
| { setSystem(true); |
| setUser(false); |
| setPriority(Job.SHORT); |
| } |
| @Override |
| protected IStatus run(final IProgressMonitor monitor) { |
| RCore.getRHelpHttpService().ensureIsRunning(); |
| final Display display= UIAccess.getDisplay(); |
| if (getCurrentBrowserPage() == null |
| && display != null && !display.isDisposed()) { |
| display.asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (getCurrentBrowserPage() == null |
| && UIAccess.isOkToUse(getPageBook())) { |
| newPage(null, true); |
| } |
| } |
| }); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| job.schedule(50); |
| } |
| |
| initLinking(); |
| } |
| |
| private void initLinking() { |
| this.partListener= new IPartListener2() { |
| @Override |
| public void partOpened(final IWorkbenchPartReference partRef) { |
| } |
| @Override |
| public void partClosed(final IWorkbenchPartReference partRef) { |
| if (RHelpView.this.linkedEditor != null && partRef.getPart(false) == RHelpView.this.linkedEditor) { |
| clear(); |
| } |
| } |
| @Override |
| public void partVisible(final IWorkbenchPartReference partRef) { |
| } |
| @Override |
| public void partHidden(final IWorkbenchPartReference partRef) { |
| } |
| @Override |
| public void partInputChanged(final IWorkbenchPartReference partRef) { |
| } |
| @Override |
| public void partActivated(final IWorkbenchPartReference partRef) { |
| final IWorkbenchPart part= partRef.getPart(false); |
| if (part instanceof SourceEditor1) { |
| RHelpView.this.linkedEditor= (SourceEditor1) part; |
| RHelpView.this.linkedEditor.addPostSelectionWithElementInfoListener(RHelpView.this); |
| } |
| else if (part instanceof IEditorPart) { |
| clear(); |
| } |
| } |
| @Override |
| public void partDeactivated(final IWorkbenchPartReference partRef) { |
| } |
| @Override |
| public void partBroughtToTop(final IWorkbenchPartReference partRef) { |
| } |
| private void clear() { |
| if (RHelpView.this.linkedEditor != null) { |
| RHelpView.this.linkedEditor.removePostSelectionWithElementInfoListener(RHelpView.this); |
| RHelpView.this.linkedEditor= null; |
| } |
| } |
| }; |
| |
| getSite().getPage().addPartListener(this.partListener); |
| } |
| |
| @Override |
| protected void initActions(final IServiceLocator serviceLocator, final ContextHandlers handlers) { |
| super.initActions(serviceLocator, handlers); |
| final IHandlerService handlerService= serviceLocator.getService(IHandlerService.class); |
| |
| { final IHandler2 handler= new RunCode(false); |
| handlers.add(RCodeLaunching.SUBMIT_SELECTION_COMMAND_ID, handler); |
| handlerService.activateHandler(RCodeLaunching.SUBMIT_SELECTION_COMMAND_ID, handler); |
| } |
| { final IHandler2 handler= new RunCode(true); |
| handlers.add(RCodeLaunching.SUBMIT_FILEVIACOMMAND_GOTOCONSOLE_COMMAND_ID, handler); |
| handlerService.activateHandler(RCodeLaunching.SUBMIT_FILEVIACOMMAND_GOTOCONSOLE_COMMAND_ID, handler); |
| } |
| } |
| |
| @Override |
| protected @NonNull IHandler2 createOpenExternalHandler() { |
| return new BrowserHandler.OpenExternalHandler(getBrowserInterface()) { |
| @Override |
| protected void open(final String url, final IWebBrowser webBrowser) throws Exception { |
| try { |
| final URI serverUrl= RCore.getRHelpHttpService().toServerUrl(new URI(url)); |
| if (serverUrl != null) { |
| webBrowser.openURL(serverUrl.toURL()); |
| return; |
| } |
| } |
| catch (final URISyntaxException | MalformedURLException e) {} |
| |
| super.open(url, webBrowser); |
| } |
| }; |
| } |
| |
| @Override |
| protected void contributeToActionBars(final IServiceLocator serviceLocator, |
| final IActionBars actionBars, final HandlerCollection handlers) { |
| super.contributeToActionBars(serviceLocator, actionBars, handlers); |
| |
| final IToolBarManager toolBarManager= actionBars.getToolBarManager(); |
| toolBarManager.appendToGroup("bookmarks", //$NON-NLS-1$ |
| new CommandContributionItem(new CommandContributionItemParameter( |
| serviceLocator, null, "org.eclipse.statet.workbench.commands.OpenSearchDialog", //$NON-NLS-1$ |
| Collections.singletonMap("pageId", "org.eclipse.statet.r.searchPages.RHelpPage"), //$NON-NLS-1$ //$NON-NLS-2$ |
| null, null, null, |
| null, null, null, |
| CommandContributionItem.STYLE_PUSH, null, false))); |
| toolBarManager.add(this.linkingWithEditorHandler); |
| } |
| |
| @Override |
| protected PageBookBrowserPage doCreatePage(final BrowserSession session) { |
| return new RHelpViewPage(this, session); |
| } |
| |
| @Override |
| protected void updateTitle() { |
| final BrowserSession session= getCurrentSession(); |
| if (session == null) { |
| setContentDescription(getNoPageTitle()); |
| } |
| else { |
| setContentDescription(""); //$NON-NLS-1$ |
| } |
| } |
| |
| |
| @Override |
| protected BookmarkCollection initBookmarkCollection() { |
| final BookmarkCollection collection= BookmarkCollection.getCollection(RHelpPreferences.RHELP_QUALIFIER); |
| final List<BrowserBookmark> bookmarks= collection.getBookmarks(); |
| synchronized (collection) { |
| if (bookmarks.isEmpty()) { |
| bookmarks.add(new BrowserBookmark("R Homepage - The R Project for Statistical Computing", "http://www.r-project.org")); //$NON-NLS-1$ //$NON-NLS-2$ |
| bookmarks.add(new BrowserBookmark("CRAN - The Comprehensive R Archive Network", "http://cran.r-project.org/")); //$NON-NLS-1$ //$NON-NLS-2$ |
| bookmarks.add(new BrowserBookmark("RSeek.org - R-project Search Engine", "http://rseek.org/")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| return collection; |
| } |
| |
| @Override |
| protected @Nullable BrowserBookmark createBookmark() { |
| final PageBookBrowserPage page= getCurrentBrowserPage(); |
| if (page != null) { |
| String sUrl= page.getCurrentUrl(); |
| try { |
| final URI url= new URI(sUrl); |
| final URI portableUrl= RCore.getRHelpHttpService().toPortableUrl(url); |
| sUrl= (portableUrl != null) ? portableUrl.toString() : url.toString(); |
| } |
| catch (final URISyntaxException e) { |
| // ? |
| } |
| return new BrowserBookmark(page.getCurrentTitle(), sUrl); |
| } |
| return null; |
| } |
| |
| @Override |
| public String getHomePageUrl() { |
| return PreferenceUtils.getInstancePrefs().getPreferenceValue(RHelpPreferences.HOMEPAGE_URL_PREF); |
| } |
| |
| @Override |
| protected void collectContextMenuPreferencePages(final List<String> pageIds) { |
| pageIds.add("org.eclipse.statet.r.preferencePages.RHelpPage"); //$NON-NLS-1$ |
| pageIds.add("org.eclipse.statet.r.preferencePages.REnvironmentPage"); //$NON-NLS-1$ |
| super.collectContextMenuPreferencePages(pageIds); |
| } |
| |
| |
| public void setLinkingWithEditor(final boolean enable) { |
| this.isLinkingWithEditor= enable; |
| this.linkingWithEditorHandler.setChecked(enable); |
| if (enable && this.linkedEditor != null) { |
| final ISelection selection= this.linkedEditor.getShowInContext().getSelection(); |
| if (selection instanceof LTKInputData) { |
| stateChanged((LTKInputData) selection); |
| } |
| } |
| } |
| |
| @Override |
| public void inputChanged() { |
| } |
| |
| @Override |
| public void stateChanged(final LTKInputData state) { |
| if (!this.isLinkingWithEditor) { |
| return; |
| } |
| show(state, this.requestSync.newRequest(false)); |
| } |
| |
| private boolean show(final LTKInputData state, final @Nullable RequestKey request) { |
| if (request == null) { |
| return false; |
| } |
| try { |
| if (state.getInputElement().getModelTypeId() == RModel.R_TYPE_ID |
| || state.getInputElement() instanceof IRSourceUnit) { |
| final AstSelection astSelection= state.getAstSelection(); |
| final IRSourceUnit rSourceUnit= (IRSourceUnit) state.getInputElement(); |
| |
| final ISelection selection= state.getSelection(); |
| if (astSelection != null && selection instanceof ITextSelection) { |
| final ITextSelection textSelection= (ITextSelection) selection; |
| if (!(astSelection.getCovering() instanceof RAstNode) || textSelection.getLength() > 0) { |
| return false; |
| } |
| final RAstNode rNode= (RAstNode) astSelection.getCovering(); |
| RElementName name= null; |
| if (!rNode.hasChildren()) { |
| name= RHelpLtkUI.searchName(rNode, rNode, false); |
| } |
| if (name == null) { |
| name= RHelpLtkUI.searchNameOfFunction(rNode, |
| LTKSelectionUtils.toTextRegion(textSelection) ); |
| } |
| if (name == null) { |
| return false; |
| } |
| |
| if (this.requestSync.startRequest(request)) { |
| doShow1(request, rSourceUnit, rNode, name); |
| } |
| } |
| } |
| return false; |
| } |
| finally { |
| if (request.job == null) { |
| this.requestSync.deleteRequest(request); |
| } |
| } |
| } |
| |
| private boolean doShow1(final RequestKey request, |
| final @Nullable IRSourceUnit rSourceUnit, final RAstNode rNode, final RElementName name) { |
| final REnvHelp help; |
| Object helpObject= null; |
| try { |
| help= RHelpLtkUI.getEnvHelp(rSourceUnit); |
| } |
| catch (final StatusException e) { |
| final IStatus status= StatusUtils.convert(e.getStatus()); |
| showMessage(status, request); |
| return false; |
| } |
| try { |
| if (RElementName.isPackageFacetScopeType(name.getType())) { |
| helpObject= help.getPkgHelp(name.getSegmentName()); |
| } |
| else { |
| if (name.getScope() != null |
| && RElementName.isPackageFacetScopeType(name.getScope().getType()) ) { |
| final RPkgHelp pkgHelp= help.getPkgHelp(name.getScope().getSegmentName()); |
| if (pkgHelp != null) { |
| helpObject= pkgHelp.getPageForTopic(name.getSegmentName()); |
| } |
| } |
| if (helpObject == null && !isTopicShown(name.getSegmentName())) { |
| helpObject= RHelpLtkUI.searchTopicObject1(help, name.getSegmentName(), |
| rNode, rSourceUnit ); |
| if (helpObject == null && request.isValid()) { |
| doShow2(help.getREnv(), name.getSegmentName(), request); |
| return true; |
| } |
| } |
| } |
| } |
| catch (final CoreException e) { |
| } |
| finally { |
| help.unlock(); |
| } |
| if (helpObject != null) { |
| return showHelpObject(helpObject, request); |
| } |
| return false; |
| } |
| |
| private void doShow2(final REnv rEnv, final String topic, final RequestKey request) { |
| class BackgroundJob extends Job { |
| public BackgroundJob() { |
| super(String.format("Lookup R Help for '%1$s'", topic)); |
| setPriority(Job.SHORT); |
| setUser(request.isExplicite()); |
| } |
| @Override |
| protected IStatus run(final IProgressMonitor monitor) { |
| final ProgressMonitor m= StatusUtils.convert(monitor); |
| try { |
| final REnvHelp help= RCore.getRHelpManager().getHelp(rEnv); |
| if (help != null) { |
| Object helpObject= null; |
| try { |
| helpObject= RHelpLtkUI.searchTopicObject2(help, topic, m); |
| } |
| catch (final StatusException e) { |
| final IStatus status= StatusUtils.convert(e.getStatus()); |
| showMessage(status, request); |
| return status; |
| } |
| finally { |
| help.unlock(); |
| } |
| if (helpObject != null) { |
| showHelpObject(helpObject, request); |
| return Status.OK_STATUS; |
| } |
| if (request.isExplicite()) { |
| final IStatus status= new Status(IStatus.INFO, RUI.BUNDLE_ID, "No related help found."); |
| showMessage(status, request); |
| return status; |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| finally { |
| RHelpView.this.requestSync.deleteRequest(request); |
| } |
| } |
| @Override |
| protected void canceling() { |
| RHelpView.this.requestSync.deleteRequest(request); |
| } |
| } |
| |
| final Job job= new BackgroundJob(); |
| job.schedule(); |
| request.setAsync(job); |
| if (request.isExplicite()) { |
| final IProgressService progressService= getSite().getService(IProgressService.class); |
| if (progressService != null) { |
| progressService.showInDialog(getSite().getShell(), job); |
| } |
| } |
| } |
| |
| private boolean isTopicShown(final String topic) { |
| final IPage currentPage= getCurrentPage(); |
| if (currentPage instanceof RHelpViewPage) { |
| final Object helpObject= ((RHelpViewPage) currentPage).getHelpObject(); |
| if (helpObject instanceof RHelpPage) { |
| return ((RHelpPage) helpObject).getTopics().contains(topic); |
| } |
| } |
| return false; |
| } |
| |
| private boolean isLinkedValid() { |
| return (this.linkedEditor != null |
| && this.linkedEditor == this.linkedEditor.getSite().getPage().getActiveEditor()); |
| } |
| |
| |
| private boolean showHelpObject(final Object helpObject, final RequestKey request) { |
| final URI httpUrl= RCore.getRHelpHttpService().toHttpUrl(helpObject, |
| RHelpHttpService.BROWSE_TARGET ); |
| if (httpUrl == null) { |
| return false; |
| } |
| final String urlString= httpUrl.toString(); |
| UIAccess.getDisplay().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (isValid(request) && UIAccess.isOkToUse(getPageBook())) { |
| final BrowserSession session= getCurrentSession(); |
| if (session == null || !urlString.equals(session.getUrl())) { |
| openUrl(urlString, session); |
| } |
| } |
| } |
| }); |
| return true; |
| } |
| |
| |
| public boolean isValid(final RequestKey request) { |
| if (request.isExplicite()) { |
| return request.isValid(); |
| } |
| else { |
| return (isLinkedValid() && request.isValid()); |
| } |
| } |
| |
| private void showMessage(final IStatus status, final RequestKey request) { |
| if (!request.isExplicite()) { |
| return; |
| } |
| UIAccess.getDisplay().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (isValid(request) && UIAccess.isOkToUse(getPageBook())) { |
| getStatusManager().setMessage(status, 10); |
| } |
| } |
| }); |
| } |
| |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T> @Nullable T getAdapter(final Class<T> adapterType) { |
| if (adapterType == IShowInTarget.class) { |
| return (T) this; |
| } |
| return super.getAdapter(adapterType); |
| } |
| |
| @Override |
| public boolean show(final ShowInContext context) { |
| final ISelection selection= context.getSelection(); |
| if (selection instanceof LTKInputData) { |
| final LTKInputData state= (LTKInputData) selection; |
| return show(state, this.requestSync.newRequest(true)); |
| } |
| return false; |
| } |
| |
| } |