| /*=============================================================================# |
| # Copyright (c) 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.sourceediting; |
| |
| import static org.eclipse.statet.docmlet.tex.core.ast.TexAstStatusConstants.STATUS2_GROUP_NOT_CLOSED; |
| import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert; |
| |
| import org.eclipse.jface.text.AbstractDocument; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.TextPresentation; |
| import org.eclipse.jface.text.contentassist.IContextInformation; |
| import org.eclipse.jface.text.contentassist.IContextInformationPresenter; |
| import org.eclipse.jface.text.contentassist.IContextInformationValidator; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyleRange; |
| |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.text.core.input.OffsetStringParserInput; |
| |
| import org.eclipse.statet.ecommons.text.core.FragmentDocument; |
| import org.eclipse.statet.ecommons.text.core.treepartitioner.ITreePartitionNode; |
| import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionUtils; |
| |
| import org.eclipse.statet.docmlet.tex.core.TexCore; |
| 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.LtxParser; |
| import org.eclipse.statet.docmlet.tex.core.ast.TexAst; |
| import org.eclipse.statet.docmlet.tex.core.ast.TexAstNode; |
| import org.eclipse.statet.docmlet.tex.core.model.ILtxModelInfo; |
| import org.eclipse.statet.docmlet.tex.core.model.TexSourceUnit; |
| import org.eclipse.statet.docmlet.tex.core.parser.NowebLtxLexer; |
| import org.eclipse.statet.docmlet.tex.core.source.LtxPartitionNodeScanner; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnit; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnitModelInfo; |
| import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditor; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistCompletionInformationProposalWrapper; |
| |
| |
| @NonNullByDefault |
| public class LtxContextInformationValidator implements IContextInformationValidator, IContextInformationPresenter { |
| |
| |
| private final ISourceEditor sourceEditor; |
| |
| private @Nullable LtxArgumentListContextInformation info; |
| |
| private long scannedArgsStamp; |
| private @Nullable ControlNode scannedArgs; |
| |
| private int lastPresentation= -2; |
| |
| |
| public LtxContextInformationValidator(final ISourceEditor editor) { |
| this.sourceEditor= editor; |
| } |
| |
| |
| @Override |
| public void install(IContextInformation info, final ITextViewer viewer, final int offset) { |
| if (info instanceof AssistCompletionInformationProposalWrapper) { |
| info= ((AssistCompletionInformationProposalWrapper)info).getContextInformation(); |
| } |
| |
| this.scannedArgs= null; |
| this.lastPresentation= -2; |
| |
| if (info instanceof LtxArgumentListContextInformation |
| && viewer == this.sourceEditor.getViewer()) { |
| this.info= (LtxArgumentListContextInformation)info; |
| } |
| else { |
| this.info= null; |
| return; |
| } |
| } |
| |
| @Override |
| public boolean isContextInformationValid(final int offset) { |
| final LtxArgumentListContextInformation info= this.info; |
| if (info == null) { |
| return false; |
| } |
| if (offset < info.getContextInformationPosition() |
| || offset > this.sourceEditor.getViewer().getDocument().getLength()) { |
| return false; |
| } |
| final ControlNode args= getScannedArgs(); |
| if (args != null) { |
| return (offset <= args.getEndOffset()); |
| } |
| return (offset == info.getContextInformationPosition()); |
| } |
| |
| @Override |
| public boolean updatePresentation(final int offset, final TextPresentation presentation) { |
| final LtxArgumentListContextInformation info= this.info; |
| if (info == null) { |
| return false; |
| } |
| if (info.getCommand().getArguments().size() > 0) { |
| final int argIdx= getCurrentArgInDef(offset); |
| final int[] idxs= info.getInformationDisplayStringArgumentIdxs(); |
| if (argIdx >= 0 && argIdx < idxs.length) { |
| if (argIdx == this.lastPresentation) { |
| return false; |
| } |
| final int start= idxs[argIdx]; |
| final int stop= (argIdx + 1 < idxs.length) ? idxs[argIdx + 1] : info.getInformationDisplayString().length(); |
| presentation.clear(); |
| presentation.addStyleRange(new StyleRange(start, stop - start, null, null, SWT.BOLD)); |
| this.lastPresentation= argIdx; |
| return true; |
| } |
| } |
| if (this.lastPresentation >= 0) { |
| presentation.clear(); |
| this.lastPresentation= -1; |
| return true; |
| } |
| return false; |
| } |
| |
| |
| private LtxParser.TaskConfig getParserTaskConfig() { |
| final ISourceUnit sourceUnit= this.sourceEditor.getSourceUnit(); |
| if (sourceUnit instanceof TexSourceUnit) { |
| final TexSourceUnit texSourceUnit= (TexSourceUnit)sourceUnit; |
| final LtxParser.TaskConfig config= new LtxParser.TaskConfig( |
| texSourceUnit.getTexCoreAccess().getTexCommandSet() ); |
| final ISourceUnitModelInfo modelInfo= texSourceUnit.getModelInfo(null, 0, null); |
| if (modelInfo instanceof ILtxModelInfo) { |
| config.additionalCommands= ((ILtxModelInfo)modelInfo).getCustomCommandMap(); |
| config.additionalEnvs= ((ILtxModelInfo)modelInfo).getCustomEnvMap(); |
| } |
| return config; |
| } |
| return new LtxParser.TaskConfig(TexCore.getWorkbenchAccess().getTexCommandSet()); |
| } |
| |
| private @Nullable ControlNode getScannedArgs() { |
| final LtxArgumentListContextInformation info= nonNullAssert(this.info); |
| final int startOffset= info.getCallArgsOffset(); |
| AbstractDocument document= (AbstractDocument)this.sourceEditor.getViewer().getDocument(); |
| int docStartOffset= startOffset; |
| if (document instanceof FragmentDocument) { |
| final FragmentDocument fragmentDoc= (FragmentDocument)document; |
| document= fragmentDoc.getMasterDocument(); |
| docStartOffset+= fragmentDoc.getOffsetInMasterDocument(); |
| } |
| if (docStartOffset < 0) { |
| docStartOffset= 0; |
| } |
| final long stamp= document.getModificationStamp(); |
| if (this.scannedArgs == null || this.scannedArgsStamp != stamp) { |
| try { |
| ControlNode args= null; |
| final ITreePartitionNode rRootNode= LtxPartitionNodeScanner.findLtxRootNode( |
| TreePartitionUtils.getNode(document, this.sourceEditor.getDocumentContentInfo().getPartitioning(), |
| docStartOffset, true)); |
| if (rRootNode != null) { |
| final int docEndOffset= Math.min(0x800, rRootNode.getEndOffset() - docStartOffset); |
| final String text= document.get(docStartOffset, docEndOffset); |
| final LtxParser parser= new LtxParser(new NowebLtxLexer(), null); |
| args= parser.scanControlWordArgs(new OffsetStringParserInput(text, startOffset) |
| .init(startOffset, startOffset + text.length()), |
| info.getCommand(), true, getParserTaskConfig()); |
| } |
| this.scannedArgs= args; |
| this.scannedArgsStamp= stamp; |
| } |
| catch (final Exception e) { |
| this.scannedArgs= null; |
| } |
| } |
| |
| return this.scannedArgs; |
| } |
| |
| private int getCurrentArgInDef(final int offset) { |
| final ControlNode args= getScannedArgs(); |
| if (args != null) { |
| @Nullable |
| final TexAstNode[] argNodes= TexAst.resolveArguments(args); |
| int idx= TexAst.getIndexAt(argNodes, offset); |
| // correct selection in between groups |
| if (idx < 0) { |
| idx= -idx - 1; |
| } |
| else if (idx >= 0 && idx + 1 < argNodes.length && argNodes[idx].getEndOffset() == offset) { |
| if ((argNodes[idx].getStatusCode() & TexProblemConstants.MASK_12) != STATUS2_GROUP_NOT_CLOSED) { |
| idx++; |
| } |
| } |
| else if (idx > 0 && argNodes[idx].getStartOffset() == offset) { |
| while (idx > 0 && argNodes[idx - 1] == null |
| && this.info.getCommand().getArguments().get(idx - 1).isOptional()) { |
| idx--; |
| } |
| } |
| return idx; |
| } |
| return -1; |
| } |
| |
| } |