/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.plugin.languageserver.ide.editor.codeassist; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import org.eclipse.che.api.languageserver.shared.model.ExtendedCompletionItem; import org.eclipse.che.api.languageserver.shared.model.ExtendedCompletionList; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.ide.api.editor.codeassist.CodeAssistCallback; import org.eclipse.che.ide.api.editor.codeassist.CodeAssistProcessor; import org.eclipse.che.ide.api.editor.codeassist.CompletionProposal; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import org.eclipse.che.ide.filters.FuzzyMatches; import org.eclipse.che.ide.filters.Match; import org.eclipse.che.plugin.languageserver.ide.LanguageServerResources; import org.eclipse.che.plugin.languageserver.ide.service.TextDocumentServiceClient; import org.eclipse.che.plugin.languageserver.ide.util.DtoBuildHelper; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextDocumentPositionParams; import java.util.ArrayList; import java.util.List; import static com.google.common.collect.Lists.newArrayList; /** * Implement code assist with LS */ public class LanguageServerCodeAssistProcessor implements CodeAssistProcessor { private final DtoBuildHelper dtoBuildHelper; private final LanguageServerResources resources; private final CompletionImageProvider imageProvider; private final ServerCapabilities serverCapabilities; private final TextDocumentServiceClient documentServiceClient; private final FuzzyMatches fuzzyMatches; private final LatestCompletionResult latestCompletionResult; private String lastErrorMessage; @Inject public LanguageServerCodeAssistProcessor(TextDocumentServiceClient documentServiceClient, DtoBuildHelper dtoBuildHelper, LanguageServerResources resources, CompletionImageProvider imageProvider, @Assisted ServerCapabilities serverCapabilities, FuzzyMatches fuzzyMatches) { this.documentServiceClient = documentServiceClient; this.dtoBuildHelper = dtoBuildHelper; this.resources = resources; this.imageProvider = imageProvider; this.serverCapabilities = serverCapabilities; this.fuzzyMatches = fuzzyMatches; this.latestCompletionResult = new LatestCompletionResult(); } @Override public void computeCompletionProposals(TextEditor editor, final int offset, final boolean triggered, final CodeAssistCallback callback) { this.lastErrorMessage = null; TextDocumentPositionParams documentPosition = dtoBuildHelper.createTDPP(editor.getDocument(), offset); final TextDocumentIdentifier documentId = documentPosition.getTextDocument(); String currentLine = editor.getDocument().getLineContent(documentPosition.getPosition().getLine()); final String currentWord = getCurrentWord(currentLine, documentPosition.getPosition().getCharacter()); if (!triggered && latestCompletionResult.isGoodFor(documentId, offset, currentWord)) { // no need to send new completion request computeProposals(currentWord, offset - latestCompletionResult.getOffset(), callback); } else { documentServiceClient.completion(documentPosition).then(new Operation<ExtendedCompletionList>() { @Override public void apply(ExtendedCompletionList list) throws OperationException { latestCompletionResult.update(documentId, offset, currentWord, list); computeProposals(currentWord, 0, callback); } }).catchError(new Operation<PromiseError>() { @Override public void apply(PromiseError error) throws OperationException { lastErrorMessage = error.getMessage(); } }); } } @Override public String getErrorMessage() { return lastErrorMessage; } private String getCurrentWord(String text, int offset) { int i = offset - 1; while (i >= 0 && isWordChar(text.charAt(i))) { i--; } return text.substring(i + 1, offset); } private boolean isWordChar(char c) { return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c >= '\u007f' && c <= '\u00ff' || c == '$' || c == '_' || c == '-'; } private List<Match> filter(String word, CompletionItem item) { return filter(word, item.getLabel(), item.getFilterText()); } private List<Match> filter(String word, String label, String filterText) { if (filterText == null || filterText.isEmpty()) { filterText = label; } // check if the word matches the filterText if (fuzzyMatches.fuzzyMatch(word, filterText) != null) { // return the highlights based on the label List<Match> highlights = fuzzyMatches.fuzzyMatch(word, label); // return empty list of highlights if nothing matches the label return (highlights == null) ? new ArrayList<Match>() : highlights; } return null; } private void computeProposals(String currentWord, int offset, CodeAssistCallback callback) { List<CompletionProposal> proposals = newArrayList(); for (ExtendedCompletionItem item : latestCompletionResult.getCompletionList().getItems()) { List<Match> highlights = filter(currentWord, item); if (highlights != null) { proposals.add(new CompletionItemBasedCompletionProposal(item, documentServiceClient, latestCompletionResult.getDocumentId(), resources, imageProvider.getIcon(item.getKind()), serverCapabilities, highlights, offset)); } } callback.proposalComputed(proposals); } }