blob: e7a29e4b19b72b27a4a3b8c52a1d2d18939ce287 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 2019 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 org.eclipse.core.runtime.IProgressMonitor;
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.contentassist.ICompletionProposalExtension5;
import org.eclipse.jface.text.contentassist.IContextInformation;
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.ecommons.text.ui.DefaultBrowserInformationInput;
import org.eclipse.statet.internal.ltk.ui.LTKUIPlugin;
import org.eclipse.statet.ltk.ui.LTKUI;
import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditor;
@NonNullByDefault
public abstract class LinkedNamesAssistProposal implements AssistProposal,
ICompletionProposalExtension5 {
/**
* 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 final AssistInvocationContext context;
private String label;
private String description;
private int relevance;
private String valueSuggestion;
public LinkedNamesAssistProposal(final AssistInvocationContext invocationContext) {
this.context= invocationContext;
}
protected void init(final String label, final String description, final int relevance) {
this.label= label;
this.description= description;
this.relevance= relevance;
}
@Override
public boolean validate(final IDocument document, final int offset, final DocumentEvent event) {
return false;
}
@Override
public void apply(final ITextViewer viewer, final char trigger, final int stateMask, final int offset) {
try {
Point seletion= viewer.getSelectedRange();
final IDocument document= viewer.getDocument();
final LinkedModeModel model= new LinkedModeModel();
final LinkedPositionGroup group= new LinkedPositionGroup();
collectPositions(document, group);
if (group.isEmpty()) {
return;
}
model.addGroup(group);
model.forceInstall();
{ final ISourceEditor editor= this.context.getEditor();
if (editor != null && editor.getTextEditToolSynchronizer() != null) {
editor.getTextEditToolSynchronizer().install(model);
}
}
final LinkedModeUI ui= new EditorLinkedModeUI(model, viewer);
ui.setExitPolicy(new DeleteBlockingExitPolicy(document));
ui.setExitPosition(viewer, offset, 0, LinkedPositionGroup.NO_STOP);
ui.enter();
if (this.valueSuggestion != null) {
final Position position= group.getPositions()[0];
document.replace(position.getOffset(), position.getLength(), this.valueSuggestion);
seletion= new Point(position.getOffset(), this.valueSuggestion.length());
}
viewer.setSelectedRange(seletion.x, seletion.y); // by default full word is selected, restore original selection
}
catch (final BadLocationException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, LTKUIPlugin.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 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;
}
@Override
public void apply(final IDocument document) {
throw new UnsupportedOperationException();
}
@Override
public Point getSelection(final IDocument document) {
return null;
}
@Override
public int getRelevance() {
return this.relevance;
}
@Override
public String getSortingString() {
return this.label;
}
@Override
public String getDisplayString() {
return this.label;
}
@Override
public Image getImage() {
return LTKUI.getImages().get(LTKUI.OBJ_TEXT_LINKEDRENAME_IMAGE_ID);
}
@Override
public String getAdditionalProposalInfo() {
return this.description;
}
@Override
public Object getAdditionalProposalInfo(final IProgressMonitor monitor) {
return new DefaultBrowserInformationInput(null, getDisplayString(), this.description,
DefaultBrowserInformationInput.FORMAT_TEXT_INPUT);
}
@Override
public @Nullable IContextInformation getContextInformation() {
return null;
}
}