/*=============================================================================# # Copyright (c) 2010-2016 Stephan Wahlbrink (WalWare.de) and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of the Eclipse Public License v1.0 # which accompanies this distribution, and is available at # http://www.eclipse.org/legal/epl-v10.html # # Contributors: # Stephan Wahlbrink - initial API and implementation #=============================================================================*/ package de.walware.statet.r.internal.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.core.runtime.CoreException; 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.PlatformUI; import de.walware.jcommons.collections.ImCollections; import de.walware.jcommons.collections.ImList; import de.walware.ecommons.ltk.core.model.IModelElement; import de.walware.ecommons.ui.util.DialogUtil; import de.walware.ecommons.ui.util.LayoutUtil; import de.walware.ecommons.ui.util.UIAccess; import de.walware.statet.r.core.RCore; import de.walware.statet.r.core.model.RModel; import de.walware.statet.r.core.renv.IREnv; import de.walware.statet.r.core.rhelp.IREnvHelp; import de.walware.statet.r.core.rhelp.IRHelpKeyword; import de.walware.statet.r.core.rhelp.IRHelpManager; import de.walware.statet.r.core.rhelp.IRPkgHelp; import de.walware.statet.r.core.rhelp.RHelpSearchQuery; import de.walware.statet.r.core.rhelp.RHelpSearchQuery.Compiled; import de.walware.statet.r.internal.ui.REnvSelectionComposite; import de.walware.statet.r.internal.ui.RUIPlugin; import de.walware.statet.r.internal.ui.help.IRUIHelpContextIds; 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[] 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 fContainer; private IDialogSettings fDialogSettings; private final LinkedHashMap<String, RHelpSearchQuery> fQueryHistory= new LinkedHashMap<>(25); private Combo fSearchTextControl; private Button fTypeTopicsControl; private Button fTypeFieldsControl; private Button fTypeDocControl; private Button fFieldTitleControl; private Button fFieldConceptsControl; private Button fFieldAliasControl; private Combo fKeywordsInputControl; private Combo fPackagesInputControl; private REnvSelectionComposite fREnvControl; public RHelpSearchInputPage() { } private IDialogSettings getDialogSettings() { if (fDialogSettings == null) { fDialogSettings = DialogUtil.getDialogSettings(RUIPlugin.getDefault(), PAGE_ID); } return fDialogSettings; } @Override public void setContainer(final ISearchPageContainer container) { fContainer = container; } @Override public void createControl(final Composite parent) { final Composite composite = new Composite(parent, SWT.NONE); composite.setLayout(LayoutUtil.createTabGrid(1)); final Label label = new Label(composite, SWT.NONE); label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); label.setText("Search S&tring:"); fSearchTextControl = new Combo(composite, SWT.DROP_DOWN); fSearchTextControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); fSearchTextControl.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { final int selectionIdx; if (!fSearchTextControl.getListVisible() && (selectionIdx = fSearchTextControl.getSelectionIndex()) >= 0) { loadPattern(fQueryHistory.get(fSearchTextControl.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(); fREnvControl.setSetting(RCore.getREnvManager().getDefault()); Display.getCurrent().asyncExec(new Runnable() { @Override public void run() { if (UIAccess.isOkToUse(fSearchTextControl)) { fSearchTextControl.setFocus(); } updateState(); } }); } private Composite createSearchInGroup(final Composite parent) { final Composite composite = new Composite(parent, SWT.NONE); composite.setLayout(LayoutUtil.applyCompositeDefaults(new GridLayout(), 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 = LayoutUtil.applyGroupDefaults(new GridLayout(), 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)); fTypeTopicsControl = 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); fTypeFieldsControl = button; } { final Button button = new Button(group, SWT.RADIO); button.setText("Complete &Document"); button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); fTypeDocControl = 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 = LayoutUtil.applyGroupDefaults(new GridLayout(), 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); fFieldTitleControl = 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); fFieldAliasControl = 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); fFieldConceptsControl = button; } } fTypeFieldsControl.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { updateFieldsState(); } }); return composite; } private void updateFieldsState() { final boolean enable = fTypeFieldsControl.getSelection(); fFieldTitleControl.setEnabled(enable); fFieldAliasControl.setEnabled(enable); fFieldConceptsControl.setEnabled(enable); } private Composite createRestrictToGroup(final Composite parent) { final Group group = new Group(parent, SWT.NONE); group.setText("Restrict to:"); group.setLayout(LayoutUtil.applyGroupDefaults(new GridLayout(), 3)); { final Label label = new Label(group, SWT.NONE); label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); label.setText("&Keyword:"); } fKeywordsInputControl = new Combo(group, SWT.DROP_DOWN); fKeywordsInputControl.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<IRHelpKeyword.Group> keywords = null; IREnv rEnv = fREnvControl.getSelection(); final IRHelpManager rHelpManager = RCore.getRHelpManager(); if (rEnv != null) { final IREnvHelp help = rHelpManager.getHelp(rEnv); if (help != null) { try { keywords = help.getKeywords(); } finally { help.unlock(); } } } if (keywords == null) { rEnv = RCore.getREnvManager().getDefault(); final IREnvHelp help = rHelpManager.getHelp(rEnv); 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 IRHelpKeyword) { input.append(((IRHelpKeyword) selectionItem).getKeyword()); break; } } fKeywordsInputControl.setText(input.toString()); } } } }); } { final Label label = new Label(group, SWT.NONE); label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); label.setText("&Package:"); } fPackagesInputControl = new Combo(group, SWT.DROP_DOWN); fPackagesInputControl.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<IRPkgHelp> packages = null; IREnv rEnv = fREnvControl.getSelection(); final IRHelpManager rHelpManager = RCore.getRHelpManager(); if (rEnv != null) { final IREnvHelp help = rHelpManager.getHelp(rEnv); if (help != null) { try { packages = help.getPkgs(); } finally { help.unlock(); } } } if (packages == null) { rEnv = RCore.getREnvManager().getDefault(); final IREnvHelp help = rHelpManager.getHelp(rEnv); if (help != null) { try { packages = help.getPkgs(); } finally { help.unlock(); } } } if (packages != null) { final ImList<String> currentNames= toList(fPackagesInputControl.getText()); final List<IRPkgHelp> currentPkgs= new ArrayList<>(currentNames.size()); for (final String name : currentNames) { for (final IRPkgHelp 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 IRPkgHelp[] result = dialog.getResult(); if (result.length > 0) { Arrays.sort(result); for (final IRPkgHelp pkg : result) { input.append(pkg.getName()); input.append(", "); //$NON-NLS-1$ } fPackagesInputControl.setText(input.substring(0, input.length()-2)); } else { fPackagesInputControl.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(LayoutUtil.applyGroupDefaults(new GridLayout(), 1)); fREnvControl = new REnvSelectionComposite(group); fREnvControl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); fREnvControl.addChangeListener(new REnvSelectionComposite.ChangeListener() { @Override public void settingChanged(final REnvSelectionComposite source, final String oldValue, final String newValue, final IREnv newREnv) { updateState(); } }); return group; } private void updateState() { final IREnv rEnv = fREnvControl.getSelection(); if (rEnv == null || rEnv.getConfig() == null || !RCore.getRHelpManager().hasHelp(rEnv)) { fContainer.setPerformActionEnabled(false); return; } fContainer.setPerformActionEnabled(true); } private void loadSettings() { final IDialogSettings dialogSettings = getDialogSettings(); fKeywordsInputControl.setItems(notNull(dialogSettings.getArray("keywords"))); //$NON-NLS-1$ fPackagesInputControl.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()); fQueryHistory.put(hist.getSearchString(), hist); } else { break; } } if (!fQueryHistory.isEmpty()) { fSearchTextControl.setItems(texts.toArray(new String[texts.size()])); } } private void initSettings() { final ISelection selection = fContainer.getSelection(); if (selection instanceof ITextSelection) { fSearchTextControl.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.TYPE_ID && element.getElementName().getSegmentName() != null) { fSearchTextControl.setText(element.getElementName().getDisplayName()); return; } } } if (!fQueryHistory.isEmpty()) { loadPattern(fQueryHistory.values().iterator().next()); return; } } private void saveSettings(final RHelpSearchQuery query) { final IDialogSettings dialogSettings = getDialogSettings(); dialogSettings.put("keywords", DialogUtil.combineHistoryItems( //$NON-NLS-1$ fKeywordsInputControl.getItems(), prettyList(query.getKeywords()) )); dialogSettings.put("packages", DialogUtil.combineHistoryItems( //$NON-NLS-1$ fPackagesInputControl.getItems(), prettyList(query.getPackages()) )); fQueryHistory.remove(query.getSearchString()); int num = 0; saveQuery(query, dialogSettings.addNewSection("searchhist"+(num++))); //$NON-NLS-1$ for (final RHelpSearchQuery hist : fQueryHistory.values()) { saveQuery(hist, dialogSettings.addNewSection("searchhist"+(num++))); //$NON-NLS-1$ if (num >= DialogUtil.HISTORY_MAX) { break; } } } private void loadPattern(final RHelpSearchQuery query) { if (query == null) { return; } fSearchTextControl.setText(query.getSearchString()); fTypeTopicsControl.setSelection(false); fTypeFieldsControl.setSelection(false); fTypeDocControl.setSelection(false); switch (query.getSearchType()) { case RHelpSearchQuery.TOPIC_SEARCH: fTypeTopicsControl.setSelection(true); break; case RHelpSearchQuery.FIELD_SEARCH: fTypeFieldsControl.setSelection(true); break; case RHelpSearchQuery.DOC_SEARCH: fTypeDocControl.setSelection(true); break; } fFieldAliasControl.setSelection(query.getEnabledFields().contains(RHelpSearchQuery.TOPICS_FIELD)); fFieldTitleControl.setSelection(query.getEnabledFields().contains(RHelpSearchQuery.TITLE_FIELD)); fFieldConceptsControl.setSelection(query.getEnabledFields().contains(RHelpSearchQuery.CONCEPTS_FIELD)); updateFieldsState(); fKeywordsInputControl.setText(prettyList(query.getKeywords())); fPackagesInputControl.setText(prettyList(query.getPackages())); } private RHelpSearchQuery createPattern() { int type = 0; if (fTypeTopicsControl.getSelection()) { type = RHelpSearchQuery.TOPIC_SEARCH; } else if (fTypeFieldsControl.getSelection()) { type = RHelpSearchQuery.FIELD_SEARCH; } else if (fTypeDocControl.getSelection()) { type = RHelpSearchQuery.DOC_SEARCH; } final String text = fSearchTextControl.getText(); final List<String> fields= new ArrayList<>(3); if (fFieldAliasControl.getSelection()) { fields.add(RHelpSearchQuery.TOPICS_FIELD); } if (fFieldTitleControl.getSelection()) { fields.add(RHelpSearchQuery.TITLE_FIELD); } if (fFieldConceptsControl.getSelection()) { fields.add(RHelpSearchQuery.CONCEPTS_FIELD); } final ImList<String> keywords= toList(fKeywordsInputControl.getText()); final ImList<String> packages= toList(fPackagesInputControl.getText()); final IREnv renv = fREnvControl.getSelection(); return new RHelpSearchQuery(type, text, fields, keywords, packages, renv); } @Override public boolean performAction() { final RHelpSearchQuery query = createPattern(); if (query.getREnv() == null || query.getREnv().getConfig() == null) { return false; } try { final Compiled preparedQuery = query.compile(); // let us show errors directly saveSettings(query); final RHelpSearchUIQuery uiQuery = new RHelpSearchUIQuery(preparedQuery); NewSearchUI.runQueryInBackground(uiQuery); return true; } catch (final CoreException e) { setErrorMessage(e.getLocalizedMessage()); return false; } } }