]>
Commit | Line | Data |
---|---|---|
1b2798f6 EK |
1 | /******************************************************************************* |
2 | * Copyright (c) 2000, 2012 IBM Corporation and others. | |
3 | * All rights reserved. This program and the accompanying materials | |
4 | * are made available under the terms of the Eclipse Public License v1.0 | |
5 | * which accompanies this distribution, and is available at | |
6 | * http://www.eclipse.org/legal/epl-v10.html | |
7 | * | |
8 | * Contributors: | |
9 | * Andrew McCullough - initial API and implementation | |
10 | * IBM Corporation - general improvement and bug fixes, partial reimplementation | |
11 | *******************************************************************************/ | |
12 | package org.eclipse.jdt.internal.ui.text.java; | |
13 | ||
14 | import org.eclipse.swt.SWT; | |
15 | import org.eclipse.swt.events.VerifyEvent; | |
16 | import org.eclipse.swt.graphics.Point; | |
17 | import org.eclipse.swt.widgets.Shell; | |
18 | ||
19 | import org.eclipse.core.runtime.Platform; | |
20 | ||
21 | import org.eclipse.jface.dialogs.MessageDialog; | |
22 | ||
23 | import org.eclipse.jface.text.BadLocationException; | |
24 | import org.eclipse.jface.text.BadPositionCategoryException; | |
25 | import org.eclipse.jface.text.IDocument; | |
26 | import org.eclipse.jface.text.IPositionUpdater; | |
27 | import org.eclipse.jface.text.IRegion; | |
28 | import org.eclipse.jface.text.Position; | |
29 | import org.eclipse.jface.text.Region; | |
30 | import org.eclipse.jface.text.contentassist.ICompletionProposal; | |
31 | import org.eclipse.jface.text.link.ILinkedModeListener; | |
32 | import org.eclipse.jface.text.link.InclusivePositionUpdater; | |
33 | import org.eclipse.jface.text.link.LinkedModeModel; | |
34 | import org.eclipse.jface.text.link.LinkedModeUI; | |
35 | import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags; | |
36 | import org.eclipse.jface.text.link.LinkedPosition; | |
37 | import org.eclipse.jface.text.link.LinkedPositionGroup; | |
38 | import org.eclipse.jface.text.link.ProposalPosition; | |
39 | ||
40 | import org.eclipse.ui.IEditorPart; | |
41 | ||
42 | import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; | |
43 | ||
44 | import org.eclipse.jdt.core.CompletionContext; | |
45 | import org.eclipse.jdt.core.CompletionProposal; | |
46 | import org.eclipse.jdt.core.IJavaElement; | |
47 | import org.eclipse.jdt.core.JavaModelException; | |
48 | import org.eclipse.jdt.core.Signature; | |
49 | ||
50 | import org.eclipse.jdt.internal.corext.template.java.SignatureUtil; | |
51 | ||
52 | import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; | |
53 | ||
54 | import org.eclipse.jdt.internal.ui.JavaPlugin; | |
55 | import org.eclipse.jdt.internal.ui.javaeditor.EditorHighlightingSynchronizer; | |
56 | import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; | |
57 | ||
58 | ||
59 | /** | |
60 | * This is a {@link org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal} which includes templates | |
61 | * that represent the best guess completion for each parameter of a method. | |
62 | */ | |
63 | public class ParameterGuessingProposal extends JavaMethodCompletionProposal { | |
64 | ||
65 | /** | |
66 | * Creates a {@link ParameterGuessingProposal} or <code>null</code> if the core context isn't available or extended. | |
67 | * | |
68 | * @param proposal the original completion proposal | |
69 | * @param context the currrent context | |
70 | * @param fillBestGuess if set, the best guess will be filled in | |
71 | * | |
72 | * @return a proposal or <code>null</code> | |
73 | */ | |
74 | public static ParameterGuessingProposal createProposal(CompletionProposal proposal, JavaContentAssistInvocationContext context, boolean fillBestGuess) { | |
75 | CompletionContext coreContext= context.getCoreContext(); | |
76 | if (coreContext != null && coreContext.isExtended()) { | |
77 | return new ParameterGuessingProposal(proposal, context, coreContext, fillBestGuess); | |
78 | } | |
79 | return null; | |
80 | } | |
81 | ||
82 | ||
83 | ||
84 | /** Tells whether this class is in debug mode. */ | |
85 | private static final boolean DEBUG= "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jdt.ui/debug/ResultCollector")); //$NON-NLS-1$//$NON-NLS-2$ | |
86 | ||
87 | ICompletionProposal[][] fChoices; // initialized by guessParameters() | |
88 | Position[] fPositions; // initialized by guessParameters() | |
89 | ||
90 | private IRegion fSelectedRegion; // initialized by apply() | |
91 | private IPositionUpdater fUpdater; | |
92 | ||
93 | private final boolean fFillBestGuess; | |
94 | ||
95 | private final CompletionContext fCoreContext; | |
96 | ||
97 | private ParameterGuessingProposal(CompletionProposal proposal, JavaContentAssistInvocationContext context, CompletionContext coreContext, boolean fillBestGuess) { | |
98 | super(proposal, context); | |
99 | fCoreContext= coreContext; | |
100 | fFillBestGuess= fillBestGuess; | |
101 | } | |
102 | ||
103 | private IJavaElement getEnclosingElement() { | |
104 | return fCoreContext.getEnclosingElement(); | |
105 | } | |
106 | ||
107 | private IJavaElement[][] getAssignableElements() { | |
108 | char[] signature= SignatureUtil.fix83600(getProposal().getSignature()); | |
109 | char[][] types= Signature.getParameterTypes(signature); | |
110 | ||
111 | IJavaElement[][] assignableElements= new IJavaElement[types.length][]; | |
112 | for (int i= 0; i < types.length; i++) { | |
113 | assignableElements[i]= fCoreContext.getVisibleElements(new String(types[i])); | |
114 | } | |
115 | return assignableElements; | |
116 | } | |
117 | ||
118 | /* | |
119 | * @see ICompletionProposalExtension#apply(IDocument, char) | |
120 | */ | |
121 | @Override | |
122 | public void apply(final IDocument document, char trigger, int offset) { | |
123 | try { | |
124 | super.apply(document, trigger, offset); | |
125 | ||
126 | int baseOffset= getReplacementOffset(); | |
127 | String replacement= getReplacementString(); | |
128 | ||
129 | if (fPositions != null && getTextViewer() != null) { | |
130 | ||
131 | LinkedModeModel model= new LinkedModeModel(); | |
132 | ||
133 | for (int i= 0; i < fPositions.length; i++) { | |
134 | LinkedPositionGroup group= new LinkedPositionGroup(); | |
135 | int positionOffset= fPositions[i].getOffset(); | |
136 | int positionLength= fPositions[i].getLength(); | |
137 | ||
138 | if (fChoices[i].length < 2) { | |
139 | group.addPosition(new LinkedPosition(document, positionOffset, positionLength, LinkedPositionGroup.NO_STOP)); | |
140 | } else { | |
141 | ensurePositionCategoryInstalled(document, model); | |
142 | document.addPosition(getCategory(), fPositions[i]); | |
143 | group.addPosition(new ProposalPosition(document, positionOffset, positionLength, LinkedPositionGroup.NO_STOP, fChoices[i])); | |
144 | } | |
145 | model.addGroup(group); | |
146 | } | |
147 | ||
148 | model.forceInstall(); | |
149 | JavaEditor editor= getJavaEditor(); | |
150 | if (editor != null) { | |
151 | model.addLinkingListener(new EditorHighlightingSynchronizer(editor)); | |
152 | } | |
153 | ||
154 | LinkedModeUI ui= new EditorLinkedModeUI(model, getTextViewer()); | |
155 | ui.setExitPosition(getTextViewer(), baseOffset + replacement.length(), 0, Integer.MAX_VALUE); | |
156 | ui.setExitPolicy(new ExitPolicy(')', document) { | |
157 | @Override | |
158 | public ExitFlags doExit(LinkedModeModel model2, VerifyEvent event, int offset2, int length) { | |
159 | if (event.character == ',') { | |
160 | for (int i= 0; i < fPositions.length - 1; i++) { // not for the last one | |
161 | Position position= fPositions[i]; | |
162 | if (position.offset <= offset2 && offset2 + length <= position.offset + position.length) { | |
163 | event.character= '\t'; | |
164 | event.keyCode= SWT.TAB; | |
165 | return null; | |
166 | } | |
167 | } | |
168 | } | |
169 | return super.doExit(model2, event, offset2, length); | |
170 | } | |
171 | }); | |
172 | ui.setCyclingMode(LinkedModeUI.CYCLE_WHEN_NO_PARENT); | |
173 | ui.setDoContextInfo(true); | |
174 | ui.enter(); | |
175 | fSelectedRegion= ui.getSelectedRegion(); | |
176 | ||
177 | } else { | |
178 | fSelectedRegion= new Region(baseOffset + replacement.length(), 0); | |
179 | } | |
180 | ||
181 | } catch (BadLocationException e) { | |
182 | ensurePositionCategoryRemoved(document); | |
183 | JavaPlugin.log(e); | |
184 | openErrorDialog(e); | |
185 | } catch (BadPositionCategoryException e) { | |
186 | ensurePositionCategoryRemoved(document); | |
187 | JavaPlugin.log(e); | |
188 | openErrorDialog(e); | |
189 | } | |
190 | } | |
191 | ||
192 | /* | |
193 | * @see org.eclipse.jdt.internal.ui.text.java.JavaMethodCompletionProposal#needsLinkedMode() | |
194 | */ | |
195 | @Override | |
196 | protected boolean needsLinkedMode() { | |
197 | return false; // we handle it ourselves | |
198 | } | |
199 | ||
200 | /* | |
201 | * @see org.eclipse.jdt.internal.ui.text.java.JavaMethodCompletionProposal#computeReplacementString() | |
202 | */ | |
203 | @Override | |
204 | protected String computeReplacementString() { | |
205 | ||
206 | if (!hasParameters() || !hasArgumentList()) | |
207 | return super.computeReplacementString(); | |
208 | ||
209 | long millis= DEBUG ? System.currentTimeMillis() : 0; | |
210 | String replacement; | |
211 | try { | |
212 | replacement= computeGuessingCompletion(); | |
213 | } catch (JavaModelException x) { | |
214 | fPositions= null; | |
215 | fChoices= null; | |
216 | JavaPlugin.log(x); | |
217 | openErrorDialog(x); | |
218 | return super.computeReplacementString(); | |
219 | } | |
220 | if (DEBUG) System.err.println("Parameter Guessing: " + (System.currentTimeMillis() - millis)); //$NON-NLS-1$ | |
221 | ||
222 | return replacement; | |
223 | } | |
224 | ||
225 | /** | |
226 | * Creates the completion string. Offsets and Lengths are set to the offsets and lengths of the | |
227 | * parameters. | |
228 | * | |
229 | * @return the completion string | |
230 | * @throws JavaModelException if parameter guessing failed | |
231 | */ | |
232 | private String computeGuessingCompletion() throws JavaModelException { | |
233 | ||
234 | StringBuffer buffer= new StringBuffer(); | |
235 | appendMethodNameReplacement(buffer); | |
236 | ||
237 | FormatterPrefs prefs= getFormatterPrefs(); | |
238 | ||
239 | return prefs.generated_1904169089833146011(buffer, this); | |
240 | } | |
241 | ||
242 | /** | |
243 | * Returns the currently active java editor, or <code>null</code> if it | |
244 | * cannot be determined. | |
245 | * | |
246 | * @return the currently active java editor, or <code>null</code> | |
247 | */ | |
248 | private JavaEditor getJavaEditor() { | |
249 | IEditorPart part= JavaPlugin.getActivePage().getActiveEditor(); | |
250 | if (part instanceof JavaEditor) | |
251 | return (JavaEditor) part; | |
252 | else | |
253 | return null; | |
254 | } | |
255 | ||
256 | ICompletionProposal[][] guessParameters(char[][] parameterNames) throws JavaModelException { | |
257 | // find matches in reverse order. Do this because people tend to declare the variable meant for the last | |
258 | // parameter last. That is, local variables for the last parameter in the method completion are more | |
259 | // likely to be closer to the point of code completion. As an example consider a "delegation" completion: | |
260 | // | |
261 | // public void myMethod(int param1, int param2, int param3) { | |
262 | // someOtherObject.yourMethod(param1, param2, param3); | |
263 | // } | |
264 | // | |
265 | // The other consideration is giving preference to variables that have not previously been used in this | |
266 | // code completion (which avoids "someOtherObject.yourMethod(param1, param1, param1)"; | |
267 | ||
268 | int count= parameterNames.length; | |
269 | fPositions= new Position[count]; | |
270 | fChoices= new ICompletionProposal[count][]; | |
271 | ||
272 | String[] parameterTypes= getParameterTypes(); | |
273 | ParameterGuesser guesser= new ParameterGuesser(getEnclosingElement()); | |
274 | IJavaElement[][] assignableElements= getAssignableElements(); | |
275 | ||
276 | for (int i= count - 1; i >= 0; i--) { | |
277 | String paramName= new String(parameterNames[i]); | |
278 | Position position= new Position(0,0); | |
279 | ||
280 | boolean isLastParameter= i == count - 1; | |
281 | ICompletionProposal[] argumentProposals= guesser.parameterProposals(parameterTypes[i], paramName, position, assignableElements[i], fFillBestGuess, isLastParameter); | |
282 | if (argumentProposals.length == 0) { | |
283 | JavaCompletionProposal proposal= new JavaCompletionProposal(paramName, 0, paramName.length(), null, paramName, 0); | |
284 | argumentProposals= proposal.generated_2597104358469167081(isLastParameter); | |
285 | } | |
286 | ||
287 | fPositions[i]= position; | |
288 | fChoices[i]= argumentProposals; | |
289 | } | |
290 | ||
291 | return fChoices; | |
292 | } | |
293 | ||
294 | private String[] getParameterTypes() { | |
295 | char[] signature= SignatureUtil.fix83600(fProposal.getSignature()); | |
296 | char[][] types= Signature.getParameterTypes(signature); | |
297 | ||
298 | String[] ret= new String[types.length]; | |
299 | for (int i= 0; i < types.length; i++) { | |
300 | ret[i]= new String(Signature.toCharArray(types[i])); | |
301 | } | |
302 | return ret; | |
303 | } | |
304 | ||
305 | /* | |
306 | * @see ICompletionProposal#getSelection(IDocument) | |
307 | */ | |
308 | @Override | |
309 | public Point getSelection(IDocument document) { | |
310 | if (fSelectedRegion == null) | |
311 | return new Point(getReplacementOffset(), 0); | |
312 | ||
313 | return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength()); | |
314 | } | |
315 | ||
316 | private void openErrorDialog(Exception e) { | |
317 | Shell shell= getTextViewer().getTextWidget().getShell(); | |
318 | MessageDialog.openError(shell, JavaTextMessages.ParameterGuessingProposal_error_msg, e.getMessage()); | |
319 | } | |
320 | ||
321 | private void ensurePositionCategoryInstalled(final IDocument document, LinkedModeModel model) { | |
322 | if (!document.containsPositionCategory(getCategory())) { | |
323 | document.addPositionCategory(getCategory()); | |
324 | fUpdater= new InclusivePositionUpdater(getCategory()); | |
325 | document.addPositionUpdater(fUpdater); | |
326 | ||
327 | model.addLinkingListener(new ILinkedModeListener() { | |
328 | ||
329 | /* | |
330 | * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel, int) | |
331 | */ | |
332 | public void left(LinkedModeModel environment, int flags) { | |
333 | ensurePositionCategoryRemoved(document); | |
334 | } | |
335 | ||
336 | public void suspend(LinkedModeModel environment) {} | |
337 | public void resume(LinkedModeModel environment, int flags) {} | |
338 | }); | |
339 | } | |
340 | } | |
341 | ||
342 | private void ensurePositionCategoryRemoved(IDocument document) { | |
343 | if (document.containsPositionCategory(getCategory())) { | |
344 | try { | |
345 | document.removePositionCategory(getCategory()); | |
346 | } catch (BadPositionCategoryException e) { | |
347 | // ignore | |
348 | } | |
349 | document.removePositionUpdater(fUpdater); | |
350 | } | |
351 | } | |
352 | ||
353 | private String getCategory() { | |
354 | return "ParameterGuessingProposal_" + toString(); //$NON-NLS-1$ | |
355 | } | |
356 | ||
357 | } |