blob: 4f9fe72940dd1a5d64a19e515b338a893cf6dfc1 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 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.internal.docmlet.tex.ui.sourceediting;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.docmlet.tex.core.TexCore;
import org.eclipse.statet.docmlet.tex.core.TexCoreAccess;
import org.eclipse.statet.docmlet.tex.core.ast.ControlNode;
import org.eclipse.statet.docmlet.tex.core.ast.Group;
import org.eclipse.statet.docmlet.tex.core.ast.Label;
import org.eclipse.statet.docmlet.tex.core.ast.TexAst;
import org.eclipse.statet.docmlet.tex.core.ast.TexAstNode;
import org.eclipse.statet.docmlet.tex.core.ast.Text;
import org.eclipse.statet.docmlet.tex.core.commands.TexCommand;
import org.eclipse.statet.docmlet.tex.core.model.TexSourceUnit;
import org.eclipse.statet.docmlet.tex.core.source.LtxHeuristicTokenScanner;
import org.eclipse.statet.ltk.model.core.IModelManager;
import org.eclipse.statet.ltk.model.core.elements.ISourceUnit;
import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditor;
import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistInvocationContext;
@NonNullByDefault
public class LtxAssistInvocationContext extends AssistInvocationContext {
public class CommandCall {
private final ControlNode controlNode;
private final @Nullable TexAstNode[] argNodes;
private final int invocationArgIdx;
private CommandCall(final ControlNode controlNode) {
nonNullAssert(controlNode.getCommand());
this.controlNode= controlNode;
this.argNodes= TexAst.resolveArguments(controlNode);
this.invocationArgIdx= TexAst.getIndexAt(this.argNodes, getInvocationOffset());
}
/**
* Returns the node of the command call.
* @return the ast node
*/
public final ControlNode getControlNode() {
return this.controlNode;
}
@SuppressWarnings("null")
public final TexCommand getCommand() {
return this.controlNode.getCommand();
}
/**
* Returns the node of the argument.
* @param argIdx the index of the argument
* @return the list with ast nodes
*/
public final @Nullable TexAstNode getArgNode(final int argIdx) {
return this.argNodes[argIdx];
}
/**
* Returns the index of the argument at the {@link LtxAssistInvocationContext#getInvocationOffset() invocation offset}.
* @return the index of the argument
*/
public final int getInvocationArgIdx() {
return this.invocationArgIdx;
}
}
private @Nullable LtxHeuristicTokenScanner scanner;
private int invocationChecked;
private @Nullable CommandCall invocationDirect;
private @Nullable CommandCall invocationAny;
public LtxAssistInvocationContext(final ISourceEditor editor,
final int offset, final String contentType,
final boolean isProposal, final IProgressMonitor monitor) {
super(editor, offset, contentType,
(isProposal) ? IModelManager.MODEL_FILE : IModelManager.NONE, monitor );
}
public TexCoreAccess getTexCoreAccess() {
final ISourceUnit sourceUnit= getSourceUnit();
if (sourceUnit instanceof TexSourceUnit) {
return ((TexSourceUnit) sourceUnit).getTexCoreAccess();
}
return TexCore.getWorkbenchAccess();
}
public final LtxHeuristicTokenScanner getLtxHeuristicTokenScanner() {
LtxHeuristicTokenScanner scanner= this.scanner;
if (scanner == null) {
scanner= LtxHeuristicTokenScanner.create(getEditor().getDocumentContentInfo());
this.scanner= scanner;
}
return scanner;
}
@Override
protected String computeIdentifierPrefix(int offset) {
final IDocument document= getDocument();
try {
int start= offset;
SEARCH_START: while (offset > 0) {
final char c= document.getChar(offset - 1);
switch (c) {
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
start= --offset;
continue SEARCH_START;
case '\\':
start= offset;
while (true) {
if (--offset <= 0 || document.getChar(offset - 1) != '\\') {
start--;
break SEARCH_START;
}
if (--offset <= 0 || document.getChar(offset -1) != '\\') {
break SEARCH_START;
}
}
default:
break SEARCH_START;
}
}
return document.get(start, getInvocationOffset() - start);
}
catch (final BadLocationException e) {
return ""; //$NON-NLS-1$
}
}
private @Nullable CommandCall searchCommandCall(final boolean direct) {
if (getAstSelection().getCovering() instanceof TexAstNode) {
TexAstNode texNode= (TexAstNode)getAstSelection().getCovering();
if (direct) {
if (texNode instanceof Label || texNode instanceof Text) {
texNode= texNode.getTexParent();
}
}
else {
while (texNode != null && !(texNode instanceof Group)) {
texNode= texNode.getTexParent();
}
}
ControlNode controlNode;
if (texNode instanceof Group && texNode.getParent() instanceof ControlNode
&& (controlNode= (ControlNode)texNode.getParent()).getCommand() != null) {
return new CommandCall(controlNode);
}
if (texNode instanceof ControlNode
&& (controlNode= (ControlNode)texNode).getCommand() != null
&& controlNode.getArgsStartOffset() <= getStartOffset()
&& controlNode.getEndOffset() >= getEndOffset() ) {
return new CommandCall(controlNode);
}
}
return null;
}
public @Nullable CommandCall getCommandCall(final boolean direct) {
if (this.invocationChecked == 0) {
this.invocationChecked= 1;
this.invocationDirect= searchCommandCall(true);
}
if (direct) {
return this.invocationDirect;
}
if (this.invocationChecked == 1) {
this.invocationChecked= 2;
this.invocationAny= (this.invocationDirect != null) ? this.invocationDirect :
searchCommandCall(false);
}
return this.invocationAny;
}
}