/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.search2.internal.ui.text; import com.google.dart.tools.search.internal.ui.SearchPlugin; import com.google.dart.tools.search.ui.IQueryListener; import com.google.dart.tools.search.ui.ISearchQuery; import com.google.dart.tools.search.ui.ISearchResult; import com.google.dart.tools.search.ui.ISearchResultListener; import com.google.dart.tools.search.ui.NewSearchUI; import com.google.dart.tools.search.ui.SearchResultEvent; import com.google.dart.tools.search.ui.text.AbstractTextSearchResult; import com.google.dart.tools.search.ui.text.IFileMatchAdapter; import com.google.dart.tools.search.ui.text.Match; import com.google.dart.tools.search.ui.text.MatchEvent; import com.google.dart.tools.search.ui.text.RemoveAllEvent; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.IFileBuffer; import org.eclipse.core.filebuffers.IFileBufferListener; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.filebuffers.LocationKind; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Position; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; public class PositionTracker implements IQueryListener, ISearchResultListener, IFileBufferListener { private interface IFileBufferMatchOperation { void run(ITextFileBuffer buffer, Match match); } public static Position convertToCharacterPosition(Position linePosition, IDocument doc) throws BadLocationException { int lineOffset = linePosition.getOffset(); int lineLength = linePosition.getLength(); int charOffset = doc.getLineOffset(lineOffset); int charLength = 0; if (lineLength > 0) { int lastLine = lineOffset + lineLength - 1; int endPosition = doc.getLineOffset(lastLine) + doc.getLineLength(lastLine); charLength = endPosition - charOffset; } return new Position(charOffset, charLength); } public static Position convertToLinePosition(Position pos, IDocument doc) throws BadLocationException { int offset = doc.getLineOfOffset(pos.getOffset()); int end = doc.getLineOfOffset(pos.getOffset() + pos.getLength()); int lineLength = end - offset; if (pos.getLength() > 0 && lineLength == 0) { // if the character length is > 0, add the last line, too lineLength++; } return new Position(offset, lineLength); } private Map<Match, Position> fMatchesToPositions = new HashMap<Match, Position>(); private Map<Match, AbstractTextSearchResult> fMatchesToSearchResults = new HashMap<Match, AbstractTextSearchResult>(); private Map<ITextFileBuffer, Set<Match>> fFileBuffersToMatches = new HashMap<ITextFileBuffer, Set<Match>>(); public PositionTracker() { NewSearchUI.addQueryListener(this); FileBuffers.getTextFileBufferManager().addFileBufferListener(this); } @Override public void bufferContentAboutToBeReplaced(IFileBuffer buffer) { // not interesting for us. } @Override public void bufferContentReplaced(IFileBuffer buffer) { final int[] trackCount = new int[1]; doForExistingMatchesIn(buffer, new IFileBufferMatchOperation() { @Override public void run(ITextFileBuffer textBuffer, Match match) { trackCount[0]++; AbstractTextSearchResult result = fMatchesToSearchResults.get(match); untrackPosition(textBuffer, match); trackPosition(result, textBuffer, match); } }); } // IFileBufferListener implementation --------------------------------------------------------------------- @Override public void bufferCreated(IFileBuffer buffer) { final int[] trackCount = new int[1]; if (!(buffer instanceof ITextFileBuffer)) { return; } IFile file = FileBuffers.getWorkspaceFileAtLocation(buffer.getLocation()); if (file == null) { return; } ISearchQuery[] queries = NewSearchUI.getQueries(); for (int i = 0; i < queries.length; i++) { ISearchResult result = queries[i].getSearchResult(); if (result instanceof AbstractTextSearchResult) { AbstractTextSearchResult textResult = (AbstractTextSearchResult) result; IFileMatchAdapter adapter = textResult.getFileMatchAdapter(); if (adapter != null) { Match[] matches = adapter.computeContainedMatches(textResult, file); for (int j = 0; j < matches.length; j++) { trackCount[0]++; trackPosition((AbstractTextSearchResult) result, (ITextFileBuffer) buffer, matches[j]); } } } } } @Override public void bufferDisposed(IFileBuffer buffer) { final int[] trackCount = new int[1]; doForExistingMatchesIn(buffer, new IFileBufferMatchOperation() { @Override public void run(ITextFileBuffer textBuffer, Match match) { trackCount[0]++; untrackPosition(textBuffer, match); } }); } @Override public void dirtyStateChanged(IFileBuffer buffer, boolean isDirty) { if (isDirty) { return; } final int[] trackCount = new int[1]; doForExistingMatchesIn(buffer, new IFileBufferMatchOperation() { @Override public void run(ITextFileBuffer textBuffer, Match match) { trackCount[0]++; Position pos = fMatchesToPositions.get(match); if (pos != null) { if (pos.isDeleted()) { AbstractTextSearchResult result = fMatchesToSearchResults.get(match); // might be that the containing element has been removed. if (result != null) { result.removeMatch(match); } untrackPosition(textBuffer, match); } else { if (match.getBaseUnit() == Match.UNIT_LINE) { try { pos = convertToLinePosition(pos, textBuffer.getDocument()); } catch (BadLocationException e) { SearchPlugin.getDefault().getLog().log( new Status(IStatus.ERROR, SearchPlugin.getID(), 0, e.getLocalizedMessage(), e)); } } match.setOffset(pos.getOffset()); match.setLength(pos.getLength()); } } } }); } public void dispose() { NewSearchUI.removeQueryListener(this); FileBuffers.getTextFileBufferManager().removeFileBufferListener(this); } public Position getCurrentPosition(Match match) { Position pos = fMatchesToPositions.get(match); if (pos == null) { return pos; } AbstractTextSearchResult result = fMatchesToSearchResults.get(match); if (match.getBaseUnit() == Match.UNIT_LINE && result != null) { ITextFileBuffer fb = getTrackedFileBuffer(result, match.getElement()); if (fb != null) { IDocument doc = fb.getDocument(); try { pos = convertToLinePosition(pos, doc); } catch (BadLocationException e) { } } } return pos; } // tracking search results -------------------------------------------------------------- @Override public void queryAdded(ISearchQuery query) { if (query.getSearchResult() instanceof AbstractTextSearchResult) { query.getSearchResult().addListener(this); } } @Override public void queryFinished(ISearchQuery query) { // not interested } @Override public void queryRemoved(ISearchQuery query) { ISearchResult result = query.getSearchResult(); if (result instanceof AbstractTextSearchResult) { untrackAll((AbstractTextSearchResult) result); result.removeListener(this); } } @Override public void queryStarting(ISearchQuery query) { // not interested here } // tracking matches --------------------------------------------------------------------- @Override public void searchResultChanged(SearchResultEvent e) { if (e instanceof MatchEvent) { MatchEvent evt = (MatchEvent) e; Match[] matches = evt.getMatches(); int kind = evt.getKind(); AbstractTextSearchResult result = (AbstractTextSearchResult) e.getSearchResult(); for (int i = 0; i < matches.length; i++) { ITextFileBuffer fb = getTrackedFileBuffer(result, matches[i].getElement()); if (fb != null) { updateMatch(matches[i], fb, kind, result); } } } else if (e instanceof RemoveAllEvent) { RemoveAllEvent evt = (RemoveAllEvent) e; ISearchResult result = evt.getSearchResult(); untrackAll((AbstractTextSearchResult) result); } } @Override public void stateChangeFailed(IFileBuffer buffer) { // not interesting for us. } @Override public void stateChanging(IFileBuffer buffer) { // not interesting for us } @Override public void stateValidationChanged(IFileBuffer buffer, boolean isStateValidated) { // not interesting for us. } @Override public void underlyingFileDeleted(IFileBuffer buffer) { // not interesting for us. } @Override public void underlyingFileMoved(IFileBuffer buffer, IPath path) { // not interesting for us. } private void addFileBufferMapping(ITextFileBuffer fb, Match match) { Set<Match> matches = fFileBuffersToMatches.get(fb); if (matches == null) { matches = new HashSet<Match>(); fFileBuffersToMatches.put(fb, matches); } matches.add(match); } private void doForExistingMatchesIn(IFileBuffer buffer, IFileBufferMatchOperation operation) { if (!(buffer instanceof ITextFileBuffer)) { return; } Set<Match> matches = fFileBuffersToMatches.get(buffer); if (matches != null) { HashSet<Match> matchSet = new HashSet<Match>(matches); for (Iterator<Match> matchIterator = matchSet.iterator(); matchIterator.hasNext();) { Match element = matchIterator.next(); operation.run((ITextFileBuffer) buffer, element); } } } private ITextFileBuffer getTrackedFileBuffer(AbstractTextSearchResult result, Object element) { IFileMatchAdapter adapter = result.getFileMatchAdapter(); if (adapter == null) { return null; } IFile file = adapter.getFile(element); if (file == null) { return null; } if (!file.exists()) { return null; } return FileBuffers.getTextFileBufferManager().getTextFileBuffer( file.getFullPath(), LocationKind.IFILE); } private void removeFileBufferMapping(ITextFileBuffer fb, Match match) { Set<Match> matches = fFileBuffersToMatches.get(fb); if (matches != null) { matches.remove(match); if (matches.size() == 0) { fFileBuffersToMatches.remove(fb); } } } private void trackPosition(AbstractTextSearchResult result, ITextFileBuffer fb, Match match) { int offset = match.getOffset(); int length = match.getLength(); if (offset < 0 || length < 0) { return; } try { IDocument doc = fb.getDocument(); Position position = new Position(offset, length); if (match.getBaseUnit() == Match.UNIT_LINE) { position = convertToCharacterPosition(position, doc); } doc.addPosition(position); fMatchesToSearchResults.put(match, result); fMatchesToPositions.put(match, position); addFileBufferMapping(fb, match); } catch (BadLocationException e) { // the match is outside the document result.removeMatch(match); } } private void untrackAll(AbstractTextSearchResult result) { Set<Match> matchSet = new HashSet<Match>(fMatchesToPositions.keySet()); for (Iterator<Match> matches = matchSet.iterator(); matches.hasNext();) { Match match = matches.next(); AbstractTextSearchResult matchContainer = fMatchesToSearchResults.get(match); if (result.equals(matchContainer)) { ITextFileBuffer fb = getTrackedFileBuffer(result, match.getElement()); if (fb != null) { untrackPosition(fb, match); } } } } private void untrackPosition(ITextFileBuffer fb, Match match) { Position position = fMatchesToPositions.get(match); if (position != null) { removeFileBufferMapping(fb, match); fMatchesToSearchResults.remove(match); fMatchesToPositions.remove(match); fb.getDocument().removePosition(position); } } private void updateMatch(Match match, ITextFileBuffer fb, int kind, AbstractTextSearchResult result) { if (kind == MatchEvent.ADDED) { trackPosition(result, fb, match); } else if (kind == MatchEvent.REMOVED) { untrackPosition(fb, match); } } }