| /*=============================================================================# |
| # Copyright (c) 2008, 2021 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.ltk.ui.sourceediting.assist; |
| |
| import static org.eclipse.statet.ltk.ui.LtkUI.BUNDLE_ID; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.link.LinkedModeModel; |
| import org.eclipse.jface.text.link.LinkedModeUI; |
| import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags; |
| import org.eclipse.jface.text.link.LinkedModeUI.IExitPolicy; |
| import org.eclipse.jface.text.link.LinkedPosition; |
| import org.eclipse.jface.text.link.LinkedPositionGroup; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.VerifyEvent; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; |
| |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.text.core.TextRegion; |
| |
| import org.eclipse.statet.ltk.ui.LtkUI; |
| import org.eclipse.statet.ltk.ui.sourceediting.SourceEditor; |
| import org.eclipse.statet.ltk.ui.sourceediting.TextEditToolSynchronizer; |
| |
| |
| @NonNullByDefault |
| public abstract class LinkedNamesAssistProposal<TContext extends AssistInvocationContext> |
| extends CommandAssistProposal<TContext> { |
| |
| |
| /** |
| * An exit policy that skips Backspace and Delete at the beginning and at the end |
| * of a linked position, respectively. |
| */ |
| public static class DeleteBlockingExitPolicy implements IExitPolicy { |
| |
| private final IDocument document; |
| |
| public DeleteBlockingExitPolicy(final IDocument document) { |
| this.document= document; |
| } |
| |
| @Override |
| public @Nullable ExitFlags doExit(final LinkedModeModel model, final VerifyEvent event, |
| final int offset, final int length) { |
| switch (event.character) { |
| case SWT.BS: |
| { //skip backspace at beginning of linked position |
| final LinkedPosition position= model.findPosition(new LinkedPosition( |
| this.document, offset, 0, LinkedPositionGroup.NO_STOP)); |
| if (position != null && offset <= position.getOffset() && length == 0) { |
| event.doit= false; |
| } |
| return null; |
| } |
| case SWT.DEL: |
| { //skip delete at end of linked position |
| final LinkedPosition position= model.findPosition(new LinkedPosition( |
| this.document, offset, 0, LinkedPositionGroup.NO_STOP)); |
| if (position != null && offset >= position.getOffset() + position.getLength() && length == 0) { |
| event.doit= false; |
| } |
| return null; |
| } |
| } |
| return null; |
| } |
| } |
| |
| |
| private @Nullable String valueSuggestion; |
| |
| |
| public LinkedNamesAssistProposal(final ProposalParameters<TContext> parameters) { |
| super(parameters); |
| } |
| |
| |
| @Override |
| public Image getImage() { |
| return LtkUI.getUIResources().getImage(LtkUI.OBJ_TEXT_LINKEDRENAME_IMAGE_ID); |
| } |
| |
| |
| @Override |
| public boolean validate(final IDocument document, final int offset, |
| final @Nullable DocumentEvent event) { |
| return false; |
| } |
| |
| |
| @Override |
| public void apply(final ITextViewer viewer, final char trigger, final int stateMask, |
| final int offset) { |
| try { |
| // by default full word is selected by linked model ui |
| // instead we want to keep the original selection by default |
| int selectionStartOffset; |
| int selectionEndOffset; |
| { final Point selection= viewer.getSelectedRange(); |
| selectionStartOffset= selection.x; |
| selectionEndOffset= selection.x + selection.y; |
| } |
| final IDocument document= viewer.getDocument(); |
| if (document == null) { |
| return; |
| } |
| |
| final LinkedModeModel model= new LinkedModeModel(); |
| |
| final LinkedPositionGroup group= new LinkedPositionGroup(); |
| collectPositions(document, group); |
| if (group.isEmpty()) { |
| return; |
| } |
| model.addGroup(group); |
| |
| model.forceInstall(); |
| { final SourceEditor editor= getInvocationContext().getEditor(); |
| final TextEditToolSynchronizer synchronizer; |
| if (editor != null && (synchronizer= editor.getTextEditToolSynchronizer()) != null) { |
| synchronizer.install(model); |
| } |
| } |
| |
| final LinkedModeUI ui= new EditorLinkedModeUI(model, viewer); |
| ui.setExitPolicy(new DeleteBlockingExitPolicy(document)); |
| ui.setExitPosition(viewer, offset, 0, LinkedPositionGroup.NO_STOP); |
| ui.enter(); |
| |
| final String valueSuggestion= this.valueSuggestion; |
| final Position position0= group.getPositions()[0]; |
| if (valueSuggestion != null) { |
| document.replace(position0.getOffset(), position0.getLength(), valueSuggestion); |
| selectionStartOffset= position0.getOffset(); |
| selectionEndOffset= selectionStartOffset + valueSuggestion.length(); |
| } |
| else { |
| // correct selection if larger/outside of the initial position |
| final int positionEndOffset= position0.getOffset() + position0.getLength(); |
| if (selectionStartOffset < position0.getOffset()) { |
| selectionStartOffset= position0.getOffset(); |
| } |
| else if (selectionStartOffset > positionEndOffset) { |
| selectionStartOffset= positionEndOffset; |
| } |
| if (selectionEndOffset < selectionStartOffset) { |
| selectionEndOffset= selectionStartOffset; |
| } |
| else if (selectionEndOffset > positionEndOffset) { |
| selectionEndOffset= positionEndOffset; |
| } |
| } |
| |
| viewer.setSelectedRange(selectionStartOffset, selectionEndOffset - selectionStartOffset); |
| } |
| catch (final BadLocationException e) { |
| StatusManager.getManager().handle(new Status(IStatus.ERROR, BUNDLE_ID, -1, |
| "Error initializing linked rename.", e )); //$NON-NLS-1$ |
| } |
| } |
| |
| protected abstract void collectPositions(final IDocument document, final LinkedPositionGroup group) |
| throws BadLocationException; |
| |
| protected int addPosition(final LinkedPositionGroup group, final IDocument document, |
| final @Nullable Position position, final int idx) throws BadLocationException { |
| if (position != null) { |
| group.addPosition(new LinkedPosition(document, position.getOffset(), position.getLength(), idx)); |
| return idx + 1; |
| } |
| return idx; |
| } |
| |
| protected int addPosition(final LinkedPositionGroup group, final IDocument document, |
| final @Nullable TextRegion position, final int idx) throws BadLocationException { |
| if (position != null) { |
| group.addPosition(new LinkedPosition(document, position.getStartOffset(), position.getLength(), idx)); |
| return idx + 1; |
| } |
| return idx; |
| } |
| |
| |
| } |