/*=============================================================================# # Copyright (c) 2009-2016 Stephan Wahlbrink (WalWare.de) and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of the Eclipse Public License v1.0 # which accompanies this distribution, and is available at # http://www.eclipse.org/legal/epl-v10.html # # Contributors: # Stephan Wahlbrink - initial API and implementation #=============================================================================*/ package de.walware.docmlet.tex.internal.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.jface.text.IRegion; import de.walware.jcommons.collections.ImCollections; import de.walware.jcommons.collections.ImList; import de.walware.ecommons.ltk.LTKUtil; import de.walware.ecommons.ltk.core.model.IModelElement; import de.walware.ecommons.ltk.core.model.INameAccessSet; import de.walware.ecommons.ltk.core.model.ISourceStructElement; import de.walware.ecommons.ltk.core.model.ISourceUnit; import de.walware.ecommons.ltk.ui.sourceediting.ISourceEditor; import de.walware.ecommons.ltk.ui.sourceediting.assist.AssistInvocationContext; import de.walware.ecommons.ltk.ui.sourceediting.assist.AssistProposalCollector; import de.walware.ecommons.ltk.ui.sourceediting.assist.ContentAssist; import de.walware.ecommons.ltk.ui.sourceediting.assist.IContentAssistComputer; import de.walware.docmlet.tex.core.ITexCoreAccess; import de.walware.docmlet.tex.core.ITexProblemConstants; import de.walware.docmlet.tex.core.TexCore; import de.walware.docmlet.tex.core.ast.ControlNode; import de.walware.docmlet.tex.core.ast.ITexAstStatusConstants; import de.walware.docmlet.tex.core.ast.TexAst; import de.walware.docmlet.tex.core.ast.TexAst.NodeType; import de.walware.docmlet.tex.core.ast.TexAstNode; import de.walware.docmlet.tex.core.commands.Argument; import de.walware.docmlet.tex.core.commands.IEnvDefinitions; import de.walware.docmlet.tex.core.commands.IPreambleDefinitions; import de.walware.docmlet.tex.core.commands.LtxCommandDefinitions; import de.walware.docmlet.tex.core.commands.TexCommand; import de.walware.docmlet.tex.core.commands.TexCommandSet; import de.walware.docmlet.tex.core.model.ILtxModelInfo; import de.walware.docmlet.tex.core.model.ITexSourceElement; import de.walware.docmlet.tex.core.model.ITexSourceUnit; import de.walware.docmlet.tex.core.model.TexNameAccess; import de.walware.docmlet.tex.internal.ui.sourceediting.LtxAssistInvocationContext; public abstract class LtxElementsCompletionComputer implements IContentAssistComputer { public static class Math extends LtxElementsCompletionComputer { public Math() { } @Override protected boolean isMath() { return true; } } public static class Default extends LtxElementsCompletionComputer { public Default() { } @Override protected boolean isMath() { return false; } } private static List<TexCommand> PREAMBLE_DOCU_COMMANDS= ImCollections.newList( IPreambleDefinitions.PREAMBLE_documentclass_COMMAND ); private ITexCoreAccess fTexCoreAccess; protected LtxElementsCompletionComputer() { } @Override public void sessionStarted(final ISourceEditor editor, final ContentAssist assist) { final ISourceUnit su = editor.getSourceUnit(); if (su instanceof ITexSourceUnit) { fTexCoreAccess = ((ITexSourceUnit) su).getTexCoreAccess(); } } @Override public void sessionEnded() { fTexCoreAccess = null; } protected final ITexCoreAccess getTexCoreAccess() { return (fTexCoreAccess != null) ? fTexCoreAccess : 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 = LTKUtil.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().getOffset()) ) { 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 IRegion region = TexAst.getInnerRegion(argNode); if (region != null && region.getOffset() >= offset && offset <= region.getOffset()+region.getLength()) { 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 INameAccessSet<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 INameAccessSet<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.getOffset() <= offset && nameNode.getOffset() + nameNode.getLength() >= offset); } }