blob: 22758566f4f6410973cb530bf05d7691c0a5ed4a [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2010, 2019 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.URI;
import java.net.URISyntaxException;
import java.util.Iterator;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jetty.util.UrlEncoded;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.layout.TreeColumnLayout;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.search.ui.IContextMenuConstants;
import org.eclipse.search.ui.text.Match;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils;
import org.eclipse.statet.ecommons.ui.SharedUIResources;
import org.eclipse.statet.ecommons.ui.actions.SimpleContributionItem;
import org.eclipse.statet.ecommons.ui.mpbv.BrowserSession;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.ecommons.ui.workbench.DecoratingStyledLabelProvider;
import org.eclipse.statet.ecommons.workbench.search.ui.ExtTextSearchResultPage;
import org.eclipse.statet.ecommons.workbench.search.ui.TextSearchLabelUtil;
import org.eclipse.statet.ecommons.workbench.search.ui.TextSearchResultContentProvider;
import org.eclipse.statet.ecommons.workbench.search.ui.TextSearchResultMatchTableContentProvider;
import org.eclipse.statet.ecommons.workbench.search.ui.TextSearchResultTreeContentProvider;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.ui.RUI;
import org.eclipse.statet.rhelp.core.RHelpSearchMatch;
import org.eclipse.statet.rhelp.core.RHelpSearchMatch.MatchFragment;
import org.eclipse.statet.rhelp.core.RHelpSearchQuery;
import org.eclipse.statet.rhelp.core.RPkgHelp;
import org.eclipse.statet.rhelp.core.http.RHelpHttpService;
public class RHelpSearchResultPage extends ExtTextSearchResultPage<RPkgHelp, RHelpSearchUIMatch> {
private static final ViewerComparator ALPHA_SORTER= new ViewerComparator() {
@Override
public int compare(final Viewer viewer, final Object e1, final Object e2) {
return ((RHelpSearchUIMatch) e1).compareTo((RHelpSearchUIMatch) e2);
}
};
private static final ViewerComparator SCORE_SORTER= new ViewerComparator() {
@Override
public int compare(final Viewer viewer, final Object e1, final Object e2) {
final RHelpSearchMatch match1= ((RHelpSearchUIMatch) e1).getRHelpMatch();
final RHelpSearchMatch match2= ((RHelpSearchUIMatch) e2).getRHelpMatch();
return (int) ((match2.getScore() - match1.getScore()) * 1e8);
}
};
private static class MatchLabelProvider extends StyledCellLabelProvider {
public MatchLabelProvider() {
}
@Override
public void update(final ViewerCell cell) {
final Object element= cell.getElement();
final StyledString text= new StyledString();
if (element instanceof RHelpSearchUIMatch) {
final RHelpSearchMatch match= ((RHelpSearchUIMatch) element).getRHelpMatch();
final MatchFragment[] fragments= match.getBestFragments();
if (fragments != null && fragments.length > 0) {
{ final String fieldLabel= fragments[0].getFieldLabel();
if (fieldLabel != null) {
text.append(fieldLabel, StyledString.QUALIFIER_STYLER);
text.append(": ", StyledString.QUALIFIER_STYLER); //$NON-NLS-1$
}
}
if (fragments[0].getField() == RHelpSearchQuery.TOPICS_FIELD) {
RHelpLabelProvider.append(text, fragments[0]);
for (int i= 1; i < fragments.length; i++) {
if (fragments[i].getField() == RHelpSearchQuery.TOPICS_FIELD) {
text.append(", "); //$NON-NLS-1$
RHelpLabelProvider.append(text, fragments[i]);
}
}
}
else {
RHelpLabelProvider.append(text, fragments[0]);
}
}
}
cell.setText(text.getString());
cell.setStyleRanges(text.getStyleRanges());
super.update(cell);
}
@Override
protected StyleRange prepareStyleRange(StyleRange styleRange, final boolean applyColors) {
if (!applyColors && styleRange.background != null) {
styleRange= super.prepareStyleRange(styleRange, applyColors);
styleRange.borderStyle= SWT.BORDER_DOT;
return styleRange;
}
return super.prepareStyleRange(styleRange, applyColors);
}
}
private static Object getRelevantElement(final TreePath treePath) {
if (treePath.getSegmentCount() > 2
&& treePath.getLastSegment() instanceof RHelpSearchMatch.MatchFragment) {
return treePath.getSegment(treePath.getSegmentCount()-2);
}
return treePath.getLastSegment();
}
private static class TreeContentProvider extends TextSearchResultTreeContentProvider<RPkgHelp, RHelpSearchUIMatch> {
public TreeContentProvider(final RHelpSearchResultPage page, final TreeViewer viewer) {
super(page, viewer);
}
@Override
public boolean hasChildren(final Object element) {
if (element instanceof RPkgHelp) {
return true;
}
if (element instanceof RHelpSearchUIMatch) {
final MatchFragment[] fragments= ((RHelpSearchUIMatch) element).getRHelpMatch().getBestFragments();
return (fragments != null && fragments.length > 0);
}
return false;
}
@Override
public Object[] getChildren(final Object parentElement) {
if (parentElement instanceof RPkgHelp) {
return super.getChildren(parentElement);
}
if (parentElement instanceof RHelpSearchUIMatch) {
return ((RHelpSearchUIMatch) parentElement).getRHelpMatch().getBestFragments();
}
return NO_ELEMENTS;
}
}
private class OpenHandler extends SimpleContributionItem {
public OpenHandler() {
super("Open (New Page)", "O");
}
@Override
protected void execute() throws ExecutionException {
final StructuredViewer viewer= getViewer();
if (UIAccess.isOkToUse(viewer)) {
final IStructuredSelection selection= (IStructuredSelection) viewer.getSelection();
if (selection instanceof ITreeSelection) {
final TreePath[] paths= ((ITreeSelection) selection).getPaths();
for (int i= 0; i < paths.length; i++) {
open(getRelevantElement(paths[i]), true, true);
}
}
else {
for (final Iterator<?> iter= selection.iterator(); iter.hasNext(); ) {
open(iter.next(), true, true);
}
}
}
}
}
private BrowserSession reusedSession;
private String reusedSessionUrl;
private IWorkbenchPartReference reusedSessionView;
private ViewerComparator currentSorter= ALPHA_SORTER;
private SimpleContributionItem sortByName;
private SimpleContributionItem sortByScore;
public RHelpSearchResultPage() {
super(RHelpSearchResult.COMPARATOR);
}
@Override
public void init(final IPageSite pageSite) {
super.init(pageSite);
initActions();
}
private void initActions() {
this.sortByName= new SimpleContributionItem(
"Sort by Name", null,
SharedUIResources.getImages().getDescriptor(SharedUIResources.LOCTOOL_SORT_ALPHA_IMAGE_ID), null,
SimpleContributionItem.STYLE_CHECK ) {
@Override
protected void execute() throws ExecutionException {
RHelpSearchResultPage.this.currentSorter= ALPHA_SORTER;
updateSorter();
}
};
this.sortByScore= new SimpleContributionItem(
"Sort by Relevance", null,
SharedUIResources.getImages().getDescriptor(SharedUIResources.LOCTOOL_SORT_SCORE_IMAGE_ID), null,
SimpleContributionItem.STYLE_CHECK ) {
@Override
protected void execute() throws ExecutionException {
RHelpSearchResultPage.this.currentSorter= SCORE_SORTER;
updateSorter();
}
};
}
@Override
public void restoreState(final IMemento memento) {
super.restoreState(memento);
if (memento != null) {
final String sortValue= memento.getString("sort.by"); //$NON-NLS-1$
if (sortValue != null && sortValue.equals("score")) { //$NON-NLS-1$
this.currentSorter= SCORE_SORTER;
}
else {
this.currentSorter= ALPHA_SORTER;
}
updateSorter();
}
}
@Override
public void saveState(final IMemento memento) {
super.saveState(memento);
final String sortValue= (this.currentSorter == SCORE_SORTER) ? "score" : ""; //$NON-NLS-1$ //$NON-NLS-2$
memento.putString("sort.by", sortValue); //$NON-NLS-1$
}
@Override
protected void configureTableViewer(final TableViewer viewer) {
super.configureTableViewer(viewer);
final TableColumnLayout layout= new TableColumnLayout();
viewer.getControl().getParent().setLayout(layout);
viewer.getTable().setHeaderVisible(true);
{ final TableViewerColumn column= new TableViewerColumn(viewer, SWT.LEFT);
column.getColumn().setText("Page");
layout.setColumnData(column.getColumn(), new ColumnWeightData(1));
column.setLabelProvider(new DecoratingStyledLabelProvider(new RHelpLabelProvider(),
TextSearchLabelUtil.DEFAULT_SEARCH_LABEL_PROPERTIES));
}
{ final TableViewerColumn column= new TableViewerColumn(viewer, SWT.LEFT);
column.getColumn().setText("Package");
layout.setColumnData(column.getColumn(), new ColumnPixelData(
new PixelConverter(JFaceResources.getDialogFont()).convertWidthInCharsToPixels(10),
true, true ));
column.setLabelProvider(new CellLabelProvider() {
@Override
public void update(final ViewerCell cell) {
final Object element= cell.getElement();
String text= ""; //$NON-NLS-1$
if (element instanceof RHelpSearchUIMatch) {
final RHelpSearchMatch match= ((RHelpSearchUIMatch) element).getRHelpMatch();
text= match.getPage().getPackage().getName();
}
cell.setText(text);
}
});
}
{ final TableViewerColumn column= new TableViewerColumn(viewer, SWT.LEFT);
column.getColumn().setText("Best Match");
layout.setColumnData(column.getColumn(), new ColumnWeightData(1));
column.setLabelProvider(new MatchLabelProvider());
}
ColumnViewerToolTipSupport.enableFor(viewer);
updateSorter();
}
@Override
protected void configureTreeViewer(final TreeViewer viewer) {
super.configureTreeViewer(viewer);
final TreeColumnLayout layout= new TreeColumnLayout();
viewer.getControl().getParent().setLayout(layout);
final TreeViewerColumn column1= new TreeViewerColumn(viewer, SWT.LEFT);
column1.getColumn().setText("Package / Page / Match");
layout.setColumnData(column1.getColumn(), new ColumnWeightData(1));
column1.setLabelProvider(new RHelpLabelProvider());
ColumnViewerToolTipSupport.enableFor(viewer);
}
@Override
protected TextSearchResultTreeContentProvider<RPkgHelp, RHelpSearchUIMatch> createTreeContentProvider(
final TreeViewer viewer) {
return new TreeContentProvider(this, viewer);
}
@Override
protected TextSearchResultContentProvider<RPkgHelp, RHelpSearchUIMatch, TableViewer> createTableContentProvider(
final TableViewer viewer) {
return new TextSearchResultMatchTableContentProvider<>(this, viewer);
}
@Override
protected void fillToolbar(final IToolBarManager tbm) {
super.fillToolbar(tbm);
if (getLayout() == FLAG_LAYOUT_FLAT) {
updateSorter();
tbm.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, new Separator(".sorting")); //$NON-NLS-1$
tbm.appendToGroup(".sorting", this.sortByName); //$NON-NLS-1$
tbm.appendToGroup(".sorting", this.sortByScore); //$NON-NLS-1$
}
}
@Override
protected void fillContextMenu(final IMenuManager mgr) {
mgr.appendToGroup(IContextMenuConstants.GROUP_OPEN, new OpenHandler());
super.fillContextMenu(mgr);
}
private void updateSorter() {
final ViewerComparator sorter= this.currentSorter;
this.sortByName.setChecked(sorter == ALPHA_SORTER);
this.sortByScore.setChecked(sorter == SCORE_SORTER);
if (getLayout() == FLAG_LAYOUT_FLAT) {
final StructuredViewer viewer= getViewer();
if (UIAccess.isOkToUse(viewer)) {
viewer.setComparator(sorter);
}
}
}
@Override
public RHelpSearchResult getInput() {
return (RHelpSearchResult) super.getInput();
}
@Override
protected void handleOpen(final OpenEvent event) {
final IStructuredSelection selection= (IStructuredSelection) event.getSelection();
if (selection.size() == 1) {
Object element= null;
if (selection instanceof ITreeSelection) {
element= getRelevantElement(((ITreeSelection) selection).getPaths()[0]);
}
if (element == null) {
element= selection.getFirstElement();
}
open(element, true, false);
}
}
@Override
protected void showMatch(final Match match, final int currentOffset, final int currentLength,
final boolean activate)
throws PartInitException {
openPage((RHelpSearchUIMatch) match, activate, false);
}
protected void open(final Object element, final boolean activate, final boolean newPage) {
try {
if (element instanceof RHelpSearchUIMatch) {
openPage((RHelpSearchUIMatch) element, activate, newPage);
}
if (element instanceof RPkgHelp) {
openPackage((RPkgHelp) element, activate, newPage);
}
}
catch (final PartInitException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, RUI.BUNDLE_ID, -1,
"An error occurred when trying to open the R help page (element= " + element + ").", e));
}
}
protected void openPage(final RHelpSearchUIMatch match, final boolean activate, final boolean newPage)
throws PartInitException {
final RHelpSearchMatch rMatch= match.getRHelpMatch();
URI url= RCore.getRHelpHttpService().getPageHttpUrl(rMatch.getPage(),
RHelpHttpService.BROWSE_TARGET );
final RHelpSearchQuery rHelpQuery= getInput().getQuery().getRHelpQuery();
if (rHelpQuery.getSearchType() == RHelpSearchQuery.DOC_SEARCH
&& rHelpQuery.getSearchString().length() > 0) {
try {
url= new URI(url.toString() + "?qs=" + //$NON-NLS-1$
UrlEncoded.encodeString(rHelpQuery.getSearchString()) );
} catch (final URISyntaxException ignore) {}
}
doOpen(url, activate, newPage);
}
protected void openPackage(final RPkgHelp packageHelp, final boolean activate, final boolean newPage)
throws PartInitException {
final URI url= RCore.getRHelpHttpService().getPackageHttpUrl(packageHelp,
RHelpHttpService.BROWSE_TARGET );
doOpen(url, activate, newPage);
}
private void doOpen(final URI url, final boolean activate, final boolean newPage)
throws PartInitException {
RHelpView view= null;
final IWorkbenchPartReference reference= getSite().getPage().getReference(view);
if ( !newPage && this.reusedSession != null
&& !this.reusedSession.getUrl().equals(this.reusedSessionUrl) ) {
this.reusedSession= null;
}
else if ( this.reusedSession != null
&& this.reusedSessionView != null) {
view= (RHelpView) this.reusedSessionView.getPart(false);
}
if (view != null) {
if (activate) {
view.getSite().getPage().activate(view);
}
else {
view.getSite().getPage().bringToTop(view);
}
}
else {
view= (RHelpView) getSite().getPage().showView(RUI.R_HELP_VIEW_ID, null,
activate ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_VISIBLE );
}
final String urlString= url.toASCIIString();
if (showOpenPage(view, urlString)) {
return;
}
if (!newPage && PreferenceUtils.getInstancePrefs().getPreferenceValue(
RHelpPreferences.SEARCH_REUSE_PAGE_ENABLED_PREF )) {
if (this.reusedSession != null && !view.canOpen(this.reusedSession)) {
this.reusedSession= null;
}
this.reusedSession= view.openUrl(urlString, this.reusedSession);
}
else {
this.reusedSession= view.openUrl(urlString, null);
}
this.reusedSessionUrl= urlString;
this.reusedSessionView= reference;
}
private boolean showOpenPage(final RHelpView view, final String url) {
final BrowserSession session= BrowserSession.findSessionByUrl(view.getSessions(), url);
if (session != null) {
view.showPage(session);
return true;
}
return false;
}
}