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