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