Skip to content

Commit

Permalink
Completing /** should generate Javadoc with params
Browse files Browse the repository at this point in the history
Signed-off-by: Snjezana Peco <[email protected]>
  • Loading branch information
snjeza committed Aug 9, 2018
1 parent 3e092e3 commit 87ba981
Show file tree
Hide file tree
Showing 4 changed files with 354 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation 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
*
* Based on org.eclipse.jdt.internal.ui.text.javadoc.JavaDocAutoIndentStrategy
*
* Contributors:
* IBM Corporation - initial API and implementation
* Red Hat, Inc - decoupling from jdt.ui
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.contentassist;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.corext.util.MethodOverrideTester;
import org.eclipse.jdt.internal.corext.util.SuperTypeHierarchyCache;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.corext.codemanipulation.CodeGeneration;
import org.eclipse.jdt.ls.core.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.ls.core.internal.handlers.CompletionResolveHandler;
import org.eclipse.jdt.ls.core.internal.handlers.JsonRpcHelpers;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;

public class JavadocCompletionProposal {

public static final String JAVA_DOC_COMMENT = "Javadoc comment";

public List<CompletionItem> getProposals(ICompilationUnit cu, int offset, CompletionProposalRequestor collector, IProgressMonitor monitor) throws JavaModelException {
if (cu == null) {
throw new IllegalArgumentException("Compilation unit must not be null"); //$NON-NLS-1$
}
List<CompletionItem> result = new ArrayList<>();
IDocument d = JsonRpcHelpers.toDocument(cu.getBuffer());
if (offset < 0 || d.getLength() == 0) {
return result;
}
try {
int p = (offset == d.getLength() ? offset - 1 : offset);
IRegion line = d.getLineInformationOfOffset(p);
String lineStr = d.get(line.getOffset(), line.getLength()).trim();
if (!lineStr.startsWith("/**")) {
return result;
}
if (!hasEndJavadoc(d, offset)) {
return result;
}
String text = collector.getContext().getToken() == null ? "" : new String(collector.getContext().getToken());
StringBuilder buf = new StringBuilder(text);
IRegion prefix = findPrefixRange(d, line);
String indentation = d.get(prefix.getOffset(), prefix.getLength());
int lengthToAdd = Math.min(offset - prefix.getOffset(), prefix.getLength());
buf.append(indentation.substring(0, lengthToAdd));
String lineDelimiter = TextUtilities.getDefaultLineDelimiter(d);
ICompilationUnit unit = cu;
try {
unit.reconcile(ICompilationUnit.NO_AST, false, null, null);
String string = createJavaDocTags(d, offset, indentation, lineDelimiter, unit);
if (string != null && !string.trim().equals("*")) {
buf.append(string);
} else {
return result;
}
int nextNonWS = findEndOfWhiteSpace(d, offset, d.getLength());
if (!Character.isWhitespace(d.getChar(nextNonWS))) {
buf.append(lineDelimiter);
}
} catch (CoreException e) {
// ignore
}
final CompletionItem ci = new CompletionItem();
Range range = JDTUtils.toRange(unit, offset, 0);
ci.setTextEdit(new TextEdit(range, rtrim(buf.toString())));
ci.setFilterText(JAVA_DOC_COMMENT);
ci.setLabel(JAVA_DOC_COMMENT);
ci.setSortText(SortTextHelper.convertRelevance(0));
ci.setKind(CompletionItemKind.Snippet);
ci.setInsertTextFormat(InsertTextFormat.PlainText);
String documentation = prepareDocumentation(buf.toString(), lineDelimiter);
ci.setDocumentation(documentation);
Map<String, String> data = new HashMap<>(3);
data.put(CompletionResolveHandler.DATA_FIELD_URI, JDTUtils.toURI(cu));
data.put(CompletionResolveHandler.DATA_FIELD_REQUEST_ID, "0");
data.put(CompletionResolveHandler.DATA_FIELD_PROPOSAL_ID, "0");
ci.setData(data);
result.add(ci);
} catch (BadLocationException excp) {
// stop work
}
return result;
}

private String prepareDocumentation(String text, String lineDelimiter) {
String[] lines = text.split(lineDelimiter);
StringBuilder buf = new StringBuilder();
for (String line : lines) {
buf.append(rtrim(line));
buf.append(lineDelimiter);
}
String result = buf.toString();
if (result.indexOf(lineDelimiter) == 0) {
return result.replaceFirst(lineDelimiter, "");
}
return result;
}

private String rtrim(String text) {
int index = 0;
int end = text.length();
while (index < end) {
char c = text.charAt(index);
if (c == ' ' || c == '\t') {
index++;
} else {
break;
}
}
return text.substring(index);
}

private IRegion findPrefixRange(IDocument document, IRegion line) throws BadLocationException {
int lineOffset = line.getOffset();
int lineEnd = lineOffset + line.getLength();
int indentEnd = findEndOfWhiteSpace(document, lineOffset, lineEnd);
if (indentEnd < lineEnd && document.getChar(indentEnd) == '*') {
indentEnd++;
while (indentEnd < lineEnd && document.getChar(indentEnd) == ' ') {
indentEnd++;
}
}
return new Region(lineOffset, indentEnd - lineOffset);
}

private int findEndOfWhiteSpace(IDocument document, int offset, int end) throws BadLocationException {
while (offset < end) {
char c = document.getChar(offset);
if (c != ' ' && c != '\t') {
return offset;
}
offset++;
}
return end;
}

private boolean hasEndJavadoc(IDocument document, int offset) throws BadLocationException {
int pos = -1;
while (offset < document.getLength()) {
char c = document.getChar(offset);
if (!Character.isWhitespace(c) && !(c == '*')) {
pos = offset;
break;
}
offset++;
}
if (document.getLength() >= pos + 2 && document.get(pos - 1, 2).equals("*/")) {
return true;
}
return false;
}


private String createJavaDocTags(IDocument document, int offset, String indentation, String lineDelimiter, ICompilationUnit unit) throws CoreException, BadLocationException {
IJavaElement element = unit.getElementAt(offset);
if (element == null) {
return null;
}
switch (element.getElementType()) {
case IJavaElement.TYPE:
return createTypeTags(document, offset, indentation, lineDelimiter, (IType) element);

case IJavaElement.METHOD:
return createMethodTags(document, offset, indentation, lineDelimiter, (IMethod) element);

default:
return null;
}
}

private String createTypeTags(IDocument document, int offset, String indentation, String lineDelimiter, IType type) throws CoreException, BadLocationException {
if (!accept(offset, type)) {
return null;
}
String[] typeParamNames = StubUtility.getTypeParameterNames(type.getTypeParameters());
String comment = CodeGeneration.getTypeComment(type.getCompilationUnit(), type.getTypeQualifiedName('.'), typeParamNames, lineDelimiter);
if (comment != null) {
return prepareTemplateComment(comment.trim(), indentation, type.getJavaProject(), lineDelimiter);
}
return null;
}

private boolean accept(int offset, IMember member) throws JavaModelException {
ISourceRange nameRange = member.getNameRange();
if (nameRange == null) {
return false;
}
int srcOffset = nameRange.getOffset();
return srcOffset > offset;
}

private String createMethodTags(IDocument document, int offset, String indentation, String lineDelimiter, IMethod method) throws CoreException, BadLocationException {
if (!accept(offset, method)) {
return null;
}
IMethod inheritedMethod = getInheritedMethod(method);
String comment = CodeGeneration.getMethodComment(method, inheritedMethod, lineDelimiter);
if (comment != null) {
comment = comment.trim();
boolean javadocComment = comment.startsWith("/**"); //$NON-NLS-1$
if (javadocComment) {
return prepareTemplateComment(comment, indentation, method.getJavaProject(), lineDelimiter);
}
}
return null;
}

private String prepareTemplateComment(String comment, String indentation, IJavaProject project, String lineDelimiter) {
// trim comment start and end if any
if (comment.endsWith("*/")) {
comment = comment.substring(0, comment.length() - 2);
}
comment = comment.trim();
if (comment.startsWith("/*")) { //$NON-NLS-1$
if (comment.length() > 2 && comment.charAt(2) == '*') {
comment = comment.substring(3); // remove '/**'
} else {
comment = comment.substring(2); // remove '/*'
}
}
// trim leading spaces, but not new lines
int nonSpace = 0;
int len = comment.length();
while (nonSpace < len && Character.getType(comment.charAt(nonSpace)) == Character.SPACE_SEPARATOR) {
nonSpace++;
}
comment = comment.substring(nonSpace);
return comment;
}

private IMethod getInheritedMethod(IMethod method) throws JavaModelException {
IType declaringType = method.getDeclaringType();
MethodOverrideTester tester = SuperTypeHierarchyCache.getMethodOverrideTester(declaringType);
return tester.findOverriddenMethod(method, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.contentassist.CompletionProposalRequestor;
import org.eclipse.jdt.ls.core.internal.contentassist.JavadocCompletionProposal;
import org.eclipse.jdt.ls.core.internal.contentassist.SnippetCompletionProposal;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
import org.eclipse.lsp4j.CompletionItem;
Expand Down Expand Up @@ -109,6 +110,7 @@ public boolean isCanceled() {
unit.codeComplete(offset, collector, subMonitor);
proposals.addAll(collector.getCompletionItems());
proposals.addAll(SnippetCompletionProposal.getSnippets(unit, collector.getContext(), subMonitor));
proposals.addAll(new JavadocCompletionProposal().getProposals(unit, offset, collector, subMonitor));
} catch (OperationCanceledException e) {
monitor.setCanceled(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ InitializeResult initialize(InitializeParams param) {
}
InitializeResult result = new InitializeResult();
ServerCapabilities capabilities = new ServerCapabilities();
capabilities.setCompletionProvider(new CompletionOptions(Boolean.TRUE, Arrays.asList(".", "@", "#")));
capabilities.setCompletionProvider(new CompletionOptions(Boolean.TRUE, Arrays.asList(".", "@", "#", "*")));
if (!preferenceManager.getClientPreferences().isFormattingDynamicRegistrationSupported()) {
capabilities.setDocumentFormattingProvider(Boolean.TRUE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.eclipse.jdt.ls.core.internal.JsonMessageHelper;
import org.eclipse.jdt.ls.core.internal.TextEditUtil;
import org.eclipse.jdt.ls.core.internal.WorkspaceHelper;
import org.eclipse.jdt.ls.core.internal.contentassist.JavadocCompletionProposal;
import org.eclipse.jdt.ls.core.internal.preferences.ClientPreferences;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
import org.eclipse.lsp4j.CompletionItem;
Expand Down Expand Up @@ -319,6 +320,88 @@ public void testCompletion_import_package() throws JavaModelException{
//Not checking the range end character
}

@Test
public void testCompletion_javadocComment() throws JavaModelException {
ICompilationUnit unit = getWorkingCopy(
//@formatter:off
"src/java/Foo.java",
"public class Foo {\n"+
" /** */ \n"+
" void foo(int i, String s) {\n"+
" }\n"+
"}\n");
//@formatter:on
int[] loc = findCompletionLocation(unit, "/**");
CompletionList list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight();
assertNotNull(list);
assertEquals(1, list.getItems().size());
CompletionItem item = list.getItems().get(0);
assertNull(item.getInsertText());
assertEquals(JavadocCompletionProposal.JAVA_DOC_COMMENT, item.getLabel());
assertEquals(CompletionItemKind.Snippet, item.getKind());
assertEquals("999999999", item.getSortText());
assertNotNull(item.getTextEdit());
assertEquals("\n * @param i\n * @param s\n", item.getTextEdit().getNewText());
Range range = item.getTextEdit().getRange();
assertEquals(1, range.getStart().getLine());
assertEquals(4, range.getStart().getCharacter());
assertEquals(1, range.getEnd().getLine());
assertEquals("* @param i\n* @param s\n", item.getDocumentation().getLeft());
}

@Test
public void testCompletion_javadocCommentPartial() throws JavaModelException {
ICompilationUnit unit = getWorkingCopy(
//@formatter:off
"src/java/Foo.java",
"public class Foo {\n"+
" /** \n"+
" * @int \n"+
" */ \n"+
" void foo(int i, String s) {\n"+
" }\n"+
"}\n");
//@formatter:on
int[] loc = findCompletionLocation(unit, "/**");
CompletionList list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight();
assertNotNull(list);
assertEquals(0, list.getItems().size());
}

@Test
public void testCompletion_javadocCommentRegular() throws JavaModelException {
ICompilationUnit unit = getWorkingCopy(
//@formatter:off
"src/java/Foo.java",
"public class Foo {\n"+
" /* */ \n"+
" void foo(int i, String s) {\n"+
" }\n"+
"}\n");
//@formatter:on
int[] loc = findCompletionLocation(unit, "/*");
CompletionList list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight();
assertNotNull(list);
assertEquals(0, list.getItems().size());
}

@Test
public void testCompletion_javadocCommentNoParam() throws JavaModelException {
ICompilationUnit unit = getWorkingCopy(
//@formatter:off
"src/java/Foo.java",
"public class Foo {\n"+
" /** */ \n"+
" void foo() {\n"+
" }\n"+
"}\n");
//@formatter:on
int[] loc = findCompletionLocation(unit, "/**");
CompletionList list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight();
assertNotNull(list);
assertEquals(0, list.getItems().size());
}

@Test
public void testCompletion_import_static() throws JavaModelException{
ICompilationUnit unit = getWorkingCopy(
Expand Down

0 comments on commit 87ba981

Please sign in to comment.