/*=============================================================================#
# Copyright (c) 2008-2016 Stephan Wahlbrink (WalWare.de) and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.ecommons.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 de.walware.ecommons.text.core.ITextRegion;
import de.walware.ecommons.ltk.AstInfo;
import de.walware.ecommons.ltk.ast.AstSelection;
import de.walware.ecommons.ltk.core.model.ISourceUnit;
import de.walware.ecommons.ltk.core.model.ISourceUnitModelInfo;
import de.walware.ecommons.ltk.ui.sourceediting.ISourceEditor;
public class AssistInvocationContext implements IQuickAssistInvocationContext, ITextRegion {
private final ISourceEditor editor;
private final SourceViewer sourceViewer;
private final int invocationOffset;
private final int selectionOffset;
private final int selectionLength;
private final String invocationContentType;
private String invocationPrefix;
private final ISourceUnit sourceUnit;
private AstInfo astInfo;
private ISourceUnitModelInfo modelInfo;
private AstSelection invocationAstSelection;
private AstSelection astSelection;
int session;
public AssistInvocationContext(final ISourceEditor 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 ISourceEditor 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);
}
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 ISourceEditor editor, final int offset) {
return (this.editor == editor
&& this.invocationOffset == offset
&& isInitialState() );
}
protected String getModelTypeId() {
return null;
}
public ISourceEditor 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 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() {
if (this.invocationPrefix == null) {
try {
this.invocationPrefix= computeIdentifierPrefix(getInvocationOffset());
if (this.invocationPrefix == null) {
this.invocationPrefix= ""; // prevent recomputing //$NON-NLS-1$
}
}
catch (final BadPartitioningException | BadLocationException e) {
this.invocationPrefix= ""; //$NON-NLS-1$
throw new RuntimeException(e);
}
}
return this.invocationPrefix;
}
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 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 ISourceUnit getSourceUnit() {
return this.sourceUnit;
}
public AstInfo getAstInfo() {
return this.astInfo;
}
public ISourceUnitModelInfo getModelInfo() {
return this.modelInfo;
}
public AstSelection getInvocationAstSelection() {
if (this.invocationAstSelection == null && this.astInfo != null && this.astInfo.root != null) {
this.invocationAstSelection= AstSelection.search(this.astInfo.root,
getInvocationOffset(), getInvocationOffset(), AstSelection.MODE_COVERING_SAME_LAST );
}
return this.invocationAstSelection;
}
public AstSelection getAstSelection() {
if (this.astSelection == null && this.astInfo != null && this.astInfo.root != null) {
this.astSelection= AstSelection.search(this.astInfo.root,
getOffset(), getOffset() + getLength(), AstSelection.MODE_COVERING_SAME_LAST );
}
return this.astSelection;
}
}