blob: c9dde04c0aece8e43c9c4cdc5d43985b103c1d8a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.gradle.editor.cli;
import org.netbeans.modules.gradle.api.GradleBaseProject;
import org.netbeans.modules.gradle.api.GradleTask;
import org.netbeans.modules.gradle.api.execute.GradleCommandLine;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.font.TextAttribute;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.StyledDocument;
import org.netbeans.api.annotations.common.StaticResource;
import org.netbeans.api.editor.completion.Completion;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.project.Project;
import org.netbeans.modules.gradle.spi.actions.ReplaceTokenProvider;
import org.netbeans.spi.editor.completion.CompletionDocumentation;
import org.netbeans.spi.editor.completion.CompletionItem;
import org.netbeans.spi.editor.completion.CompletionProvider;
import org.netbeans.spi.editor.completion.CompletionResultSet;
import org.netbeans.spi.editor.completion.CompletionTask;
import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery;
import org.netbeans.spi.editor.completion.support.AsyncCompletionTask;
import org.netbeans.spi.editor.completion.support.CompletionUtilities;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
/**
*
* @author lkishalmi
*/
@MimeRegistration(mimeType = GradleCliEditorKit.MIME_TYPE, service = CompletionProvider.class)
public class GradleCliCompletionProvider implements CompletionProvider {
private static final Pattern PROP_INPUT = Pattern.compile("\\$\\{([\\w.]*)$"); //NOI18N
private static final String INPUT_TOKEN = "input:"; //NOI18N
private static final Set<GradleCommandLine.GradleOptionItem> GRADLE_OPTIONS;
static {
Set<GradleCommandLine.GradleOptionItem> all = new HashSet<>();
GRADLE_OPTIONS = Collections.unmodifiableSet(all);
all.addAll(Arrays.asList(GradleCommandLine.Flag.values()));
all.addAll(Arrays.asList(GradleCommandLine.Parameter.values()));
all.addAll(Arrays.asList(GradleCommandLine.Property.values()));
}
@Override
public CompletionTask createTask(int queryType, JTextComponent component) {
if (queryType != CompletionProvider.COMPLETION_QUERY_TYPE) {
return null;
}
return new AsyncCompletionTask(new AsyncCompletionQuery() {
@Override
protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) {
String filter = null;
int startOffset = caretOffset - 1;
try {
final StyledDocument bDoc = (StyledDocument) doc;
final int lineStartOffset = getRowFirstNonWhite(bDoc, caretOffset);
final char[] line = bDoc.getText(lineStartOffset, caretOffset - lineStartOffset).toCharArray();
final int whiteOffset = indexOfWhite(line);
filter = new String(line, whiteOffset + 1, line.length - whiteOffset - 1);
if (whiteOffset > 0) {
startOffset = lineStartOffset + whiteOffset + 1;
} else {
startOffset = lineStartOffset;
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
Project project = null;
Object prop = doc.getProperty(Document.StreamDescriptionProperty);
if (prop != null && prop instanceof Project) {
project = (Project) prop;
}
Matcher tokenMatcher = PROP_INPUT.matcher(filter);
boolean tokenInFilter = tokenMatcher.find();
try {
GradleCommandLine cli = new GradleCommandLine(doc.getText(0, doc.getLength()));
if (!filter.startsWith("-") && !tokenInFilter) {
if (project != null) {
GradleBaseProject gbp = GradleBaseProject.get(project);
for (GradleTask task : gbp.getTasks()) {
if (!task.isPrivate()
&& !cli.getTasks().contains(task.getName())
&& !cli.getExcludedTasks().contains(task.getName())
&& (task.getName().startsWith(filter) || task.matches(filter))) {
resultSet.addItem(new GradleTaskCompletionItem(task, startOffset, caretOffset));
}
}
}
}
if (filter.isEmpty() || filter.startsWith("-")) {
for (GradleCommandLine.GradleOptionItem item : GRADLE_OPTIONS) {
if (cli.canAdd(item)) {
for (String f : item.getFlags()) {
if (f.startsWith(filter)) {
resultSet.addItem(new GradleOptionCompletionItem(item, f, startOffset, caretOffset));
}
}
}
}
}
} catch (BadLocationException ex) {
// Nothing to do.
}
if (tokenInFilter && (project != null)) {
String propFilter = tokenMatcher.group(1);
ReplaceTokenProvider tokenProvider = project.getLookup().lookup(ReplaceTokenProvider.class);
for (String token : tokenProvider.getSupportedTokens()) {
if (token.startsWith(propFilter)) {
resultSet.addItem(new TokenCompletionItem(token, startOffset + tokenMatcher.start(1), caretOffset));
}
}
if (INPUT_TOKEN.startsWith(propFilter)) {
resultSet.addItem(new TokenCompletionItem(INPUT_TOKEN, startOffset + tokenMatcher.start(1), caretOffset));
}
}
resultSet.finish();
}
}, component);
}
@Override
public int getAutoQueryTypes(JTextComponent component, String typedText) {
return 1;
}
static int getRowFirstNonWhite(StyledDocument doc, int offset) throws BadLocationException {
Element lineElement = doc.getParagraphElement(offset);
int start = lineElement.getStartOffset();
while (start + 1 < lineElement.getEndOffset()) {
try {
if (doc.getText(start, 1).charAt(0) != ' ') {
break;
}
} catch (BadLocationException ex) {
throw (BadLocationException) new BadLocationException(
"calling getText(" + start + ", " + (start + 1)
+ ") on doc of length: " + doc.getLength(), start
).initCause(ex);
}
start++;
}
return start;
}
static int indexOfWhite(char[] line) {
int i = line.length;
while (--i > -1) {
final char c = line[i];
if (Character.isWhitespace(c)) {
return i;
}
}
return -1;
}
private abstract static class AbstractGradleCompletionItem implements CompletionItem {
private final int startOffset;
private final int caretOffset;
public AbstractGradleCompletionItem(int startOffset, int caretOffset) {
this.startOffset = startOffset;
this.caretOffset = caretOffset;
}
protected abstract String getValue();
@Override
public void defaultAction(JTextComponent jtc) {
try {
Document doc = jtc.getDocument();
doc.remove(startOffset, caretOffset - startOffset);
doc.insertString(startOffset, getValue(), null);
//This statement will close the code completion box:
Completion.get().hideAll();
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
@Override
public void processKeyEvent(KeyEvent evt) {
}
@Override
public int getPreferredWidth(Graphics g, Font font) {
return CompletionUtilities.getPreferredWidth(getValue(), null, g, font);
}
@Override
public void render(Graphics g, Font defaultFont, Color defaultColor, Color backgroundColor, int width, int height, boolean selected) {
CompletionUtilities.renderHtml(null, getValue(), null, g, defaultFont, defaultColor, width, height, selected);
}
@Override
public CompletionTask createDocumentationTask() {
return null;
}
@Override
public CompletionTask createToolTipTask() {
return null;
}
@Override
public boolean instantSubstitution(JTextComponent component) {
return false;
}
@Override
public int getSortPriority() {
return Integer.MAX_VALUE;
}
@Override
public CharSequence getSortText() {
return getValue();
}
@Override
public CharSequence getInsertPrefix() {
return getValue();
}
}
private static class GradleTaskCompletionItem extends AbstractGradleCompletionItem {
//This resource is from Gradle Projects module
private static final String TASK_ICON = "org/netbeans/modules/gradle/resources/gradle-task.gif"; //NOI18N
private static final ImageIcon TASK_IMAGEICON = ImageUtilities.loadImageIcon(TASK_ICON, false);
private final GradleTask task;
public GradleTaskCompletionItem(GradleTask task, int startOffset, int caretOffset) {
super(startOffset, caretOffset);
this.task = task;
}
@Override
public int getSortPriority() {
switch (task.getGroup()) {
case "application": return 0;
case "build": return 1;
case "distribution": return 2;
default: return 3;
}
}
@Override
public CompletionTask createDocumentationTask() {
return new AsyncCompletionTask(new AsyncCompletionQuery() {
@Override
protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) {
resultSet.setDocumentation(new GradleTaskCompletionDocumentation());
resultSet.finish();
}
});
}
@Override
protected String getValue() {
return task.getName();
}
@Override
public void render(Graphics g, Font defaultFont, Color defaultColor, Color backgroundColor, int width, int height, boolean selected) {
CompletionUtilities.renderHtml(TASK_IMAGEICON, getValue(), null, g, defaultFont, defaultColor, width, height, selected);
}
private class GradleTaskCompletionDocumentation implements CompletionDocumentation {
@Override
public String getText() {
StringBuilder sb = new StringBuilder();
sb.append("<html>");
sb.append("<b>Name:</b> ").append(task.getName()).append("<br/>");
sb.append("<b>Group:</b> ").append(task.getGroup()).append("<br/>");
sb.append("<b>Path:</b> ").append(task.getPath()).append("<br/>");
sb.append("<b>Description:</b><p>").append(task.getDescription());
return sb.toString();
}
@Override
public URL getURL() {
return null;
}
@Override
public CompletionDocumentation resolveLink(String link) {
return null;
}
@Override
public Action getGotoSourceAction() {
return null;
}
}
}
private static class GradleOptionCompletionItem extends AbstractGradleCompletionItem {
private final GradleCommandLine.GradleOptionItem item;
private final String value;
public GradleOptionCompletionItem(GradleCommandLine.GradleOptionItem item, String value, int startOffset, int caretOffset) {
super(startOffset, caretOffset);
this.item = item;
this.value = value;
}
@Override
public int getSortPriority() {
return value.startsWith("--") ? 5 : 4;
}
@Override
protected String getValue() {
return value;
}
@Override
public void render(Graphics g, Font defaultFont, Color defaultColor, Color backgroundColor, int width, int height, boolean selected) {
Map<TextAttribute, Object> attributes = new HashMap<>(defaultFont.getAttributes());
if (!item.isSupported()) {
attributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
}
Font font = new Font(attributes);
CompletionUtilities.renderHtml(null, getValue(), null, g, font, defaultColor, width, height, selected);
}
@Override
public CompletionTask createDocumentationTask() {
return new AsyncCompletionTask(new AsyncCompletionQuery() {
@Override
protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) {
resultSet.setDocumentation(new GradleItemCompletionDocumentation());
resultSet.finish();
}
});
}
private class GradleItemCompletionDocumentation implements CompletionDocumentation {
@Override
public String getText() {
StringBuilder sb = new StringBuilder();
sb.append("<html>");
if (!item.isSupported()) {
sb.append("<b>Unsupported:</b> This argument will be ignored");
}
sb.append(item.getDescription());
return sb.toString();
}
@Override
public URL getURL() {
return null;
}
@Override
public CompletionDocumentation resolveLink(String link) {
return null;
}
@Override
public Action getGotoSourceAction() {
return null;
}
}
}
private static class TokenCompletionItem extends AbstractGradleCompletionItem {
final String token;
public TokenCompletionItem(String token, int startOffset, int caretOffset) {
super(startOffset, caretOffset);
this.token = token;
}
@Override
protected String getValue() {
return token;
}
}
}