| /*=============================================================================# |
| # 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; |
| } |
| |
| } |