blob: beaa94c9b07536139ecd5521e761083a6dcee429 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 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 org.eclipse.ui.internal.console;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IPatternMatchListener;
import org.eclipse.ui.console.PatternMatchEvent;
import org.eclipse.ui.console.TextConsole;
public class ConsolePatternMatcher implements IDocumentListener {
private MatchJob fMatchJob = new MatchJob();
/**
* Collection of compiled pattern match listeners
*/
private ArrayList fPatterns = new ArrayList();
private TextConsole fConsole;
private boolean fFinalMatch;
private boolean fScheduleFinal;
public ConsolePatternMatcher(TextConsole console) {
fConsole = console;
}
private class MatchJob extends Job {
MatchJob() {
super("Match Job"); //$NON-NLS-1$
setSystem(true);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
protected IStatus run(IProgressMonitor monitor) {
IDocument doc = fConsole.getDocument();
String text = null;
int prevBaseOffset = -1;
if (doc != null && !monitor.isCanceled()) {
int endOfSearch = doc.getLength();
int indexOfLastChar = endOfSearch;
if (indexOfLastChar > 0) {
indexOfLastChar--;
}
int lastLineToSearch = 0;
int offsetOfLastLineToSearch = 0;
try {
lastLineToSearch = doc.getLineOfOffset(indexOfLastChar);
offsetOfLastLineToSearch = doc.getLineOffset(lastLineToSearch);
} catch (BadLocationException e) {
// perhaps the buffer was re-set
return Status.OK_STATUS;
}
for (int i = 0; i < fPatterns.size(); i++) {
if (monitor.isCanceled()) {
break;
}
CompiledPatternMatchListener notifier = (CompiledPatternMatchListener) fPatterns.get(i);
int baseOffset = notifier.end;
int lengthToSearch = endOfSearch - baseOffset;
if (lengthToSearch > 0) {
try {
if (prevBaseOffset != baseOffset) {
// reuse the text string if possible
text = doc.get(baseOffset, lengthToSearch);
}
Matcher reg = notifier.pattern.matcher(text);
Matcher quick = null;
if (notifier.qualifier != null) {
quick = notifier.qualifier.matcher(text);
}
int startOfNextSearch = 0;
int endOfLastMatch = -1;
int lineOfLastMatch = -1;
while ((startOfNextSearch < lengthToSearch) && !monitor.isCanceled()) {
if (quick != null) {
if (quick.find(startOfNextSearch)) {
// start searching on the beginning
// of the line where the potential
// match was found, or after the
// last match on the same line
int matchLine = doc.getLineOfOffset(baseOffset + quick.start());
if (lineOfLastMatch == matchLine) {
startOfNextSearch = endOfLastMatch;
} else {
startOfNextSearch = doc.getLineOffset(matchLine) - baseOffset;
}
} else {
startOfNextSearch = lengthToSearch;
}
}
if (startOfNextSearch < 0) {
startOfNextSearch = 0;
}
if (startOfNextSearch < lengthToSearch) {
if (reg.find(startOfNextSearch)) {
endOfLastMatch = reg.end();
lineOfLastMatch = doc.getLineOfOffset(baseOffset + endOfLastMatch - 1);
int regStart = reg.start();
IPatternMatchListener listener = notifier.listener;
if (listener != null && !monitor.isCanceled()) {
listener.matchFound(new PatternMatchEvent(fConsole, baseOffset + regStart, endOfLastMatch - regStart));
}
startOfNextSearch = endOfLastMatch;
} else {
startOfNextSearch = lengthToSearch;
}
}
}
// update start of next search to the last line
// searched
// or the end of the last match if it was on the
// line that
// was last searched
if (lastLineToSearch == lineOfLastMatch) {
notifier.end = baseOffset + endOfLastMatch;
} else {
notifier.end = offsetOfLastLineToSearch;
}
} catch (BadLocationException e) {
ConsolePlugin.log(e);
}
}
prevBaseOffset = baseOffset;
}
}
if (fFinalMatch) {
disconnect();
fConsole.matcherFinished();
} else if (fScheduleFinal) {
fFinalMatch = true;
schedule();
}
return Status.OK_STATUS;
}
public boolean belongsTo(Object family) {
return family == fConsole;
}
}
private class CompiledPatternMatchListener {
Pattern pattern;
Pattern qualifier;
IPatternMatchListener listener;
int end = 0;
CompiledPatternMatchListener(Pattern pattern, Pattern qualifier, IPatternMatchListener matchListener) {
this.pattern = pattern;
this.listener = matchListener;
this.qualifier = qualifier;
}
public void dispose() {
listener.disconnect();
pattern = null;
qualifier = null;
listener = null;
}
}
/**
* Adds the given pattern match listener to this console. The listener will
* be connected and receive match notifications.
*
* @param matchListener
* the pattern match listener to add
*/
public void addPatternMatchListener(IPatternMatchListener matchListener) {
synchronized (fPatterns) {
// check for dups
for (Iterator iter = fPatterns.iterator(); iter.hasNext();) {
CompiledPatternMatchListener element = (CompiledPatternMatchListener) iter.next();
if (element.listener == matchListener) {
return;
}
}
if (matchListener == null || matchListener.getPattern() == null) {
throw new IllegalArgumentException("Pattern cannot be null"); //$NON-NLS-1$
}
Pattern pattern = Pattern.compile(matchListener.getPattern(), matchListener.getCompilerFlags());
String qualifier = matchListener.getLineQualifier();
Pattern qPattern = null;
if (qualifier != null) {
qPattern = Pattern.compile(qualifier, matchListener.getCompilerFlags());
}
CompiledPatternMatchListener notifier = new CompiledPatternMatchListener(pattern, qPattern, matchListener);
fPatterns.add(notifier);
matchListener.connect(fConsole);
fMatchJob.schedule();
}
}
/**
* Removes the given pattern match listener from this console. The listener
* will be disconnected and will no longer receive match notifications.
*
* @param matchListener
* the pattern match listener to remove.
*/
public void removePatternMatchListener(IPatternMatchListener matchListener) {
synchronized (fPatterns) {
for (Iterator iter = fPatterns.iterator(); iter.hasNext();) {
CompiledPatternMatchListener element = (CompiledPatternMatchListener) iter.next();
if (element.listener == matchListener) {
iter.remove();
matchListener.disconnect();
}
}
}
}
public void disconnect() {
fMatchJob.cancel();
synchronized (fPatterns) {
Iterator iterator = fPatterns.iterator();
while (iterator.hasNext()) {
CompiledPatternMatchListener notifier = (CompiledPatternMatchListener) iterator.next();
notifier.dispose();
}
fPatterns.clear();
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
*/
public void documentAboutToBeChanged(DocumentEvent event) {
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
*/
public void documentChanged(DocumentEvent event) {
if (event.fLength > 0) {
synchronized (fPatterns) {
if (event.fDocument.getLength() == 0) {
// document has been cleared, reset match listeners
Iterator iter = fPatterns.iterator();
while (iter.hasNext()) {
CompiledPatternMatchListener notifier = (CompiledPatternMatchListener) iter.next();
notifier.end = 0;
}
} else {
if (event.fOffset == 0) {
//document was trimmed
Iterator iter = fPatterns.iterator();
while (iter.hasNext()) {
CompiledPatternMatchListener notifier = (CompiledPatternMatchListener) iter.next();
notifier.end = notifier.end > event.fLength ? notifier.end-event.fLength : 0;
}
}
}
}
}
fMatchJob.schedule();
}
public void forceFinalMatching() {
fScheduleFinal = true;
fMatchJob.schedule();
}
}