blob: f32bedc6d530be4341c73616a2c82a17c689e537 [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.editors;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.text.core.TextRegion;
import org.eclipse.statet.docmlet.tex.core.ITexCoreAccess;
import org.eclipse.statet.docmlet.tex.core.ITexProblemConstants;
import org.eclipse.statet.docmlet.tex.core.TexCore;
import org.eclipse.statet.docmlet.tex.core.ast.ControlNode;
import org.eclipse.statet.docmlet.tex.core.ast.ITexAstStatusConstants;
import org.eclipse.statet.docmlet.tex.core.ast.TexAst;
import org.eclipse.statet.docmlet.tex.core.ast.TexAst.NodeType;
import org.eclipse.statet.docmlet.tex.core.ast.TexAstNode;
import org.eclipse.statet.docmlet.tex.core.commands.Argument;
import org.eclipse.statet.docmlet.tex.core.commands.IEnvDefinitions;
import org.eclipse.statet.docmlet.tex.core.commands.IPreambleDefinitions;
import org.eclipse.statet.docmlet.tex.core.commands.LtxCommandDefinitions;
import org.eclipse.statet.docmlet.tex.core.commands.TexCommand;
import org.eclipse.statet.docmlet.tex.core.commands.TexCommandSet;
import org.eclipse.statet.docmlet.tex.core.model.ILtxModelInfo;
import org.eclipse.statet.docmlet.tex.core.model.ITexSourceElement;
import org.eclipse.statet.docmlet.tex.core.model.ITexSourceUnit;
import org.eclipse.statet.docmlet.tex.core.model.TexNameAccess;
import org.eclipse.statet.internal.docmlet.tex.ui.sourceediting.LtxAssistInvocationContext;
import org.eclipse.statet.ltk.core.LTKUtils;
import org.eclipse.statet.ltk.model.core.elements.IModelElement;
import org.eclipse.statet.ltk.model.core.elements.ISourceStructElement;
import org.eclipse.statet.ltk.model.core.elements.ISourceUnit;
import org.eclipse.statet.ltk.model.core.elements.NameAccessSet;
import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditor;
import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistInvocationContext;
import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistProposalCollector;
import org.eclipse.statet.ltk.ui.sourceediting.assist.ContentAssist;
import org.eclipse.statet.ltk.ui.sourceediting.assist.IContentAssistComputer;
public abstract class LtxElementCompletionComputer implements IContentAssistComputer {
public static class Math extends LtxElementCompletionComputer {
public Math() {
}
@Override
protected boolean isMath() {
return true;
}
}
public static class Default extends LtxElementCompletionComputer {
public Default() {
}
@Override
protected boolean isMath() {
return false;
}
}
private static List<TexCommand> PREAMBLE_DOCU_COMMANDS= ImCollections.newList(
IPreambleDefinitions.PREAMBLE_documentclass_COMMAND
);
private ITexCoreAccess texCoreAccess;
protected LtxElementCompletionComputer() {
}
@Override
public void sessionStarted(final ISourceEditor editor, final ContentAssist assist) {
final ISourceUnit su= editor.getSourceUnit();
if (su instanceof ITexSourceUnit) {
this.texCoreAccess= ((ITexSourceUnit) su).getTexCoreAccess();
}
}
@Override
public void sessionEnded() {
this.texCoreAccess= null;
}
protected final ITexCoreAccess getTexCoreAccess() {
return (this.texCoreAccess != null) ? this.texCoreAccess : TexCore.getWorkbenchAccess();
}
protected abstract boolean isMath();
@Override
public IStatus computeCompletionProposals(final AssistInvocationContext context, final int mode,
final AssistProposalCollector proposals, final IProgressMonitor monitor) {
final String prefix= context.getIdentifierPrefix();
final ILtxModelInfo modelInfo= (context.getModelInfo() instanceof ILtxModelInfo) ?
(ILtxModelInfo) context.getModelInfo() : null;
final TexCommandSet commandSet= getTexCoreAccess().getTexCommandSet();
if (prefix.length() > 0 && prefix.charAt(0) == '\\') {
final int offset= context.getInvocationOffset() - prefix.length() + 1;
addCommands(context, prefix, (isMath()) ?
commandSet.getLtxMathCommandsASorted() : commandSet.getLtxTextCommandsASorted(),
(modelInfo != null) ? modelInfo.getCustomCommandMap().values() : null,
proposals );
if (modelInfo != null && !isMath()) {
if (modelInfo.getSourceElement() != null) {
final List<? extends ISourceStructElement> elements= modelInfo
.getSourceElement().getSourceChildren(null);
final ISourceStructElement element= LTKUtils.getCoveringSourceElement(elements, offset);
if (element != null
&& (element.getElementType() & IModelElement.MASK_C2) == ITexSourceElement.C2_PREAMBLE) {
addCommands(context, prefix,
commandSet.getLtxPreambleCommandsASorted(), null,
proposals );
}
else if (prefix.startsWith("\\docu") //$NON-NLS-1$
&& (elements.size() == 0 || offset < elements.get(0).getSourceRange().getStartOffset()) ) {
addCommands(context, prefix, PREAMBLE_DOCU_COMMANDS, null, proposals);
}
if (!isMath() && context.getAstSelection().getCovering() instanceof TexAstNode) {
TexAstNode texNode= (TexAstNode) context.getAstSelection().getCovering();
while (texNode != null) {
TexCommand command;
if (texNode.getNodeType() == TexAst.NodeType.CONTROL
&& (command= ((ControlNode) texNode).getCommand()) != null
&& (command.getType() & TexCommand.MASK_C2) == TexCommand.C2_PREAMBLE_CONTROLDEF) {
addCommands(context, prefix,
commandSet.getLtxMathCommandsASorted(), null, proposals );
break;
}
texNode= texNode.getTexParent();
}
}
}
}
}
else if (context instanceof LtxAssistInvocationContext) {
final LtxAssistInvocationContext texContext= (LtxAssistInvocationContext) context;
final int argIdx= texContext.getInvocationArgIdx();
if (argIdx >= 0) {
final TexCommand command= texContext.getInvocationControlNode().getCommand();
final Argument argDef= command.getArguments().get(argIdx);
final TexAstNode argNode= texContext.getInvocationArgNodes()[argIdx];
final int offset= texContext.getInvocationOffset() - prefix.length();
final TextRegion region= TexAst.getInnerRegion(argNode);
if (region != null
&& region.getStartOffset() <= offset && offset <= region.getEndOffset() ) {
if (argIdx == 0
&& ((command.getType() & TexCommand.MASK_MAIN) == TexCommand.GENERICENV
|| (command.getType() & TexCommand.MASK_MAIN) == TexCommand.ENV )) {
final List<String> prefered= new ArrayList<>();
if (command == IEnvDefinitions.GENERICENV_end_COMMAND) {
TexAstNode node= texContext.getInvocationControlNode();
while (node != null) {
if (node.getNodeType() == NodeType.ENVIRONMENT
&& (prefered.isEmpty()
|| (node.getStatusCode() & ITexProblemConstants.MASK_12) == ITexAstStatusConstants.STATUS2_ENV_NOT_CLOSED )) {
final String name= node.getText();
if (!name.isEmpty() && !prefered.contains(name)) {
prefered.add(name);
}
}
node= node.getTexParent();
}
}
addEnvs(context, prefix, (isMath()) ?
commandSet.getLtxMathEnvsASorted() : commandSet.getLtxTextEnvsASorted(),
(modelInfo != null) ? modelInfo.getCustomEnvMap().values() : null,
prefered, proposals );
}
else {
switch (argDef.getContent()) {
case Argument.LABEL_REFLABEL_DEF:
if (modelInfo != null) {
final NameAccessSet<TexNameAccess> labels= modelInfo.getLabels();
LABELS: for (final String label : labels.getNames()) {
final ImList<TexNameAccess> accessList= labels.getAllInUnit(label);
boolean isDef= false;
for (final TexNameAccess access : accessList) {
if (access.isWriteAccess()) {
if (isDef(access, offset)) {
isDef= true;
}
else {
proposals.add(new TexLabelCompletionProposal(context,
offset, accessList.get(0), 94));
continue LABELS;
}
}
}
if (isDef) {
continue LABELS;
}
proposals.add(new TexLabelCompletionProposal(context,
offset, accessList.get(0), 95));
}
}
break;
case Argument.LABEL_REFLABEL_REF:
if (modelInfo != null) {
final NameAccessSet<TexNameAccess> labels= modelInfo.getLabels();
LABELS: for (final String label : labels.getNames()) {
final ImList<TexNameAccess> accessList= labels.getAllInUnit(label);
for (final TexNameAccess access : accessList) {
if (access.isWriteAccess()) {
proposals.add(new TexLabelCompletionProposal(context,
offset, accessList.get(0), 95));
continue LABELS;
}
}
if (accessList.size() == 1 && isDef(accessList.get(0), offset)) {
continue LABELS;
}
proposals.add(new TexLabelCompletionProposal(context,
offset, accessList.get(0), 94));
}
}
}
}
}
}
}
return Status.OK_STATUS;
}
@Override
public IStatus computeInformationProposals(final AssistInvocationContext context,
final AssistProposalCollector tenders, final IProgressMonitor monitor) {
return null;
}
private void addCommands(final AssistInvocationContext context, final String prefix,
final List<TexCommand> commands, final Collection<TexCommand> commands2,
final AssistProposalCollector proposals) {
final int offset= context.getInvocationOffset() - prefix.length() + 1;
final int length= prefix.length() - 1;
for (final TexCommand command : commands) {
if ((prefix.length() == 1 || command.getControlWord().regionMatches(true, 0, prefix, 1, length))
&& (command.getType() & TexCommand.MASK_C2) != TexCommand.C2_SYMBOL_CHAR) {
proposals.add(new LtxCommandCompletionProposal(context, offset, command));
}
}
if (commands2 != null) {
for (final TexCommand command : commands2) {
if ((prefix.length() == 1 || command.getControlWord().regionMatches(true, 0, prefix, 1, length))
&& (command.getType() & TexCommand.MASK_C2) != TexCommand.C2_SYMBOL_CHAR) {
proposals.add(new LtxCommandCompletionProposal(context, offset, command));
}
}
}
}
private void addEnvs(final AssistInvocationContext context, final String prefix,
final List<TexCommand> envs, final Collection<TexCommand> envs2,
final List<String> prefered,
final AssistProposalCollector proposals) {
final int offset= context.getInvocationOffset() - prefix.length();
final int length= prefix.length();
final List<String> addedPrefered= new ArrayList<>(prefered.size());
for (final TexCommand env : envs) {
if (prefix.length() == 0 || env.getControlWord().regionMatches(true, 0, prefix, 0, length)) {
final int idx= prefered.indexOf(env.getControlWord());
proposals.add(new LtxCommandCompletionProposal.Env(context, offset, env,
(idx >= 0 && idx < 5) ? 5-idx : 0));
if (idx >= 0) {
addedPrefered.add(env.getControlWord());
}
}
}
if (envs2 != null) {
for (final TexCommand env : envs2) {
if (prefix.length() == 0 || env.getControlWord().regionMatches(true, 0, prefix, 0, length)) {
final int idx= prefered.indexOf(env.getControlWord());
proposals.add(new LtxCommandCompletionProposal.Env(context, offset, env,
(idx >= 0 && idx < 5) ? 5-idx : 0));
if (idx >= 0) {
addedPrefered.add(env.getControlWord());
}
}
}
}
for (final String name : prefered) {
if ((prefix.length() == 0 || name.regionMatches(true, 0, prefix, 0, length))
&& !addedPrefered.contains(name) ) {
final int idx= prefered.indexOf(name);
TexCommand env= LtxCommandDefinitions.getEnv(name);
if (env == null) {
env= new TexCommand(TexCommand.C2_ENV_OTHER_BEGIN, name, "(open environment)");
}
proposals.add(new LtxCommandCompletionProposal.Env(context, offset, env,
(idx >= 0 && idx < 5) ? 5-idx : 0));
}
}
}
private boolean isDef(final TexNameAccess access, final int offset) {
final TexAstNode nameNode= access.getNameNode();
return (nameNode != null
&& nameNode.getStartOffset() <= offset
&& nameNode.getEndOffset() >= offset );
}
}