blob: e7a2a82dc79b06ce9921df373c07c8ebeeec8b5d [file] [log] [blame]
/*=============================================================================#
# 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;
}
}