blob: dcee07b7350858732bf917246ccc41b606dc482f [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.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.DialogPage;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.search.ui.ISearchPage;
import org.eclipse.search.ui.ISearchPageContainer;
import org.eclipse.search.ui.NewSearchUI;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.ecommons.ui.dialogs.DialogUtils;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.internal.r.ui.REnvSelectionComposite;
import org.eclipse.statet.internal.r.ui.RUIPlugin;
import org.eclipse.statet.internal.r.ui.help.IRUIHelpContextIds;
import org.eclipse.statet.ltk.model.core.elements.IModelElement;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.core.model.RModel;
import org.eclipse.statet.rhelp.core.REnvHelp;
import org.eclipse.statet.rhelp.core.REnvHelpConfiguration;
import org.eclipse.statet.rhelp.core.RHelpKeyword;
import org.eclipse.statet.rhelp.core.RHelpKeywordGroup;
import org.eclipse.statet.rhelp.core.RHelpManager;
import org.eclipse.statet.rhelp.core.RHelpSearchQuery;
import org.eclipse.statet.rhelp.core.RPkgHelp;
import org.eclipse.statet.rj.renv.core.REnv;
@NonNullByDefault
public class RHelpSearchInputPage extends DialogPage implements ISearchPage {
private static final String PAGE_ID= "RHelpSearchPage"; //$NON-NLS-1$
private static String prettyList(final List<String> list) {
if (list.isEmpty()) {
return ""; //$NON-NLS-1$
}
final StringBuilder sb= new StringBuilder(list.size() * 10);
for (final String s : list) {
sb.append(s);
sb.append(", "); //$NON-NLS-1$
}
return sb.substring(0, sb.length()-2);
}
private static final Pattern SEPARATOR_PATTERN= Pattern.compile("[\\,\\;\\s]+"); //$NON-NLS-1$
private static ImList<String> toList(final String input) {
final String[] array= SEPARATOR_PATTERN.split(input);
if (array.length == 1 && array[0].isEmpty()) {
return ImCollections.emptyList();
}
return ImCollections.newList(array);
}
private static String[] notNull(final String @Nullable [] array) {
return (array != null) ? array : new String[0];
}
private RHelpSearchQuery loadQuery(final IDialogSettings settings) {
final int type= settings.getInt("type"); //$NON-NLS-1$
final String text= settings.get("text"); //$NON-NLS-1$
final ImList<String> fields= ImCollections.newList(settings.getArray("fields")); //$NON-NLS-1$
final ImList<String> keywords= ImCollections.newList(settings.getArray("keywords")); //$NON-NLS-1$
final ImList<String> packages= ImCollections.newList(settings.getArray("packages")); //$NON-NLS-1$
return new RHelpSearchQuery(type, text, fields, keywords, packages, null);
}
private void saveQuery(final RHelpSearchQuery query, final IDialogSettings settings) {
settings.put("type", query.getSearchType()); //$NON-NLS-1$
settings.put("text", query.getSearchString()); //$NON-NLS-1$
final ImList<String> fields= query.getEnabledFields();
settings.put("fields", fields.toArray(new String[fields.size()])); //$NON-NLS-1$
final ImList<String> keywords= query.getKeywords();
settings.put("keywords", keywords.toArray(new String[keywords.size()])); //$NON-NLS-1$
final ImList<String> packages= query.getPackages();
settings.put("packages", packages.toArray(new String[packages.size()])); //$NON-NLS-1$
}
private ISearchPageContainer container;
private IDialogSettings dialogSettings;
private final LinkedHashMap<String, RHelpSearchQuery> queryHistory= new LinkedHashMap<>(25);
private Combo searchTextControl;
private Button typeTopicsControl;
private Button typeFieldsControl;
private Button typeDocControl;
private Button fieldTitleControl;
private Button fieldConceptsControl;
private Button fieldAliasControl;
private Combo keywordsInputControl;
private Combo packagesInputControl;
private REnvSelectionComposite rEnvControl;
private final RHelpManager rHelpManager;
public RHelpSearchInputPage() {
this.rHelpManager= RCore.getRHelpManager();
}
private IDialogSettings getDialogSettings() {
if (this.dialogSettings == null) {
this.dialogSettings= DialogUtils.getDialogSettings(RUIPlugin.getInstance(), PAGE_ID);
}
return this.dialogSettings;
}
@Override
public void setContainer(final ISearchPageContainer container) {
this.container= container;
}
@Override
public void createControl(final Composite parent) {
final Composite composite= new Composite(parent, SWT.NONE);
composite.setLayout(LayoutUtils.newTabGrid(1));
final Label label= new Label(composite, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
label.setText("Search S&tring:");
this.searchTextControl= new Combo(composite, SWT.DROP_DOWN);
this.searchTextControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.searchTextControl.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final int selectionIdx;
if (!RHelpSearchInputPage.this.searchTextControl.getListVisible()
&& (selectionIdx= RHelpSearchInputPage.this.searchTextControl.getSelectionIndex()) >= 0) {
loadPattern(RHelpSearchInputPage.this.queryHistory.get(RHelpSearchInputPage.this.searchTextControl.getItem(selectionIdx)));
}
}
});
final Composite searchInGroup= createSearchInGroup(composite);
searchInGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
final Composite restrictToGroup= createRestrictToGroup(composite);
restrictToGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
final Composite scopeGroup= createScopeGroup(composite);
scopeGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
setControl(composite);
PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, IRUIHelpContextIds.R_HELP_SEARCH_PAGE);
loadSettings();
initSettings();
Display.getCurrent().asyncExec(new Runnable() {
@Override
public void run() {
if (UIAccess.isOkToUse(RHelpSearchInputPage.this.searchTextControl)) {
RHelpSearchInputPage.this.searchTextControl.setFocus();
}
updateState();
}
});
}
private Composite createSearchInGroup(final Composite parent) {
final Composite composite= new Composite(parent, SWT.NONE);
composite.setLayout(LayoutUtils.newCompositeGrid(2));
{ final Group group= new Group(composite, SWT.NONE);
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
group.setText("Search in:");
final GridLayout layout= LayoutUtils.newGroupGrid(1);
group.setLayout(layout);
{ final Button button= new Button(group, SWT.RADIO);
button.setText("&Topics/Alias (strict)");
button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.typeTopicsControl= button;
}
{ final Button button= new Button(group, SWT.RADIO);
button.setText("Selected &Fields");
button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
button.setSelection(true);
this.typeFieldsControl= button;
}
{ final Button button= new Button(group, SWT.RADIO);
button.setText("Complete &Document");
button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.typeDocControl= button;
}
}
{ final Group group= new Group(composite, SWT.NONE);
group.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, false));
group.setText("Fields:");
final GridLayout layout= LayoutUtils.newGroupGrid(1);
group.setLayout(layout);
{ final Button button= new Button(group, SWT.CHECK);
button.setText("T&itle");
button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
button.setSelection(true);
this.fieldTitleControl= button;
}
{ final Button button= new Button(group, SWT.CHECK);
button.setText("Topics/&Alias");
button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
button.setSelection(true);
this.fieldAliasControl= button;
}
{ final Button button= new Button(group, SWT.CHECK);
button.setText("C&oncepts");
button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
button.setSelection(true);
this.fieldConceptsControl= button;
}
}
this.typeFieldsControl.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
updateFieldsState();
}
});
return composite;
}
private void updateFieldsState() {
final boolean enable= this.typeFieldsControl.getSelection();
this.fieldTitleControl.setEnabled(enable);
this.fieldAliasControl.setEnabled(enable);
this.fieldConceptsControl.setEnabled(enable);
}
private @Nullable REnvHelp getREnvHelp(final boolean fallbackDefault) {
{ final REnv rEnv= RHelpSearchInputPage.this.rEnvControl.getSelection();
if (rEnv != null) {
final REnvHelp help= RHelpSearchInputPage.this.rHelpManager.getHelp(rEnv);
if (help != null) {
return help;
}
}
}
if (fallbackDefault) {
final REnv rEnv= RCore.getREnvManager().getDefault();
if (rEnv != null) {
final REnvHelp help= RHelpSearchInputPage.this.rHelpManager.getHelp(rEnv);
if (help != null) {
return help;
}
}
}
return null;
}
private Composite createRestrictToGroup(final Composite parent) {
final Group group= new Group(parent, SWT.NONE);
group.setText("Restrict to:");
group.setLayout(LayoutUtils.newGroupGrid(3));
{ final Label label= new Label(group, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
label.setText("&Keyword:");
}
this.keywordsInputControl= new Combo(group, SWT.DROP_DOWN);
this.keywordsInputControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
{ final Button button= new Button(group, SWT.PUSH);
button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
button.setText("Select...");
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
List<RHelpKeywordGroup> keywords= null;
{ final REnvHelp help= getREnvHelp(true);
if (help != null) {
try {
keywords= help.getKeywords();
}
finally {
help.unlock();
}
}
}
if (keywords != null) {
final KeywordSelectionDialog dialog= new KeywordSelectionDialog(
getControl().getShell(), keywords);
if (dialog.open() == Dialog.OK) {
final StringBuilder input= new StringBuilder();
final Object[] result= dialog.getResult();
for (final Object selectionItem : result) {
if (selectionItem instanceof RHelpKeyword) {
input.append(((RHelpKeyword) selectionItem).getKeyword());
break;
}
}
RHelpSearchInputPage.this.keywordsInputControl.setText(input.toString());
}
}
}
});
}
{ final Label label= new Label(group, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
label.setText("&Package:");
}
this.packagesInputControl= new Combo(group, SWT.DROP_DOWN);
this.packagesInputControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
{ final Button button= new Button(group, SWT.PUSH);
button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
button.setText("Select...");
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
List<RPkgHelp> packages= null;
{ final REnvHelp help= getREnvHelp(true);
if (help != null) {
try {
packages= help.getPkgs();
}
finally {
help.unlock();
}
}
}
if (packages != null) {
final ImList<String> currentNames= toList(RHelpSearchInputPage.this.packagesInputControl.getText());
final List<RPkgHelp> currentPkgs= new ArrayList<>(currentNames.size());
for (final String name : currentNames) {
for (final RPkgHelp pkg : packages) {
if (pkg.getName().equals(name)) {
currentPkgs.add(pkg);
}
}
}
final PackageSelectionDialog dialog= new PackageSelectionDialog(
getControl().getShell(), packages, currentPkgs);
if (dialog.open() == Dialog.OK) {
final StringBuilder input= new StringBuilder();
final RPkgHelp[] result= dialog.getResult();
if (result.length > 0) {
Arrays.sort(result);
for (final RPkgHelp pkg : result) {
input.append(pkg.getName());
input.append(", "); //$NON-NLS-1$
}
RHelpSearchInputPage.this.packagesInputControl.setText(input.substring(0, input.length()-2));
}
else {
RHelpSearchInputPage.this.packagesInputControl.setText(""); //$NON-NLS-1$
}
}
}
}
});
}
return group;
}
private Composite createScopeGroup(final Composite parent) {
final Group group= new Group(parent, SWT.NONE);
group.setText("Scope:");
group.setLayout(LayoutUtils.newGroupGrid(1));
this.rEnvControl= new REnvSelectionComposite(group);
this.rEnvControl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
this.rEnvControl.addChangeListener(new REnvSelectionComposite.ChangeListener() {
@Override
public void settingChanged(final REnvSelectionComposite source, final String oldValue,
final String newValue, final REnv newREnv) {
updateState();
}
});
return group;
}
private void updateState() {
final REnv rEnv= this.rEnvControl.getSelection();
if (rEnv == null || rEnv.get(REnvHelpConfiguration.class) == null
|| !this.rHelpManager.hasHelp(rEnv) ) {
this.container.setPerformActionEnabled(false);
return;
}
this.container.setPerformActionEnabled(true);
}
private void loadSettings() {
final IDialogSettings dialogSettings= getDialogSettings();
this.keywordsInputControl.setItems(notNull(dialogSettings.getArray("keywords"))); //$NON-NLS-1$
this.packagesInputControl.setItems(notNull(dialogSettings.getArray("packages"))); //$NON-NLS-1$
int num= 0;
final List<String> texts= new ArrayList<>();
while (true) {
final IDialogSettings section= dialogSettings.getSection("searchhist" + (num++)); //$NON-NLS-1$
if (section != null) {
final RHelpSearchQuery hist= loadQuery(section);
texts.add(hist.getSearchString());
this.queryHistory.put(hist.getSearchString(), hist);
}
else {
break;
}
}
if (!this.queryHistory.isEmpty()) {
this.searchTextControl.setItems(texts.toArray(new String[texts.size()]));
}
}
private void initREnv(final @Nullable IWorkbenchPart part) {
final REnv defaultREnv= RCore.getREnvManager().getDefault();
this.rEnvControl.setSetting(defaultREnv);
if (part == null) {
return;
}
final REnv rEnv= part.getAdapter(REnv.class);
if (rEnv == null) {
return;
}
if (rEnv != defaultREnv && rEnv.resolve() != defaultREnv.resolve()
&& this.rHelpManager.hasHelp(rEnv) ) {
this.rEnvControl.setSetting(rEnv);
}
}
private void initSettings() {
final IWorkbenchPart part= UIAccess.getActiveWorkbenchPart(true);
initREnv(part);
final ISelection selection= this.container.getSelection();
if (selection instanceof ITextSelection) {
this.searchTextControl.setText(((ITextSelection) selection).getText());
return;
}
if (selection instanceof IStructuredSelection) {
final Object firstElement= ((IStructuredSelection) selection).getFirstElement();
if (firstElement instanceof IModelElement) {
final IModelElement element= (IModelElement) firstElement;
if (element.getModelTypeId() == RModel.R_TYPE_ID
&& element.getElementName().getSegmentName() != null) {
this.searchTextControl.setText(element.getElementName().getDisplayName());
return;
}
}
}
if (!this.queryHistory.isEmpty()) {
loadPattern(this.queryHistory.values().iterator().next());
return;
}
}
private void saveSettings(final RHelpSearchQuery query) {
final IDialogSettings dialogSettings= getDialogSettings();
dialogSettings.put("keywords", DialogUtils.combineHistoryItems( //$NON-NLS-1$
this.keywordsInputControl.getItems(),
prettyList(query.getKeywords()) ));
dialogSettings.put("packages", DialogUtils.combineHistoryItems( //$NON-NLS-1$
this.packagesInputControl.getItems(),
prettyList(query.getPackages()) ));
this.queryHistory.remove(query.getSearchString());
int num= 0;
saveQuery(query, dialogSettings.addNewSection("searchhist"+(num++))); //$NON-NLS-1$
for (final RHelpSearchQuery hist : this.queryHistory.values()) {
saveQuery(hist, dialogSettings.addNewSection("searchhist"+(num++))); //$NON-NLS-1$
if (num >= DialogUtils.HISTORY_MAX) {
break;
}
}
}
private void loadPattern(final @Nullable RHelpSearchQuery query) {
if (query == null) {
return;
}
this.searchTextControl.setText(query.getSearchString());
this.typeTopicsControl.setSelection(false);
this.typeFieldsControl.setSelection(false);
this.typeDocControl.setSelection(false);
switch (query.getSearchType()) {
case RHelpSearchQuery.TOPIC_SEARCH:
this.typeTopicsControl.setSelection(true);
break;
case RHelpSearchQuery.FIELD_SEARCH:
this.typeFieldsControl.setSelection(true);
break;
case RHelpSearchQuery.DOC_SEARCH:
this.typeDocControl.setSelection(true);
break;
}
this.fieldAliasControl.setSelection(query.getEnabledFields().contains(RHelpSearchQuery.TOPICS_FIELD));
this.fieldTitleControl.setSelection(query.getEnabledFields().contains(RHelpSearchQuery.TITLE_FIELD));
this.fieldConceptsControl.setSelection(query.getEnabledFields().contains(RHelpSearchQuery.CONCEPTS_FIELD));
updateFieldsState();
this.keywordsInputControl.setText(prettyList(query.getKeywords()));
this.packagesInputControl.setText(prettyList(query.getPackages()));
}
private RHelpSearchQuery createPattern() {
int type= 0;
if (this.typeTopicsControl.getSelection()) {
type= RHelpSearchQuery.TOPIC_SEARCH;
}
else if (this.typeFieldsControl.getSelection()) {
type= RHelpSearchQuery.FIELD_SEARCH;
}
else if (this.typeDocControl.getSelection()) {
type= RHelpSearchQuery.DOC_SEARCH;
}
final String text= this.searchTextControl.getText();
final List<String> fields= new ArrayList<>(3);
if (this.fieldAliasControl.getSelection()) {
fields.add(RHelpSearchQuery.TOPICS_FIELD);
}
if (this.fieldTitleControl.getSelection()) {
fields.add(RHelpSearchQuery.TITLE_FIELD);
}
if (this.fieldConceptsControl.getSelection()) {
fields.add(RHelpSearchQuery.CONCEPTS_FIELD);
}
final ImList<String> keywords= toList(this.keywordsInputControl.getText());
final ImList<String> packages= toList(this.packagesInputControl.getText());
final REnv renv= this.rEnvControl.getSelection();
return new RHelpSearchQuery(type, text, fields, keywords, packages, renv);
}
@Override
public boolean performAction() {
final RHelpSearchQuery query= createPattern();
try {
query.validate(); // let us show errors directly
saveSettings(query);
final RHelpSearchUIQuery uiQuery= new RHelpSearchUIQuery(query);
NewSearchUI.runQueryInBackground(uiQuery);
return true;
}
catch (final StatusException e) {
setErrorMessage(e.getLocalizedMessage());
return false;
}
}
}