/** * Copyright (c) 20015 by Brainwy Software Ltda. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.shared_ui.search; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.viewers.IContentProvider; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.OpenEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.search.ui.IContextMenuConstants; import org.eclipse.search.ui.ISearchQuery; import org.eclipse.search.ui.ISearchResult; import org.eclipse.search.ui.ISearchResultViewPart; import org.eclipse.search.ui.text.AbstractTextSearchResult; import org.eclipse.search.ui.text.AbstractTextSearchViewPage; import org.eclipse.search.ui.text.Match; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.IMemento; import org.eclipse.ui.IPageLayout; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PartInitException; import org.eclipse.ui.actions.ActionContext; import org.eclipse.ui.actions.ActionGroup; import org.eclipse.ui.forms.events.HyperlinkAdapter; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.part.IShowInSource; import org.eclipse.ui.part.IShowInTargetList; import org.eclipse.ui.part.ResourceTransfer; import org.eclipse.ui.part.ShowInContext; import org.eclipse.ui.progress.WorkbenchJob; import org.eclipse.ui.views.navigator.NavigatorDragAdapter; import org.python.pydev.shared_core.callbacks.ICallback; import org.python.pydev.shared_core.log.Log; import org.python.pydev.shared_core.structure.TreeNode; import org.python.pydev.shared_core.structure.TreeNodeContentProvider; import org.python.pydev.shared_core.structure.Tuple; import org.python.pydev.shared_ui.dialogs.DialogHelpers; import org.python.pydev.shared_ui.search.replace.ReplaceAction; import org.python.pydev.shared_ui.swt.StyledLink.MultiStyledLink; public abstract class AbstractSearchIndexResultPage extends AbstractTextSearchViewPage { protected ISearchIndexContentProvider fContentProvider; protected Text filterText; protected WorkbenchJob refreshJob; protected GroupByAction[] fGroupByActions; protected ActionGroup fActionGroup; public AbstractSearchIndexResultPage() { super(FLAG_LAYOUT_TREE); } public static class DecoratorIgnoringViewerSorter extends ViewerComparator { private final ILabelProvider fLabelProvider; public DecoratorIgnoringViewerSorter(ILabelProvider labelProvider) { fLabelProvider = labelProvider; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.ViewerComparator#category(java.lang.Object) */ @Override public int category(Object element) { if (element instanceof IContainer) { return 1; } return 2; } @Override public int compare(Viewer viewer, Object e1, Object e2) { int cat1 = category(e1); int cat2 = category(e2); if (cat1 != cat2) { return cat1 - cat2; } if (e1 instanceof ICustomLineElement && e2 instanceof ICustomLineElement) { ICustomLineElement m1 = (ICustomLineElement) e1; ICustomLineElement m2 = (ICustomLineElement) e2; return m1.getOffset() - m2.getOffset(); } String name1 = fLabelProvider.getText(e1); String name2 = fLabelProvider.getText(e2); if (name1 == null) { name1 = "";//$NON-NLS-1$ } if (name2 == null) { name2 = "";//$NON-NLS-1$ } return getComparator().compare(name1, name2); } } protected int groupWithConfiguration = ISearchIndexContentProvider.GROUP_WITH_PROJECT | ISearchIndexContentProvider.GROUP_WITH_MODULES; public int getGroupWithConfiguration() { return groupWithConfiguration; } public void setGroupWithConfiguration(int groupWithConfiguration) { this.groupWithConfiguration = groupWithConfiguration; updateGroupWith(getViewer()); } private static String STORE_GROUP_WITH = "group_with"; @Override public void restoreState(IMemento memento) { super.restoreState(memento); if (memento != null) { Integer value = memento.getInteger(STORE_GROUP_WITH); if (value != null) { groupWithConfiguration = value.intValue(); updateGroupWith(this.getViewer()); } for (GroupByAction act : this.fGroupByActions) { act.updateImage(); } } } private void updateGroupWith(StructuredViewer viewer) { if (viewer != null) { IContentProvider contentProvider = viewer.getContentProvider(); if (contentProvider instanceof ISearchIndexContentProvider) { ISearchIndexContentProvider searchIndexTreeContentProvider = (ISearchIndexContentProvider) contentProvider; searchIndexTreeContentProvider.setGroupWith(groupWithConfiguration); } } } @Override public void saveState(IMemento memento) { super.saveState(memento); memento.putInteger(STORE_GROUP_WITH, this.groupWithConfiguration); } protected void textChanged() { if (refreshJob != null) { refreshJob.cancel(); } getRefreshJob().schedule(650); } protected WorkbenchJob getRefreshJob() { if (refreshJob == null) { refreshJob = new WorkbenchJob("Refresh Filter") {//$NON-NLS-1$ @Override public IStatus runInUIThread(IProgressMonitor monitor) { if (filterText != null && !filterText.isDisposed()) { final String text = filterText.getText().trim(); AbstractTextSearchResult input = getInput(); if (input != null) { if (!text.isEmpty()) { ViewerFilter[] filters = new ViewerFilter[] { createFilterFilter(text, false) }; getViewer().setFilters(filters); TreeViewer viewer = (TreeViewer) getViewer(); viewer.expandAll(); } else { getViewer().setFilters(new ViewerFilter[0]); } } } getViewPart().updateLabel(); return Status.OK_STATUS; } }; refreshJob.setSystem(true); } return refreshJob; } protected abstract AbstractSearchResultsViewerFilter createFilterFilter(String text, boolean wholeWord); protected static final String[] SHOW_IN_TARGETS = new String[] { IPageLayout.ID_RES_NAV }; protected static final IShowInTargetList SHOW_IN_TARGET_LIST = new IShowInTargetList() { @Override public String[] getShowInTargetIds() { return SHOW_IN_TARGETS; } }; @Override protected void configureTreeViewer(TreeViewer viewer) { viewer.setUseHashlookup(true); SearchIndexLabelProvider innerLabelProvider = createSearchIndexLabelProvider(); viewer.setLabelProvider(new DecoratingFileSearchLabelProvider(innerLabelProvider)); viewer.setContentProvider(createTreeContentProvider(viewer)); viewer.setComparator(new DecoratorIgnoringViewerSorter(innerLabelProvider)); fContentProvider = (ISearchIndexContentProvider) viewer.getContentProvider(); addDragAdapters(viewer); updateGroupWith(viewer); } protected SearchIndexLabelProvider createSearchIndexLabelProvider() { return new SearchIndexLabelProvider(this); } protected abstract TreeNodeContentProvider createTreeContentProvider(TreeViewer viewer); @Override protected void showMatch(Match match, int offset, int length, boolean activate) throws PartInitException { IFile file = (IFile) match.getElement(); IWorkbenchPage page = getSite().getPage(); if (offset >= 0 && length != 0) { openAndSelect(page, file, offset, length, activate); } else { open(page, file, activate); } } @Override protected void handleOpen(OpenEvent event) { Object firstElement = ((IStructuredSelection) event.getSelection()).getFirstElement(); if (getDisplayedMatchCount(firstElement) == 0) { try { if (firstElement instanceof IAdaptable) { IAdaptable iAdaptable = (IAdaptable) firstElement; IFile file = iAdaptable.getAdapter(IFile.class); if (file != null) { open(getSite().getPage(), file, false); } } } catch (PartInitException e) { ErrorDialog.openError(getSite().getShell(), "Open File", "Opening the file failed.", e.getStatus()); } return; } super.handleOpen(event); } @Override protected void configureTableViewer(TableViewer viewer) { throw new RuntimeException("Table layout is unsupported."); } @Override public StructuredViewer getViewer() { return super.getViewer(); } @Override public void setElementLimit(Integer elementLimit) { super.setElementLimit(elementLimit); } private void addDragAdapters(StructuredViewer viewer) { Transfer[] transfers = new Transfer[] { ResourceTransfer.getInstance() }; int ops = DND.DROP_COPY | DND.DROP_LINK; viewer.addDragSupport(ops, transfers, new NavigatorDragAdapter(viewer)); } @Override protected void fillContextMenu(IMenuManager mgr) { super.fillContextMenu(mgr); fActionGroup.setContext(new ActionContext(getSite().getSelectionProvider().getSelection())); fActionGroup.fillContextMenu(mgr); AbstractSearchIndexQuery query = (AbstractSearchIndexQuery) getInput().getQuery(); if (query.getSearchString().length() > 0) { IStructuredSelection selection = (IStructuredSelection) getViewer().getSelection(); if (!selection.isEmpty()) { ReplaceAction replaceSelection = new ReplaceAction(getSite().getShell(), getInput(), selection.toArray(), true); replaceSelection.setText(SearchMessages.ReplaceAction_label_selected); mgr.appendToGroup(IContextMenuConstants.GROUP_REORGANIZE, replaceSelection); } ICallback<Boolean, Match> skipMatch = new ICallback<Boolean, Match>() { @Override public Boolean call(Match match) { StructuredViewer viewer = getViewer(); ViewerFilter[] filters = viewer.getFilters(); if (filters == null || filters.length == 0) { return false; } for (ViewerFilter viewerFilter : filters) { if (viewerFilter instanceof AbstractSearchResultsViewerFilter) { AbstractSearchResultsViewerFilter searchResultsViewerFilter = (AbstractSearchResultsViewerFilter) viewerFilter; if (searchResultsViewerFilter.isLeafMatch(viewer, match)) { return false; } } } return true; } }; ReplaceAction replaceAll = new ReplaceAction(getSite().getShell(), getInput(), null, true, skipMatch); replaceAll.setText(SearchMessages.ReplaceAction_label_all); mgr.appendToGroup(IContextMenuConstants.GROUP_REORGANIZE, replaceAll); } } @Override public void setViewPart(ISearchResultViewPart part) { super.setViewPart(part); fActionGroup = new NewTextSearchActionGroup(part); } @Override protected void fillToolbar(IToolBarManager tbm) { super.fillToolbar(tbm); for (Action a : fGroupByActions) { String id = IContextMenuConstants.GROUP_PROPERTIES + "." + a.hashCode(); a.setId(id); tbm.add(a); } } @Override protected void elementsChanged(Object[] objects) { if (fContentProvider != null) { ViewerFilter[] filters = getViewer().getFilters(); for (ViewerFilter viewerFilter : filters) { if (viewerFilter instanceof AbstractSearchResultsViewerFilter) { AbstractSearchResultsViewerFilter searchResultsViewerFilter = (AbstractSearchResultsViewerFilter) viewerFilter; searchResultsViewerFilter.clearCache(); } } fContentProvider.elementsChanged(objects); } } @Override protected void clear() { if (fContentProvider != null) { fContentProvider.clear(); Job r = this.refreshJob; if (r != null) { r.cancel(); } filterText.setText(""); getViewer().setFilters(new ViewerFilter[0]); } } @Override public Object getUIState() { return new Tuple<>(super.getUIState(), filterText.getText()); } @SuppressWarnings("unchecked") @Override public void setInput(ISearchResult newSearch, Object viewState) { String filter = ""; if (viewState instanceof Tuple) { Tuple<Object, Object> tuple = (Tuple<Object, Object>) viewState; filter = (String) tuple.o2; viewState = tuple.o1; } StructuredViewer viewer = getViewer(); Control control = viewer.getControl(); control.setRedraw(false); try { viewer.setFilters(new ViewerFilter[0]); //Reset the filter before setting the new selection try { super.setInput(newSearch, viewState); } catch (Exception e) { Log.log(e); super.setInput(newSearch, null); } filterText.setText(filter); textChanged(); } finally { control.setRedraw(true); } } @Override public void dispose() { fActionGroup.dispose(); if (this.filterText != null) { this.filterText.dispose(); this.filterText = null; } if (refreshJob != null) { refreshJob.cancel(); refreshJob = null; } super.dispose(); } public Object getAdapter(Class<?> adapter) { if (IShowInTargetList.class.equals(adapter)) { return SHOW_IN_TARGET_LIST; } if (adapter == IShowInSource.class) { ISelectionProvider selectionProvider = getSite().getSelectionProvider(); if (selectionProvider == null) { return null; } ISelection selection = selectionProvider.getSelection(); if (selection instanceof IStructuredSelection) { IStructuredSelection structuredSelection = ((StructuredSelection) selection); final Set<Object> newSelection = new HashSet<>(structuredSelection.size()); Iterator<?> iter = structuredSelection.iterator(); while (iter.hasNext()) { Object element = iter.next(); if (element instanceof ICustomLineElement) { element = ((ICustomLineElement) element).getParent(); } newSelection.add(element); } return new IShowInSource() { @Override public ShowInContext getShowInContext() { return new ShowInContext(null, new StructuredSelection(new ArrayList<>(newSelection))); } }; } return null; } return null; } @Override public String getLabel() { StructuredViewer viewer = getViewer(); if (viewer instanceof TreeViewer) { int count = 0; TreeViewer tv = (TreeViewer) viewer; final AbstractTextSearchResult input = getInput(); if (input != null) { ViewerFilter[] filters = tv.getFilters(); if (filters != null && filters.length > 0) { Object[] elements = input.getElements(); for (int j = 0; j < elements.length; j++) { Object element = elements[j]; Match[] matches = input.getMatches(element); for (Match match : matches) { for (int i = 0; i < filters.length; i++) { ViewerFilter vf = filters[i]; if (vf instanceof AbstractSearchResultsViewerFilter) { AbstractSearchResultsViewerFilter searchResultsViewerFilter = (AbstractSearchResultsViewerFilter) vf; if (searchResultsViewerFilter.isLeafMatch(viewer, match)) { count += 1; } } } } } } else { // No active filters count = input.getMatchCount(); } } AbstractTextSearchResult result = getInput(); if (result == null) { return ""; } ISearchQuery query = result.getQuery(); if (query instanceof AbstractSearchIndexQuery) { AbstractSearchIndexQuery searchIndexQuery = (AbstractSearchIndexQuery) query; return searchIndexQuery.getResultLabel(count); } } return super.getLabel(); } @Override public int getDisplayedMatchCount(Object element) { if (element instanceof TreeNode<?>) { element = ((TreeNode<?>) element).data; } if (element instanceof ICustomLineElement) { ICustomLineElement lineEntry = (ICustomLineElement) element; return lineEntry.getNumberOfMatches(getInput()); } return 0; } @Override public Match[] getDisplayedMatches(Object element) { if (element instanceof TreeNode<?>) { element = ((TreeNode<?>) element).data; } if (element instanceof ICustomModule) { ICustomModule customModule = (ICustomModule) element; element = customModule.getModuleLineElement(); } if (element instanceof ICustomLineElement) { ICustomLineElement lineEntry = (ICustomLineElement) element; return lineEntry.getMatches(getInput()); } return new Match[0]; } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected void evaluateChangedElements(Match[] matches, Set changedElements) { for (int i = 0; i < matches.length; i++) { changedElements.add(((ICustomMatch) matches[i]).getLineElement()); } } @Override protected TreeViewer createTreeViewer(Composite parent) { createFilterControl(parent); TreeViewer ret = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL) { long currentTimeMillis; boolean inExpandAll = false; @Override public void expandAll() { currentTimeMillis = System.currentTimeMillis(); Control control = this.getControl(); control.setRedraw(false); try { inExpandAll = true; super.expandAll(); } catch (OperationCanceledException e) { // Ignore Log.log("Aborted expand operation because it took more than 5 seconds."); } finally { inExpandAll = false; control.setRedraw(true); } } @Override protected void internalExpandToLevel(Widget widget, int level) { if (inExpandAll) { if (System.currentTimeMillis() - currentTimeMillis > 5000) { throw new OperationCanceledException(); } } super.internalExpandToLevel(widget, level); } @Override public void collapseAll() { Control control = this.getControl(); control.setRedraw(false); try { super.collapseAll(); } finally { control.setRedraw(true); } } }; fixViewerLayout(ret.getControl()); return ret; } @Override protected TableViewer createTableViewer(Composite parent) { createFilterControl(parent); TableViewer ret = super.createTableViewer(parent); fixViewerLayout(ret.getControl()); return ret; } private void fixViewerLayout(Control control) { GridData layoutData = new GridData(GridData.FILL_BOTH); layoutData.grabExcessHorizontalSpace = true; layoutData.grabExcessVerticalSpace = true; layoutData.horizontalSpan = 3; control.setLayoutData(layoutData); } private void createFilterControl(Composite parent) { GridLayout layout = new GridLayout(3, false); parent.setLayout(layout); Label label = new Label(parent, SWT.NONE); label.setText(getFilterText()); filterText = new Text(parent, SWT.BORDER | SWT.SINGLE); GridData layoutData = new GridData(SWT.FILL, SWT.NONE, true, false); filterText.setLayoutData(layoutData); filterText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { textChanged(); } }); MultiStyledLink link = new MultiStyledLink(parent, SWT.NONE); link.setText("<a> ? </a>"); final String filterHelp = getFilterHelp(); link.getLink(0).addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { DialogHelpers.openInfo("", filterHelp); } }); link.getLink(0).setToolTipText(filterHelp); } protected abstract String getFilterHelp(); protected abstract String getFilterText(); }