blob: d741f941302967a47b3e2574d5341057dd31a7ad [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.jface.text.BadLocationException;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.text.core.SearchPattern;
import org.eclipse.statet.jcommons.text.core.TextRegion;
import org.eclipse.statet.docmlet.base.core.DocmlSearchPattern;
import org.eclipse.statet.docmlet.tex.core.TexProblemConstants;
import org.eclipse.statet.docmlet.tex.core.ast.ControlNode;
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.ast.TexAstStatusConstants;
import org.eclipse.statet.docmlet.tex.core.commands.Argument;
import org.eclipse.statet.docmlet.tex.core.commands.EnvDefinitions;
import org.eclipse.statet.docmlet.tex.core.commands.LtxCommandDefinitions;
import org.eclipse.statet.docmlet.tex.core.commands.PreambleDefinitions;
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.TexNameAccess;
import org.eclipse.statet.docmlet.tex.core.source.LtxHeuristicTokenScanner;
import org.eclipse.statet.internal.docmlet.tex.ui.editors.LtxCommandCompletionProposal.LtxCommandProposalParameters;
import org.eclipse.statet.internal.docmlet.tex.ui.editors.TexLabelCompletionProposal.TexLabelProposalParameters;
import org.eclipse.statet.internal.docmlet.tex.ui.sourceediting.LtxAssistInvocationContext;
import org.eclipse.statet.internal.docmlet.tex.ui.sourceediting.LtxAssistInvocationContext.CommandCall;
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.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.ContentAssistComputer;
@NonNullByDefault
public abstract class LtxElementCompletionComputer implements ContentAssistComputer {
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(
PreambleDefinitions.PREAMBLE_documentclass_COMMAND
);
private int searchMatchRules;
protected LtxElementCompletionComputer() {
}
@Override
public void onSessionStarted(final ISourceEditor editor, final ContentAssist assist) {
int matchRules= SearchPattern.PREFIX_MATCH;
if (assist.getShowSubstringMatches()) {
matchRules |= SearchPattern.SUBSTRING_MATCH;
}
this.searchMatchRules= matchRules;
}
@Override
public void onSessionEnded() {
}
protected int getSearchMatchRules() {
return this.searchMatchRules;
}
protected int getLabelSearchMatchRules() {
int rules= this.searchMatchRules;
if ((rules & DocmlSearchPattern.SUBSTRING_MATCH) != 0) {
rules|= DocmlSearchPattern.LABEL_SUBSTRING_MATCH;
}
return rules;
}
protected abstract boolean isMath();
@Override
public void computeCompletionProposals(final AssistInvocationContext context, final int mode,
final AssistProposalCollector proposals, final IProgressMonitor monitor) {
if (context instanceof LtxAssistInvocationContext) {
computeCompletionProposals((LtxAssistInvocationContext) context, mode, proposals, monitor);
}
}
protected void computeCompletionProposals(final LtxAssistInvocationContext 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= context.getTexCoreAccess().getTexCommandSet();
if (prefix.length() > 0 && prefix.charAt(0) == '\\') {
final int offset= context.getInvocationOffset() - prefix.length() + 1;
addCommandProposals(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) {
addCommandProposals(context, prefix,
commandSet.getLtxPreambleCommandsASorted(), null,
proposals );
}
else if (prefix.startsWith("\\docu") //$NON-NLS-1$
&& (elements.size() == 0 || offset < elements.get(0).getSourceRange().getStartOffset()) ) {
addCommandProposals(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) {
addCommandProposals(context, prefix,
commandSet.getLtxMathCommandsASorted(), null, proposals );
break;
}
texNode= texNode.getTexParent();
}
}
}
}
}
else if (context instanceof LtxAssistInvocationContext) {
final LtxAssistInvocationContext texContext= context;
final CommandCall commandCall= texContext.getCommandCall(true);
final int argIdx;
if (commandCall != null && (argIdx= commandCall.getInvocationArgIdx()) >= 0) {
final TexCommand command= commandCall.getCommand();
final Argument argDef= command.getArguments().get(argIdx);
final TexAstNode argNode= commandCall.getArgNode(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 == EnvDefinitions.GENERICENV_end_COMMAND) {
TexAstNode node= commandCall.getControlNode();
while (node != null) {
if (node.getNodeType() == NodeType.ENVIRONMENT
&& (prefered.isEmpty()
|| (node.getStatusCode() & TexProblemConstants.MASK_12) == TexAstStatusConstants.STATUS2_ENV_NOT_CLOSED )) {
final String name= node.getText();
if (!name.isEmpty() && !prefered.contains(name)) {
prefered.add(name);
}
}
node= node.getTexParent();
}
}
addEnvProposals(context, prefix, (isMath()) ?
commandSet.getLtxMathEnvsASorted() : commandSet.getLtxTextEnvsASorted(),
(modelInfo != null) ? modelInfo.getCustomEnvMap().values() : null,
prefered, proposals );
}
else {
if (modelInfo != null) {
switch (argDef.getContent()) {
case Argument.LABEL_REFLABEL_DEF:
addLabelDefProposals(context, argNode, modelInfo.getLabels(), proposals);
break;
case Argument.LABEL_REFLABEL_REF:
addLabelRefProposals(context, argNode, modelInfo.getLabels(), proposals);
break;
}
}
}
}
}
}
}
@Override
public void computeInformationProposals(final AssistInvocationContext context,
final AssistProposalCollector tenders, final IProgressMonitor monitor) {
if (context instanceof LtxAssistInvocationContext) {
computeInformationProposals0((LtxAssistInvocationContext)context, tenders, monitor);
}
}
protected void computeInformationProposals0(final LtxAssistInvocationContext context,
final AssistProposalCollector tenders, final IProgressMonitor monitor) {
if (context.getModelInfo() == null) {
return;
}
final CommandCall commandCall= context.getCommandCall(false);
if (commandCall != null) {
final LtxCommandProposalParameters parameters= new LtxCommandProposalParameters(
context, commandCall.getControlNode().getArgsStartOffset() );
parameters.command= commandCall.getCommand();
tenders.add(new LtxCommandCompletionProposal.ContextInformationProposal(parameters));
}
}
private void addCommandProposals(final LtxAssistInvocationContext context, final String prefix,
final List<TexCommand> commands, final Collection<TexCommand> commands2,
final AssistProposalCollector proposals) {
final LtxCommandProposalParameters parameters= new LtxCommandProposalParameters(
context, context.getInvocationOffset() - prefix.length() + 1,
new DocmlSearchPattern(getSearchMatchRules(), prefix.substring(1)), 95 );
for (final TexCommand command : commands) {
if ((parameters.matchesNamePattern(command.getControlWord()))
&& (command.getType() & TexCommand.MASK_C2) != TexCommand.C2_SYMBOL_CHAR) {
parameters.command= command;
proposals.add(new LtxCommandCompletionProposal(parameters));
}
}
if (commands2 != null) {
for (final TexCommand command : commands2) {
if ((parameters.matchesNamePattern(command.getControlWord()))
&& (command.getType() & TexCommand.MASK_C2) != TexCommand.C2_SYMBOL_CHAR) {
parameters.command= command;
proposals.add(new LtxCommandCompletionProposal(parameters));
}
}
}
}
private void addEnvProposals(final LtxAssistInvocationContext context, final String prefix,
final List<TexCommand> envs, final Collection<TexCommand> envs2,
final List<String> prefered,
final AssistProposalCollector proposals) {
final LtxCommandProposalParameters parameters= new LtxCommandProposalParameters(
context, context.getInvocationOffset() - prefix.length(),
new DocmlSearchPattern(getSearchMatchRules(), prefix), 95 );
final List<String> addedPrefered= new ArrayList<>(prefered.size());
for (final TexCommand env : envs) {
if (parameters.matchesNamePattern(env.getControlWord())) {
final int idx= prefered.indexOf(env.getControlWord());
parameters.command= env;
parameters.baseRelevance= (idx >= 0 && idx < 5) ? 5 - idx : 0;
proposals.add(new LtxCommandCompletionProposal.Env(parameters));
if (idx >= 0) {
addedPrefered.add(env.getControlWord());
}
}
}
if (envs2 != null) {
for (final TexCommand env : envs2) {
if (parameters.matchesNamePattern(env.getControlWord())) {
final int idx= prefered.indexOf(env.getControlWord());
parameters.command= env;
parameters.baseRelevance= (idx >= 0 && idx < 5) ? 5 - idx : 0;
proposals.add(new LtxCommandCompletionProposal.Env(parameters));
if (idx >= 0) {
addedPrefered.add(env.getControlWord());
}
}
}
}
for (final String name : prefered) {
if ((parameters.matchesNamePattern(name))
&& !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)");
}
parameters.command= env;
parameters.baseRelevance= (idx >= 0 && idx < 5) ? 5 - idx : 0;
proposals.add(new LtxCommandCompletionProposal.Env(parameters));
}
}
}
private @Nullable String getLabelPrefix(final LtxAssistInvocationContext context,
final TexAstNode argNode) {
try {
final LtxHeuristicTokenScanner scanner= context.getLtxHeuristicTokenScanner();
scanner.configure(context.getDocument());
final int startOffset= scanner.findAnyNonBlankForward(argNode.getStartOffset() + 1,
argNode.getEndOffset(), true );
return context.getDocument().get(startOffset, context.getInvocationOffset() - startOffset);
}
catch (final BadLocationException e) {
return null;
}
}
private void addLabelDefProposals(final LtxAssistInvocationContext context,
final TexAstNode argNode, final NameAccessSet<TexNameAccess> labels,
final AssistProposalCollector proposals) {
final String prefix= getLabelPrefix(context, argNode);
if (prefix == null) {
return;
}
final TexLabelProposalParameters parameters= new TexLabelProposalParameters(
context, context.getInvocationOffset() - prefix.length(),
new DocmlSearchPattern(getLabelSearchMatchRules(), prefix) );
LABELS: for (final String label : labels.getNames()) {
if (parameters.matchesNamePattern(label)) {
final ImList<TexNameAccess> accessList= labels.getAllInUnit(label);
parameters.access= accessList.get(0);
boolean isDef= false;
for (final TexNameAccess access : accessList) {
if (access.isWriteAccess()) {
if (isDef(access, parameters.replacementOffset)) {
isDef= true;
}
else {
parameters.baseRelevance= 94;
proposals.add(new TexLabelCompletionProposal(parameters));
continue LABELS;
}
}
}
if (isDef) {
continue LABELS;
}
parameters.baseRelevance= 95;
proposals.add(new TexLabelCompletionProposal(parameters));
}
}
}
private void addLabelRefProposals(final LtxAssistInvocationContext context,
final TexAstNode argNode, final NameAccessSet<TexNameAccess> labels,
final AssistProposalCollector proposals) {
final String prefix= getLabelPrefix(context, argNode);
if (prefix == null) {
return;
}
final TexLabelProposalParameters parameters= new TexLabelProposalParameters(
context, context.getInvocationOffset() - prefix.length(),
new DocmlSearchPattern(getLabelSearchMatchRules(), prefix) );
LABELS: for (final String label : labels.getNames()) {
if (parameters.matchesNamePattern(label)) {
final ImList<TexNameAccess> accessList= labels.getAllInUnit(label);
parameters.access= accessList.get(0);
for (final TexNameAccess access : accessList) {
if (access.isWriteAccess()) {
parameters.baseRelevance= 95;
proposals.add(new TexLabelCompletionProposal(parameters));
continue LABELS;
}
}
if (accessList.size() == 1 && isDef(accessList.get(0), parameters.replacementOffset)) {
continue LABELS;
}
parameters.baseRelevance= 94;
proposals.add(new TexLabelCompletionProposal(parameters));
}
}
}
private boolean isDef(final TexNameAccess access, final int offset) {
final TexAstNode nameNode= access.getNameNode();
return (nameNode != null
&& nameNode.getStartOffset() <= offset
&& nameNode.getEndOffset() >= offset );
}
}