| /******************************************************************************* |
| * 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(); |
| } |
| |
| } |