-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathCompletor.java
More file actions
191 lines (174 loc) · 7.61 KB
/
Completor.java
File metadata and controls
191 lines (174 loc) · 7.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package org.javacomp.completion;
import com.google.common.collect.ImmutableList;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import org.javacomp.file.FileManager;
import org.javacomp.logging.JLogger;
import org.javacomp.project.ModuleManager;
import org.javacomp.project.PositionContext;
import org.javacomp.typesolver.ExpressionSolver;
import org.javacomp.typesolver.MemberSolver;
import org.javacomp.typesolver.OverloadSolver;
import org.javacomp.typesolver.TypeSolver;
/** Entry point of completion logic. */
public class Completor {
private static final JLogger logger = JLogger.createForEnclosingClass();
private static final CompletionResult NO_CACHE =
CompletionResult.builder()
.setFilePath(Paths.get(""))
.setLine(-1)
.setColumn(-1)
.setPrefix("")
.setCompletionCandidates(ImmutableList.of())
.setTextEditOptions(TextEditOptions.DEFAULT)
.build();
private final FileManager fileManager;
private final TypeSolver typeSolver;
private final ExpressionSolver expressionSolver;
private CompletionResult cachedCompletion = NO_CACHE;
public Completor(FileManager fileManager) {
this.fileManager = fileManager;
this.typeSolver = new TypeSolver();
OverloadSolver overloadSolver = new OverloadSolver(typeSolver);
this.expressionSolver =
new ExpressionSolver(
typeSolver, overloadSolver, new MemberSolver(typeSolver, overloadSolver));
}
/**
* @param module the module of the project
* @param filePath normalized path of the file to be completed
* @param line 0-based line number of the completion point
* @param column 0-based character offset from the beginning of the line to the completion point
*/
public CompletionResult getCompletionResult(
ModuleManager moduleManager, Path filePath, int line, int column) {
// PositionContext gets the tree path whose leaf node includes the position
// (position < node's endPosition). However, for completions, we want the leaf node either
// includes the position, or just before the position (position == node's endPosition).
// Decresing column by 1 will decrease position by 1, which makes
// adjustedPosition == node's endPosition - 1 if the node is just before the actual position.
int contextColumn = column > 0 ? column - 1 : 0;
Optional<PositionContext> positionContext =
PositionContext.createForPosition(moduleManager, filePath, line, contextColumn);
logger.fine("filePath: %s, line: %s, contextColumn: %s", filePath, line, contextColumn );
if (!positionContext.isPresent()) {
return CompletionResult.builder()
.setCompletionCandidates(ImmutableList.of())
.setLine(line)
.setColumn(column)
.setPrefix("")
.setFilePath(filePath)
.build();
}
ContentWithLineMap contentWithLineMap =
ContentWithLineMap.create(positionContext.get().getFileScope(), fileManager, filePath);
String prefix = contentWithLineMap.extractCompletionPrefix(line, column);
logger.fine("prefix:%s", prefix);
// TODO: limit the number of the candidates.
if (cachedCompletion.isIncrementalCompletion(filePath, line, column, prefix)) {
logger.fine("using cache");
return getCompletionCandidatesFromCache(line, column, prefix);
} else {
cachedCompletion =
computeCompletionResult(positionContext.get(), contentWithLineMap, line, column, prefix);
return cachedCompletion;
}
}
private CompletionResult computeCompletionResult(
PositionContext positionContext,
ContentWithLineMap contentWithLineMap,
int line,
int column,
String prefix) {
TreePath treePath = positionContext.getTreePath();
CompletionAction action;
TextEditOptions.Builder textEditOptions =
TextEditOptions.builder().setAppendMethodArgumentSnippets(false);
if (treePath.getLeaf() instanceof MemberSelectTree) {
ExpressionTree parentExpression = ((MemberSelectTree) treePath.getLeaf()).getExpression();
Optional<ImportTree> importNode = findNodeOfType(treePath, ImportTree.class);
if (importNode.isPresent()) {
if (importNode.get().isStatic()) {
action =
CompleteMemberAction.forImportStatic(parentExpression, typeSolver, expressionSolver);
} else {
action = CompleteMemberAction.forImport(parentExpression, typeSolver, expressionSolver);
}
} else {
action =
CompleteMemberAction.forMemberSelect(parentExpression, typeSolver, expressionSolver);
textEditOptions.setAppendMethodArgumentSnippets(true);
}
} else if (treePath.getLeaf() instanceof MemberReferenceTree) {
ExpressionTree parentExpression =
((MemberReferenceTree) treePath.getLeaf()).getQualifierExpression();
action =
CompleteMemberAction.forMethodReference(parentExpression, typeSolver, expressionSolver);
} else if (treePath.getLeaf() instanceof LiteralTree) {
// Do not complete on any literals, especially strings.
action = NoCandidateAction.INSTANCE;
} else {
action = new CompleteSymbolAction(typeSolver, expressionSolver);
textEditOptions.setAppendMethodArgumentSnippets(true);
}
// When the cursor is before an opening parenthesis, it's likely the user is
// trying to change the name of a method invocation. In this case the
// arguments are already there and we should not append method argument
// snippet upon completion.
if ("(".equals(contentWithLineMap.substring(line, column, 1))) {
textEditOptions.setAppendMethodArgumentSnippets(false);
}
ImmutableList<CompletionCandidate> candidates =
action.getCompletionCandidates(positionContext, prefix);
return CompletionResult.builder()
.setFilePath(contentWithLineMap.getFilePath())
.setLine(line)
.setColumn(column)
.setPrefix(prefix)
.setCompletionCandidates(candidates)
.setTextEditOptions(textEditOptions.build())
.build();
}
private CompletionResult getCompletionCandidatesFromCache(int line, int column, String prefix) {
ImmutableList<CompletionCandidate> narrowedCandidates =
new CompletionCandidateListBuilder(prefix)
.addCandidates(cachedCompletion.getCompletionCandidates())
.build();
return cachedCompletion
.toBuilder()
.setCompletionCandidates(narrowedCandidates)
.setLine(line)
.setColumn(column)
.setPrefix(prefix)
.build();
}
private static <T extends Tree> Optional<T> findNodeOfType(TreePath treePath, Class<T> type) {
while (treePath != null) {
Tree leaf = treePath.getLeaf();
if (type.isAssignableFrom(leaf.getClass())) {
@SuppressWarnings("unchecked")
T casted = (T) leaf;
return Optional.of(casted);
}
treePath = treePath.getParentPath();
}
return Optional.empty();
}
/** A {@link CompletionAction} that always returns an empty list of candidates. */
private static class NoCandidateAction implements CompletionAction {
public static final NoCandidateAction INSTANCE = new NoCandidateAction();
@Override
public ImmutableList<CompletionCandidate> getCompletionCandidates(
PositionContext positionContext, String prefix) {
return ImmutableList.of();
}
}
}