package com.asparck.eclipse.multicursor.handlers; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.commands.AbstractHandlerWithState; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.State; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.FindReplaceDocumentAdapter; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.LinkedModeUI; import org.eclipse.jface.text.link.LinkedPosition; import org.eclipse.jface.text.link.LinkedPositionGroup; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.swt.graphics.Point; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.handlers.HandlerUtil; import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; import com.asparck.eclipse.multicursor.copied.DeleteBlockingExitPolicy; import com.asparck.eclipse.multicursor.hacks.ISourceViewerFinder; import com.asparck.eclipse.multicursor.util.CoordinatesUtil; import com.asparck.eclipse.multicursor.util.TextUtil; public class SelectNextOccurrenceHandler extends AbstractHandlerWithState { private static final String ID_SELECTS_IN_PROGRESS = "SELECTS_IN_PROGRESS"; private static final class SelectInProgress { public final String searchText; public final List<IRegion> existingSelections; public final int nextOffset; private final Point startingSelection; public SelectInProgress(Point startingSelection, String selectedText, List<IRegion> existingSelections, int nextOffset) { this.startingSelection = startingSelection; this.searchText = selectedText; this.existingSelections = Collections.unmodifiableList(new ArrayList<IRegion>(existingSelections)); this.nextOffset = nextOffset; } @Override public String toString() { return "[Find " + searchText + " at " + nextOffset + "; original=" + startingSelection + "]"; } } @Override public Object execute(ExecutionEvent event) throws ExecutionException { IEditorPart editor = HandlerUtil.getActiveEditorChecked(event); ISourceViewer viewer = ISourceViewerFinder.fromEditorPart(editor); if (viewer != null) { startEditing(viewer); } return null; } private void startEditing(ISourceViewer viewer) throws ExecutionException { final Point selOffsetAndLen = viewer.getSelectedRange(); final IDocument document = viewer.getDocument(); try { final String searchText; final int candidateSearchOffset; final int selStart = CoordinatesUtil.fromOffsetAndLengthToStartAndEnd(selOffsetAndLen).x; if (selOffsetAndLen.y == 0) { // no characters selected final String documentText = document.get(); final Point wordOffsetAndLen = TextUtil.findWordSurrounding(documentText, selStart); if (wordOffsetAndLen != null) { searchText = document.get(wordOffsetAndLen.x, wordOffsetAndLen.y); candidateSearchOffset = wordOffsetAndLen.x; } else { final IRegion selectedLine = document.getLineInformationOfOffset(selStart); searchText = document.get(selectedLine.getOffset(), selectedLine.getLength()); candidateSearchOffset = selectedLine.getOffset(); } } else { searchText = document.get(selOffsetAndLen.x, selOffsetAndLen.y); candidateSearchOffset = selOffsetAndLen.x; } final int searchOffset; final List<IRegion> selections; final Point startingSelection; final SelectInProgress currentState = getCurrentState(); if (LinkedModeModel.getModel(document, 0) != null && currentState != null && selOffsetAndLen.equals(currentState.startingSelection) && searchText.equals(currentState.searchText)) { startingSelection = currentState.startingSelection; selections = new ArrayList<IRegion>(currentState.existingSelections); searchOffset = currentState.nextOffset; } else { startingSelection = selOffsetAndLen; selections = new ArrayList<IRegion>(); searchOffset = candidateSearchOffset; } final IRegion matchingRegion = new FindReplaceDocumentAdapter(document).find(searchOffset, searchText, true, true, false, false); if (matchingRegion != null) { selections.add(matchingRegion); if (selections.size() == 1) { // select the next occurrence too; only selecting the current cursor pos isn't useful final IRegion secondMatchingRegion = new FindReplaceDocumentAdapter(document).find( matchingRegion.getOffset() + matchingRegion.getLength(), searchText, true, true, false, false); if (secondMatchingRegion != null) { selections.add(secondMatchingRegion); } } if (selections.size() > 1) { final IRegion lastSelection = selections.get(selections.size() - 1); saveCurrentState(new SelectInProgress(startingSelection, searchText, selections, lastSelection.getOffset() + lastSelection.getLength())); startLinkedEdit(selections, viewer, selOffsetAndLen); } } } catch (BadLocationException e) { throw new ExecutionException("Editing failed", e); } } // Reference: RenameLinkedMode class shows how linked mode is meant to be used private void startLinkedEdit(List<IRegion> selections, ITextViewer viewer, Point originalSelection) throws BadLocationException { final LinkedPositionGroup linkedPositionGroup = new LinkedPositionGroup(); for (IRegion selection : selections) { linkedPositionGroup.addPosition(new LinkedPosition(viewer.getDocument(), selection.getOffset(), selection .getLength())); } LinkedModeModel model = new LinkedModeModel(); model.addGroup(linkedPositionGroup); model.forceInstall(); //FIXME can add a listener here to listen for the end of linked mode //model.addLinkingListener(null); LinkedModeUI ui = new EditorLinkedModeUI(model, viewer); ui.setExitPolicy(new DeleteBlockingExitPolicy(viewer.getDocument())); ui.enter(); // by default the text being edited is selected so restore original selection viewer.setSelectedRange(originalSelection.x, originalSelection.y); } private SelectInProgress getCurrentState() { State state = getState(ID_SELECTS_IN_PROGRESS); if (state == null) { return null; } else { return (SelectInProgress) state.getValue(); } } private void saveCurrentState(SelectInProgress selectInProgress) { State state = new State(); state.setValue(selectInProgress); state.setId(ID_SELECTS_IN_PROGRESS); addState(ID_SELECTS_IN_PROGRESS, state); } @Override public void handleStateChange(State state, Object oldValue) { // logger.debug("State changed; new value=" + state.getId() + ":" + state.getValue() + " and old value=" // + oldValue); } }