| /*=============================================================================# |
| # Copyright (c) 2007, 2021 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.r.ui.sourceediting; |
| |
| import java.util.List; |
| |
| import org.eclipse.core.filesystem.EFS; |
| import org.eclipse.core.filesystem.IFileStore; |
| import org.eclipse.core.filesystem.URIUtil; |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.ITypedRegion; |
| import org.eclipse.jface.text.TextUtilities; |
| |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.text.core.TextRegion; |
| import org.eclipse.statet.jcommons.text.core.input.OffsetStringParserInput; |
| import org.eclipse.statet.jcommons.text.core.input.TextParserInput; |
| import org.eclipse.statet.jcommons.ts.core.Tool; |
| |
| import org.eclipse.statet.ecommons.net.resourcemapping.core.IResourceMapping; |
| import org.eclipse.statet.ecommons.net.resourcemapping.core.IResourceMappingManager; |
| import org.eclipse.statet.ecommons.net.resourcemapping.core.ResourceMappingOrder; |
| import org.eclipse.statet.ecommons.net.resourcemapping.core.ResourceMappingUtils; |
| |
| import org.eclipse.statet.ltk.model.core.element.SourceUnit; |
| import org.eclipse.statet.ltk.model.core.element.WorkspaceSourceUnit; |
| 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.PathCompletionComputor; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.SourceProposal.ProposalParameters; |
| import org.eclipse.statet.nico.ui.console.ConsolePageEditor; |
| import org.eclipse.statet.r.console.core.RProcess; |
| import org.eclipse.statet.r.core.RUtil; |
| import org.eclipse.statet.r.core.rlang.RTerminal; |
| import org.eclipse.statet.r.core.rsource.RLexer; |
| |
| |
| /** |
| * Completion computer for path in R code. |
| * |
| * Supports workspace properties of a R tool process |
| */ |
| @NonNullByDefault |
| public class RPathCompletionComputer extends PathCompletionComputor { |
| |
| |
| private @Nullable RProcess associatedTool; |
| |
| private @Nullable IContainer baseResource; |
| private @Nullable IFileStore baseFileStore; |
| |
| private final RLexer rLexer= new RLexer(RLexer.ENABLE_QUICK_CHECK); |
| |
| |
| public RPathCompletionComputer() { |
| } |
| |
| |
| @Override |
| public void onSessionStarted(final ISourceEditor editor, final ContentAssist assist) { |
| this.associatedTool= null; |
| this.baseResource= null; |
| this.baseFileStore= null; |
| if (editor instanceof ConsolePageEditor) { |
| final Tool tool= editor.getAdapter(Tool.class); |
| if (tool instanceof RProcess) { |
| this.associatedTool= (RProcess) tool; |
| } |
| } |
| else { |
| final SourceUnit su= editor.getSourceUnit(); |
| if (su instanceof WorkspaceSourceUnit) { |
| IContainer baseResource= null; |
| final IResource resource= ((WorkspaceSourceUnit)su).getResource(); |
| // final IRProject rProject= RProjects.getRProject(resource.getProject()); |
| // if (rProject != null) { |
| // this.baseResource= rProject.getRawBuildpath(); |
| // } |
| if (baseResource == null) { |
| baseResource= resource.getParent(); |
| } |
| if (baseResource != null) { |
| try { |
| this.baseResource= baseResource; |
| this.baseFileStore= EFS.getStore(baseResource.getLocationURI()); |
| } |
| catch (final CoreException e) { |
| } |
| } |
| } |
| } |
| |
| super.onSessionStarted(editor, assist); |
| } |
| |
| @Override |
| public void onSessionEnded() { |
| super.onSessionEnded(); |
| this.associatedTool= null; |
| } |
| |
| @Override |
| protected boolean getIsWindows() { |
| final RProcess associatedTool= this.associatedTool; |
| if ((associatedTool != null)) { |
| return associatedTool.getWorkspaceData().isWindows(); |
| } |
| return super.getIsWindows(); |
| } |
| |
| @Override |
| protected char getDefaultFileSeparator() { |
| final RProcess associatedTool= this.associatedTool; |
| if (associatedTool != null) { |
| return associatedTool.getWorkspaceData().getFileSeparator(); |
| } |
| return super.getDefaultFileSeparator(); |
| } |
| |
| @Override |
| protected @Nullable TextRegion getContentRegion(final AssistInvocationContext context, final int mode) |
| throws BadLocationException { |
| final IDocument document= context.getSourceViewer().getDocument(); |
| final int offset= context.getInvocationOffset(); |
| final ITypedRegion partition= TextUtilities.getPartition(document, |
| context.getEditor().getDocumentContentInfo().getPartitioning(), offset, true); |
| this.rLexer.reset(new OffsetStringParserInput( |
| document.get(partition.getOffset(), partition.getLength()), |
| partition.getOffset() ) |
| .init(partition.getOffset(), partition.getOffset() + partition.getLength()) ); |
| switch (this.rLexer.next()) { |
| case STRING_D: |
| case STRING_S: |
| return this.rLexer.getTextRegion(); |
| case STRING_R: |
| return this.rLexer.getTextRegion(); |
| default: |
| return null; |
| } |
| } |
| |
| @Override |
| protected @Nullable IPath getRelativeBasePath() { |
| { final RProcess associatedTool= this.associatedTool; |
| if (associatedTool != null) { |
| final IFileStore wd= associatedTool.getWorkspaceData().getWorkspaceDir(); |
| if (wd != null) { |
| return URIUtil.toPath(wd.toURI()); |
| } |
| return null; |
| } |
| } |
| { final IContainer baseResource= this.baseResource; |
| if (baseResource != null) { |
| return baseResource.getLocation(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| protected @Nullable IFileStore getRelativeBaseStore() { |
| { final RProcess associatedTool= this.associatedTool; |
| if (associatedTool != null) { |
| return associatedTool.getWorkspaceData().getWorkspaceDir(); |
| } |
| } |
| { final IFileStore baseFileStore= this.baseFileStore; |
| if (baseFileStore != null) { |
| return baseFileStore; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| protected @Nullable IFileStore resolveStore(final IPath path) throws CoreException { |
| { final RProcess associatedTool= this.associatedTool; |
| if (associatedTool != null) { |
| return associatedTool.getWorkspaceData().toFileStore(path); |
| } |
| } |
| return super.resolveStore(path); |
| } |
| |
| @Override |
| protected void tryAlternative(final AssistInvocationContext context, |
| final IPath path, |
| final int startOffset, final String segmentPrefix, final String completionPrefix, |
| final AssistProposalCollector proposals) throws CoreException { |
| final RProcess associatedTool= this.associatedTool; |
| if (associatedTool != null) { |
| final String address= associatedTool.getWorkspaceData().getRemoteAddress(); |
| if (address != null) { |
| final IResourceMappingManager rmManager= ResourceMappingUtils.getManager(); |
| if (rmManager != null) { |
| final ProposalParameters<?> parameters= createProposalParameters( |
| context, startOffset, segmentPrefix ); |
| |
| final List<IResourceMapping> mappings= rmManager |
| .getResourceMappingsFor(address, ResourceMappingOrder.REMOTE); |
| for (final IResourceMapping mapping : mappings) { |
| IPath remotePath= mapping.getRemotePath(); |
| if (path.isEmpty()) { |
| // remotePath |
| } |
| else if (path.isPrefixOf(remotePath)) { |
| remotePath= remotePath.setDevice(null).makeRelative().removeFirstSegments(path.segmentCount()); |
| } |
| else { |
| continue; |
| } |
| String name= remotePath.segment(0); |
| if (name == null) { |
| name= ""; //$NON-NLS-1$ |
| } |
| if (parameters.matchesNamePattern(name)) { |
| proposals.add(new ResourceCompletionProposal(parameters, |
| mapping.getFileStore(), remotePath.toString(), completionPrefix, |
| null )); |
| } |
| } |
| return; |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected @Nullable String getPrefix(final AssistInvocationContext context, |
| final TextRegion contentRegion, final int offset) |
| throws BadLocationException { |
| if (this.rLexer.getType() == RTerminal.STRING_R) { |
| return super.getPrefix(context, contentRegion, offset); |
| } |
| final RLexer textLexer= new RLexer(); |
| final TextParserInput input= this.rLexer.getInput(); |
| textLexer.reset(input.init(input.getStartIndex(), offset)); |
| switch (textLexer.next()) { |
| case STRING_D: |
| case STRING_S: |
| return super.checkPrefix(textLexer.getText()); |
| default: |
| return null; |
| } |
| } |
| |
| @Override |
| protected String checkPathCompletion(final IDocument document, final int completionOffset, |
| String completion) { |
| if (this.rLexer.getType() == RTerminal.STRING_R) { |
| return completion; |
| } |
| completion= RUtil.escapeCompletely(completion); |
| int existingBackslashCount= 0; |
| if (completionOffset >= 1) { |
| try { |
| if (document.getChar(completionOffset - 1) == '\\') { |
| existingBackslashCount++; |
| if (completionOffset >= 2) { |
| if (document.getChar(completionOffset - 2) == '\\') { |
| existingBackslashCount++; |
| } |
| } |
| } |
| } |
| catch (final BadLocationException e) {} |
| } |
| final boolean startsWithBackslash= (completion.length() >= 2 && |
| completion.charAt(0) == '\\' && completion.charAt(1) == '\\'); |
| if ((existingBackslashCount % 2) == 1) { |
| if (startsWithBackslash) { |
| completion= completion.substring(1); |
| } |
| else { |
| completion= '\\' + completion; |
| } |
| } |
| else if (existingBackslashCount > 0) { |
| if (startsWithBackslash) { |
| completion= completion.substring(2); |
| } |
| } |
| return completion; |
| } |
| |
| } |