/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.aptana.ide.search.epl;
import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.search.core.text.TextSearchEngine;
import org.eclipse.search.core.text.TextSearchMatchAccess;
import org.eclipse.search.core.text.TextSearchRequestor;
import org.eclipse.search.core.text.TextSearchScope;
import org.eclipse.search.internal.core.text.PatternConstructor;
import org.eclipse.search.internal.ui.SearchMessages;
import org.eclipse.search.internal.ui.SearchPlugin;
import org.eclipse.search.internal.ui.text.FileMatch;
import org.eclipse.search.internal.ui.text.FileSearchResult;
import org.eclipse.search.internal.ui.text.LineElement;
import org.eclipse.search.internal.ui.text.SearchResultUpdater;
import org.eclipse.search.ui.ISearchQuery;
import org.eclipse.search.ui.ISearchResult;
import org.eclipse.search.ui.text.AbstractTextSearchResult;
import org.eclipse.search.ui.text.FileTextSearchScope;
import org.eclipse.search.ui.text.Match;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.texteditor.ITextEditor;
import com.aptana.ide.core.StringUtils;
import com.aptana.ide.search.epl.filesystem.text.FileSystemTextSearchScope;
import com.aptana.ide.search.epl.filesystem.text.FileTextSearchEngine;
import com.aptana.ide.search.epl.filesystem.text.FileTextSearchMatchAccess;
import com.aptana.ide.search.epl.filesystem.text.FileTextSearchRequestor;
import com.aptana.ide.search.epl.internal.filesystem.text.FileNamePatternSearchScope;
import com.aptana.ide.search.epl.internal.filesystem.text.FileTextSearchVisitor;
/**
* @author Pavel Petrochenko
*/
public class FileSearchQuery extends
org.eclipse.search.internal.ui.text.FileSearchQuery implements
ISearchQuery
{
/**
* @author Pavel Petrochenko
*/
private static final class AptanaTextEngine extends TextSearchEngine
{
private boolean openEditorsOnly;
private boolean refresh;
/**
* @see org.eclipse.search.core.text.TextSearchEngine#search(org.eclipse.search.core.text.TextSearchScope,
* org.eclipse.search.core.text.TextSearchRequestor,
* java.util.regex.Pattern,
* org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus search(TextSearchScope scope,
TextSearchRequestor requestor, Pattern searchPattern,
IProgressMonitor monitor)
{
return new TextSearchVisitor(requestor, searchPattern,
this.openEditorsOnly, this.refresh).search(scope, monitor);
}
/**
* @see org.eclipse.search.core.text.TextSearchEngine#search(org.eclipse.core.resources.IFile[],
* org.eclipse.search.core.text.TextSearchRequestor,
* java.util.regex.Pattern,
* org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus search(IFile[] scope, TextSearchRequestor requestor,
Pattern searchPattern, IProgressMonitor monitor)
{
return new TextSearchVisitor(requestor, searchPattern,
this.openEditorsOnly, false).search(scope, monitor);
}
/**
* @param isOpenEditorsOnly
*/
public void setOpenEditorsOnly(boolean isOpenEditorsOnly)
{
this.openEditorsOnly = isOpenEditorsOnly;
}
/**
* @param isOpenEditorsOnly
*/
public void needsRefresh(boolean isOpenEditorsOnly)
{
this.refresh = isOpenEditorsOnly;
}
}
/**
* @author Pavel Petrochenko
*/
private static final class TextSearchResultCollector extends TextSearchRequestor
{
private final AbstractTextSearchResult fResult;
private final boolean fIsFileSearchOnly;
private final boolean fSearchInBinaries;
private ArrayList fCachedMatches;
private TextSearchResultCollector(AbstractTextSearchResult result,
boolean isFileSearchOnly, boolean searchInBinaries)
{
this.fResult = result;
this.fIsFileSearchOnly = isFileSearchOnly;
this.fSearchInBinaries = searchInBinaries;
}
/**
* @see org.eclipse.search.core.text.TextSearchRequestor#acceptFile(org.eclipse.core.resources.IFile)
*/
public boolean acceptFile(IFile file) throws CoreException
{
if (this.fIsFileSearchOnly)
{
this.fResult.addMatch(new AptanaFileMatch(file, 0, 0,
new LineElement(file, 0, 0, ""))); //$NON-NLS-1$
}
this.flushMatches();
return true;
}
/**
* @see org.eclipse.search.core.text.TextSearchRequestor#reportBinaryFile(org.eclipse.core.resources.IFile)
*/
public boolean reportBinaryFile(IFile file)
{
return this.fSearchInBinaries;
}
/**
* @see org.eclipse.search.core.text.TextSearchRequestor#acceptPatternMatch(org.eclipse.search.core.text.TextSearchMatchAccess)
*/
public boolean acceptPatternMatch(TextSearchMatchAccess matchRequestor)
throws CoreException
{
int matchOffset = matchRequestor.getMatchOffset();
LineElement lineElement = getLineElement(matchOffset, matchRequestor);
if (lineElement != null) {
AptanaFileMatch fileMatch = new AptanaFileMatch(matchRequestor
.getFile(), matchOffset, matchRequestor
.getMatchLength(), lineElement);
fCachedMatches.add(fileMatch);
}
return true;
}
private LineElement getLineElement(int offset, TextSearchMatchAccess matchRequestor) {
int lineNumber= 1;
int lineStart= 0;
if (!fCachedMatches.isEmpty()) {
// match on same line as last?
FileMatch last = (FileMatch) fCachedMatches.get(fCachedMatches.size() - 1);
LineElement lineElement = last.getLineElement();
if (lineElement.contains(offset)) {
return lineElement;
}
// start with the offset and line information from the last match
lineStart= lineElement.getOffset() + lineElement.getLength();
lineNumber= lineElement.getLine() + 1;
}
if (offset < lineStart) {
return null; // offset before the last line
}
int i= lineStart;
int contentLength = matchRequestor.getFileContentLength();
while (i < contentLength) {
char ch = matchRequestor.getFileContentChar(i++);
if (ch == '\n' || ch == '\r') {
if (ch == '\r' && i < contentLength && matchRequestor.getFileContentChar(i) == '\n') {
i++;
}
if (offset < i) {
String lineContent = getContents(matchRequestor, lineStart, i); // include line delimiter
return new LineElement(matchRequestor.getFile(), lineNumber, lineStart, lineContent);
}
lineNumber++;
lineStart= i;
}
}
if (offset < i) {
String lineContent = getContents(matchRequestor, lineStart, i); // until end of file
return new LineElement(matchRequestor.getFile(), lineNumber, lineStart, lineContent);
}
return null; // offset outside of range
}
private static String getContents(TextSearchMatchAccess matchRequestor, int start, int end) {
StringBuffer buf = new StringBuffer();
for (int i = start; i < end; i++) {
char ch = matchRequestor.getFileContentChar(i);
if (Character.isWhitespace(ch) || Character.isISOControl(ch)) {
buf.append(' ');
} else {
buf.append(ch);
}
}
return buf.toString();
}
/**
* @see org.eclipse.search.core.text.TextSearchRequestor#beginReporting()
*/
public void beginReporting()
{
this.fCachedMatches = new ArrayList();
}
/**
* @see org.eclipse.search.core.text.TextSearchRequestor#endReporting()
*/
public void endReporting()
{
this.flushMatches();
this.fCachedMatches = null;
}
private synchronized void flushMatches()
{
if (!this.fCachedMatches.isEmpty())
{
this.fResult.addMatches((Match[]) this.fCachedMatches
.toArray(new Match[this.fCachedMatches.size()]));
this.fCachedMatches.clear();
}
}
}
/**
* @author Pavel Petrochenko
*/
private static final class FileTextSearchResultCollector extends
FileTextSearchRequestor
{
private final AbstractTextSearchResult fResult;
private final boolean fIsFileSearchOnly;
private final boolean fSearchInBinaries;
private ArrayList fCachedMatches;
private FileTextSearchResultCollector(AbstractTextSearchResult result,
boolean isFileSearchOnly, boolean searchInBinaries)
{
this.fResult = result;
this.fIsFileSearchOnly = isFileSearchOnly;
this.fSearchInBinaries = searchInBinaries;
}
/**
* @param file
* @return
* @throws CoreException
*/
public boolean acceptFile(File file) throws CoreException
{
if (this.fIsFileSearchOnly)
{
this.fResult.addMatch(new AptanaFileSystemMatch(file, 0, 0, 0,
"")); //$NON-NLS-1$
}
this.flushMatches();
return true;
}
/**
* @param file
* @return
* @see org.eclipse.search.core.text.TextSearchRequestor#reportBinaryFile(org.eclipse.core.resources.IFile)
*/
public boolean reportBinaryFile(File file)
{
return this.fSearchInBinaries;
}
/**
* @param matchRequestor
* @return
* @throws CoreException
* @see org.eclipse.search.core.text.TextSearchRequestor#acceptPatternMatch(org.eclipse.search.core.text.TextSearchMatchAccess)
*/
public boolean acceptPatternMatch(TextSearchMatchAccess matchRequestor)
throws CoreException
{
return true;
}
/**
* @param matchRequestor
* @return
* @throws CoreException
* @see org.eclipse.search.core.text.TextSearchRequestor#acceptPatternMatch(org.eclipse.search.core.text.TextSearchMatchAccess)
*/
public boolean acceptPatternMatch(
FileTextSearchMatchAccess matchRequestor) throws CoreException
{
FileTextSearchVisitor.ReusableMatchAccess real = (FileTextSearchVisitor.ReusableMatchAccess) matchRequestor;
this.fCachedMatches.add(new AptanaFileSystemMatch(matchRequestor
.getFileSystemFile(), matchRequestor.getMatchOffset(), matchRequestor
.getMatchLength(), real.getLineNumber(), real
.getLineContent()));
return true;
}
/**
* @see org.eclipse.search.core.text.TextSearchRequestor#beginReporting()
*/
public void beginReporting()
{
this.fCachedMatches = new ArrayList();
}
/**
* @see org.eclipse.search.core.text.TextSearchRequestor#endReporting()
*/
public void endReporting()
{
this.flushMatches();
this.fCachedMatches = null;
}
private synchronized void flushMatches()
{
if (!this.fCachedMatches.isEmpty())
{
this.fResult.addMatches((Match[]) this.fCachedMatches
.toArray(new Match[this.fCachedMatches.size()]));
this.fCachedMatches.clear();
}
}
}
private final FileTextSearchScope fScope;
private final String fSearchText;
private final boolean fIsRegEx;
private final boolean fIsCaseSensitive;
private final boolean isOpenEditorsOnly;
private final String directory;
private FileSearchResult fResult;
private FileSystemSearchResult fDResult;
private boolean fIsIgnoreLineEndings;
private boolean refresh;
/**
* @param searchText
* @param isRegEx
* @param isCaseSensitive
* @param scope
* @param isOpenEditors
* @param directory
* @param isIgnoreLineEndings
*/
public FileSearchQuery(String searchText, boolean isRegEx,
boolean isCaseSensitive, FileTextSearchScope scope,
boolean isOpenEditors, String directory, boolean isIgnoreLineEndings)
{
super(searchText, isRegEx, isCaseSensitive, scope);
this.fSearchText = searchText;
this.directory = directory;
this.isOpenEditorsOnly = isOpenEditors;
this.fIsRegEx = isRegEx;
this.fIsCaseSensitive = isCaseSensitive;
this.fScope = scope;
this.fIsIgnoreLineEndings = isIgnoreLineEndings;
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#getSearchScope()
*/
public FileTextSearchScope getSearchScope()
{
return this.fScope;
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#canRunInBackground()
*/
public boolean canRunInBackground()
{
return true;
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#run(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus run(final IProgressMonitor monitor)
{
AbstractTextSearchResult textResult = (AbstractTextSearchResult) this
.getSearchResult();
textResult.removeAll();
Pattern searchPattern = this.getSearchPattern();
boolean isFileSearchOnly = searchPattern.pattern().length() == 0;
boolean searchInBinaries = !this.isScopeAllFileTypes();
if (this.directory != null || this.isOpenEditorsOnly)
{
boolean onlyFilesEditorInput = isAllOpenEditorsOnWorkspace();
if (!onlyFilesEditorInput || !isOpenEditorsOnly)
{
FileTextSearchResultCollector fcollector = new FileTextSearchResultCollector(
textResult, isFileSearchOnly, searchInBinaries);
String fs = directory == null ? "." : directory; //$NON-NLS-1$
FileSystemTextSearchScope newSearchScope = FileNamePatternSearchScope
.newSearchScope(new File[] { new File(fs) },
this.fScope.getFileNamePatterns());
newSearchScope.setOpenEditors(this.isOpenEditorsOnly);
return FileTextSearchEngine.createDefault().search(
newSearchScope, fcollector, searchPattern, monitor);
}
}
TextSearchResultCollector collector = new TextSearchResultCollector(
textResult, isFileSearchOnly, searchInBinaries);
AptanaTextEngine aptanaTextEngine = new AptanaTextEngine();
aptanaTextEngine.setOpenEditorsOnly(this.isOpenEditorsOnly);
aptanaTextEngine.needsRefresh(this.refresh);
return aptanaTextEngine.search(this.fScope, collector, searchPattern,
monitor);
}
private boolean isAllOpenEditorsOnWorkspace()
{
boolean onlyFilesEditorInput = true;
IWorkbench workbench = SearchPlugin.getDefault().getWorkbench();
IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
for (int i = 0; i < windows.length; i++)
{
IWorkbenchPage[] pages = windows[i].getPages();
for (int x = 0; x < pages.length; x++)
{
IEditorReference[] editorRefs = pages[x].getEditorReferences();
for (int z = 0; z < editorRefs.length; z++)
{
IEditorPart ep = editorRefs[z].getEditor(false);
if ((ep instanceof ITextEditor))
{ // only dirty editors
IEditorInput input = ep.getEditorInput();
if (input instanceof IFileEditorInput){
continue;
}
if (input instanceof IPathEditorInput || input instanceof IURIEditorInput)
{
onlyFilesEditorInput = false;
}
}
}
}
}
return onlyFilesEditorInput;
}
private boolean isScopeAllFileTypes()
{
String[] fileNamePatterns = this.fScope.getFileNamePatterns();
if (fileNamePatterns == null)
{
return true;
}
for (int i = 0; i < fileNamePatterns.length; i++)
{
if ("*".equals(fileNamePatterns[i])) { //$NON-NLS-1$
return true;
}
}
return false;
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#getLabel()
*/
public String getLabel()
{
return SearchMessages.FileSearchQuery_label;
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#getSearchString()
*/
public String getSearchString()
{
String searchText = this.fSearchText;
searchText = searchText.replace("\r", "\\r"); //$NON-NLS-1$//$NON-NLS-2$
searchText = searchText.replace("\n", "\\n"); //$NON-NLS-1$ //$NON-NLS-2$
if (searchText.length() > 50)
{
searchText = StringUtils.ellipsify(searchText.substring(0, 45));
}
return searchText;
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#getResultLabel(int)
*/
public String getResultLabel(int nMatches)
{
String searchString = this.getSearchString();
String fdesc = this.directory == null ? this.fScope.getDescription()
: this.directory;
if (this.isOpenEditorsOnly)
{
fdesc = Messages.OPEN_EDITORS;
}
if (searchString.length() > 0)
{
// text search
if (this.isScopeAllFileTypes())
{
// search all file extensions
if (nMatches == 1)
{
Object[] args = { searchString,
this.fScope.getDescription() };
return MessageFormat.format(
SearchMessages.FileSearchQuery_singularLabel, args);
}
Object[] args = { searchString, new Integer(nMatches), fdesc };
return MessageFormat.format(
SearchMessages.FileSearchQuery_pluralPattern, args);
}
// search selected file extensions
if (nMatches == 1)
{
Object[] args = { searchString, fdesc,
this.fScope.getFilterDescription() };
return MessageFormat
.format(
SearchMessages.FileSearchQuery_singularPatternWithFileExt,
args);
}
Object[] args = { searchString, new Integer(nMatches), fdesc,
this.fScope.getFilterDescription() };
return MessageFormat.format(
SearchMessages.FileSearchQuery_pluralPatternWithFileExt,
args);
}
// file search
if (nMatches == 1)
{
Object[] args = { this.fScope.getFilterDescription(), fdesc };
return MessageFormat
.format(
SearchMessages.FileSearchQuery_singularLabel_fileNameSearch,
args);
}
Object[] args = { this.fScope.getFilterDescription(),
new Integer(nMatches), fdesc };
return MessageFormat.format(
SearchMessages.FileSearchQuery_pluralPattern_fileNameSearch,
args);
}
/**
* @param result
* all result are added to this search result
* @param monitor
* the progress monitor to use
* @param file
* the file to search in
* @return returns the status of the operation
*/
public IStatus searchInFile(final AbstractTextSearchResult result,
final IProgressMonitor monitor, IFile file)
{
FileTextSearchScope scope = FileTextSearchScope.newSearchScope(
new IResource[] { file }, new String[] { "*" }, true); //$NON-NLS-1$
Pattern searchPattern = this.getSearchPattern();
boolean isFileSearchOnly = searchPattern.pattern().length() == 0;
TextSearchResultCollector collector = new TextSearchResultCollector(
result, isFileSearchOnly, true);
return new AptanaTextEngine().search(scope, collector, searchPattern,
monitor);
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#getSearchPattern()
*/
protected Pattern getSearchPattern()
{
return PatternConstructor
.createPattern(this.fSearchText, this.fIsCaseSensitive,
this.fIsRegEx);
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#isRegexSearch()
*/
public boolean isRegexSearch()
{
return this.fIsRegEx;
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#isCaseSensitive()
*/
public boolean isCaseSensitive()
{
return this.fIsCaseSensitive;
}
/**
* @return
*/
public boolean isIgnoreLineEndings()
{
return this.fIsIgnoreLineEndings;
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#canRerun()
*/
public boolean canRerun()
{
return true;
}
/**
* @see org.eclipse.search.internal.ui.text.FileSearchQuery#getSearchResult()
*/
public ISearchResult getSearchResult()
{
if (this.directory != null)
{
if (this.fDResult == null)
{
this.fDResult = new FileSystemSearchResult(this);
}
return this.fDResult;
}
if (this.isOpenEditorsOnly)
{
if (!isAllOpenEditorsOnWorkspace())
{
if (this.fDResult == null)
{
this.fDResult = new FileSystemSearchResult(this);
}
return this.fDResult;
}
}
if (this.fResult == null)
{
this.fResult = new FileSearchResult(this);
new SearchResultUpdater(this.fResult);
}
return this.fResult;
}
/**
* @return boolean
*/
public boolean isOpenEditorsOnly()
{
return this.isOpenEditorsOnly;
}
/**
* @return string
*/
public String getDirectory()
{
return this.directory;
}
/**
* @param result
* @param monitor
* @param entry
* @return
*/
public IStatus searchInFile(AbstractTextSearchResult result,
IProgressMonitor monitor, File entry)
{
FileSystemTextSearchScope scope = FileSystemTextSearchScope
.newSearchScope(new File[] { entry },
new String[] { "*" }, true); //$NON-NLS-1$
Pattern searchPattern = this.getSearchPattern();
boolean isFileSearchOnly = searchPattern.pattern().length() == 0;
FileTextSearchResultCollector collector = new FileTextSearchResultCollector(
result, isFileSearchOnly, true);
return FileTextSearchEngine.createDefault().search(scope, collector,
searchPattern, monitor);
}
public void setRefresh(boolean refresh)
{
this.refresh=refresh;
}
public boolean isRefresh(){
return this.refresh;
}
}