blob: 1436b993cb368583736f066f77d9cd95a21e6263 [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 org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPartitioningException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.swt.graphics.Point;
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.ast.core.AstInfo;
import org.eclipse.statet.ltk.ast.core.util.AstSelection;
import org.eclipse.statet.ltk.model.core.element.SourceUnit;
import org.eclipse.statet.ltk.model.core.element.SourceUnitModelInfo;
import org.eclipse.statet.ltk.ui.sourceediting.SourceEditor;
@NonNullByDefault
public class AssistInvocationContext implements IQuickAssistInvocationContext, TextRegion {
private final SourceEditor editor;
private final SourceViewer sourceViewer;
private final int invocationOffset;
private final int selectionOffset;
private final int selectionLength;
private final String invocationContentType;
private @Nullable String invocationPrefix;
private final @Nullable SourceUnit sourceUnit;
private @Nullable AstInfo astInfo;
private @Nullable SourceUnitModelInfo modelInfo;
private @Nullable AstSelection invocationAstSelection;
private @Nullable AstSelection astSelection;
int session;
public AssistInvocationContext(final SourceEditor editor,
final int offset, final String contentType,
final int synch, final IProgressMonitor monitor) {
this.editor= editor;
this.sourceViewer= editor.getViewer();
this.invocationOffset= offset;
final Point selectedRange= this.sourceViewer.getSelectedRange();
this.selectionOffset= selectedRange.x;
this.selectionLength= selectedRange.y;
this.invocationContentType= contentType;
this.sourceUnit= editor.getSourceUnit();
init(synch, monitor);
}
public AssistInvocationContext(final SourceEditor editor,
final IRegion region, final String contentType,
final int synch, final IProgressMonitor monitor) {
if (region.getOffset() < 0 || region.getLength() < 0) {
throw new IllegalArgumentException("region"); //$NON-NLS-1$
}
this.editor= editor;
this.sourceViewer= editor.getViewer();
this.invocationOffset= region.getOffset();
this.selectionOffset= region.getOffset();
this.selectionLength= region.getLength();
this.invocationContentType= contentType;
this.sourceUnit= editor.getSourceUnit();
init(synch, monitor);
}
public AssistInvocationContext(final SourceEditor editor,
final TextRegion region, final String contentType,
final int synch, final IProgressMonitor monitor) {
if (region.getStartOffset() < 0 || region.getLength() < 0) {
throw new IllegalArgumentException("region"); //$NON-NLS-1$
}
this.editor= editor;
this.sourceViewer= editor.getViewer();
this.invocationOffset= region.getStartOffset();
this.selectionOffset= region.getStartOffset();
this.selectionLength= region.getLength();
this.invocationContentType= contentType;
this.sourceUnit= editor.getSourceUnit();
init(synch, monitor);
}
private void init(final int synch, final IProgressMonitor monitor) {
if (this.sourceUnit != null) {
final String type= getModelTypeId();
// TODO check if/how we can reduce model requirement in content assistant
this.modelInfo= this.sourceUnit.getModelInfo(type, synch, monitor);
this.astInfo= this.modelInfo != null ? this.modelInfo.getAst() : this.sourceUnit.getAstInfo(type, true, monitor);
}
}
boolean isInitialState() {
final Point selectedRange= this.sourceViewer.getSelectedRange();
return (selectedRange.x == getOffset() && selectedRange.y == getLength());
}
protected boolean reuse(final SourceEditor editor, final int offset) {
return (this.editor == editor
&& this.invocationOffset == offset
&& isInitialState() );
}
protected @Nullable String getModelTypeId() {
return null;
}
public SourceEditor getEditor() {
return this.editor;
}
@Override
public SourceViewer getSourceViewer() {
return this.sourceViewer;
}
public IDocument getDocument() {
return getSourceViewer().getDocument();
}
/**
* Returns the invocation (cursor) offset.
*
* @return the invocation offset
*/
public final int getInvocationOffset() {
return this.invocationOffset;
}
/**
* Returns the text selection offset.
*
* @return offset of selection
*/
@Override
public int getStartOffset() {
return this.selectionOffset;
}
@Override
public int getOffset() {
return this.selectionOffset;
}
@Override
public int getEndOffset() {
return this.selectionOffset + this.selectionLength;
}
/**
* Returns the text selection length
*
* @return length of selection (>= 0)
*/
@Override
public int getLength() {
return this.selectionLength;
}
public final String getInvocationContentType() {
return this.invocationContentType;
}
public String getIdentifierPrefix() {
String prefix= this.invocationPrefix;
if (prefix == null) {
try {
prefix= computeIdentifierPrefix(getInvocationOffset());
if (prefix == null) {
prefix= ""; // prevent recomputing //$NON-NLS-1$
}
}
catch (final BadPartitioningException | BadLocationException e) {
prefix= ""; //$NON-NLS-1$
throw new RuntimeException(e);
}
finally {
this.invocationPrefix= prefix;
}
}
return prefix;
}
public int getIdentifierOffset() {
return getInvocationOffset() - getIdentifierPrefix().length();
}
/**
* Computes the prefix separated by a white space ( {@link Character#isWhitespace(char)}
* immediately precedes the invocation offset.
*
* @return the prefix preceding the content assist invocation offset, <code>null</code> if
* there is no document
*/
protected @Nullable String computeIdentifierPrefix(final int offset)
throws BadPartitioningException, BadLocationException {
final IDocument document= getDocument();
final ITypedRegion partition= TextUtilities.getPartition(document,
getEditor().getDocumentContentInfo().getPartitioning(), offset, true );
final int bound= partition.getOffset();
int prefixOffset= offset;
for (; prefixOffset > bound; prefixOffset--) {
if (Character.isWhitespace(document.getChar(prefixOffset - 1))) {
break;
}
}
return document.get(prefixOffset, offset - prefixOffset);
}
public @Nullable SourceUnit getSourceUnit() {
return this.sourceUnit;
}
public @Nullable AstInfo getAstInfo() {
return this.astInfo;
}
public @Nullable SourceUnitModelInfo getModelInfo() {
return this.modelInfo;
}
public AstSelection getInvocationAstSelection() {
AstSelection selection= this.invocationAstSelection;
if (selection == null) {
if (this.astInfo == null || this.astInfo.getRoot() == null) {
throw new UnsupportedOperationException("AST is missing.");
}
selection= AstSelection.search(this.astInfo.getRoot(),
getInvocationOffset(), getInvocationOffset(), AstSelection.MODE_COVERING_SAME_LAST );
this.invocationAstSelection= selection;
}
return selection;
}
public AstSelection getAstSelection() {
AstSelection selection= this.astSelection;
if (selection == null) {
if (this.astInfo == null || this.astInfo.getRoot() == null) {
throw new UnsupportedOperationException("AST is missing.");
}
selection= AstSelection.search(this.astInfo.getRoot(),
getOffset(), getOffset() + getLength(), AstSelection.MODE_COVERING_SAME_LAST );
this.astSelection= selection;
}
return selection;
}
public int getTabSize() {
return 4;
}
}