Skip to content

Commit b079720

Browse files
committed
Merge branch 'main' of github.com:aws/amazon-q-eclipse into refactor-input-listener-for-inline
2 parents dc518ac + f17c365 commit b079720

17 files changed

+367
-153
lines changed

plugin/build.properties

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ bin.includes = plugin.xml,\
55
icons/,\
66
target/classes/,\
77
webview/build/,\
8-
lsp/,\
98
target/dependency/,\
109
auth/
1110
jre.compilation.profile = JavaSE-17

plugin/lsp/token-standalone-macos

-62 MB
Binary file not shown.

plugin/plugin.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424
category="amazonq"
2525
inject="true">
2626
</view>
27+
<view
28+
category="amazonq"
29+
class="software.aws.toolkits.eclipse.amazonq.views.AmazonQCodeReferenceView"
30+
icon="icons/AmazonQ.png"
31+
id="software.aws.toolkits.eclipse.amazonq.views.AmazonQCodeReferenceView"
32+
name="Amazon Q Code Reference"
33+
restorable="true">
34+
</view>
2735
</extension>
2836

2937
<extension

plugin/src/software/aws/toolkits/eclipse/amazonq/handlers/QAcceptSuggestionsHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public final Object execute(final ExecutionEvent event) throws ExecutionExceptio
2424
var suggestion = QInvocationSession.getInstance().getCurrentSuggestion();
2525
var widget = QInvocationSession.getInstance().getViewer().getTextWidget();
2626
Display display = widget.getDisplay();
27-
display.syncExec(() -> this.insertSuggestion(suggestion));
27+
display.syncExec(() -> this.insertSuggestion(suggestion.getInsertText()));
2828
return null;
2929
}
3030

@@ -37,6 +37,8 @@ private void insertSuggestion(final String suggestion) {
3737
doc.replace(insertOffset, 0, suggestion);
3838
widget.setCaretOffset(insertOffset + suggestion.length());
3939
QInvocationSession.getInstance().transitionToDecisionMade();
40+
QInvocationSession.getInstance().getViewer().getTextWidget().redraw();
41+
QInvocationSession.getInstance().executeCallbackForCodeReference();
4042
QInvocationSession.getInstance().end();
4143
} catch (BadLocationException e) {
4244
PluginLogger.error(e.toString());

plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionItem.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public class InlineCompletionItem {
77

88
private String itemId;
99
private String insertText;
10+
private InlineCompletionReference[] references;
1011

1112
public final String getItemId() {
1213
return itemId;
@@ -23,4 +24,12 @@ public final String getInsertText() {
2324
public final void setInsertText(final String insertText) {
2425
this.insertText = insertText;
2526
}
27+
28+
public final InlineCompletionReference[] getReferences() {
29+
return references;
30+
}
31+
32+
public final void setReferences(final InlineCompletionReference[] references) {
33+
this.references = references;
34+
}
2635
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
3+
package software.aws.toolkits.eclipse.amazonq.lsp.model;
4+
5+
public class InlineCompletionReference {
6+
private String referenceName;
7+
private String referenceUrl;
8+
private String licenseName;
9+
private InlineCompletionReferencePosition position;
10+
11+
public final void setReferenceName(final String referenceName) {
12+
this.referenceName = referenceName;
13+
}
14+
15+
public final void setReferenceUrl(final String referenceUrl) {
16+
this.referenceUrl = referenceUrl;
17+
}
18+
19+
public final void setLicenseName(final String licenseName) {
20+
this.licenseName = licenseName;
21+
}
22+
23+
public final void setPosition(final InlineCompletionReferencePosition position) {
24+
this.position = position;
25+
}
26+
27+
public final String getReferenceName() {
28+
return referenceName;
29+
}
30+
31+
public final String getReferenceUrl() {
32+
return referenceUrl;
33+
}
34+
35+
public final String getLicenseName() {
36+
return licenseName;
37+
}
38+
39+
public final InlineCompletionReferencePosition getPosition() {
40+
return position;
41+
}
42+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
3+
package software.aws.toolkits.eclipse.amazonq.lsp.model;
4+
5+
public class InlineCompletionReferencePosition {
6+
private int startCharacter;
7+
private int endCharacter;
8+
9+
public final void setStartCharacter(final int startCharacter) {
10+
this.startCharacter = startCharacter;
11+
}
12+
13+
public final void setEndCharacter(final int endCharacter) {
14+
this.endCharacter = endCharacter;
15+
}
16+
17+
public final int getStartCharacter() {
18+
return startCharacter;
19+
}
20+
21+
public final int getEndCharacter() {
22+
return endCharacter;
23+
}
24+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package software.aws.toolkits.eclipse.amazonq.util;
2+
3+
import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionItem;
4+
5+
@FunctionalInterface
6+
public interface CodeReferenceAcceptanceCallback {
7+
void onCallback(InlineCompletionItem suggestionItem, int startLine);
8+
}

plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineRendererListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public final void paintControl(final PaintEvent e) {
2929
var widget = QInvocationSession.getInstance().getViewer().getTextWidget();
3030

3131
var location = widget.getLocationAtOffset(widget.getCaretOffset());
32-
var suggestion = QInvocationSession.getInstance().getCurrentSuggestion();
32+
var suggestion = QInvocationSession.getInstance().getCurrentSuggestion().getInsertText();
3333
int invocationOffset = QInvocationSession.getInstance().getInvocationOffset();
3434
var suggestionParts = suggestion.split("\\R");
3535

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
3+
package software.aws.toolkits.eclipse.amazonq.util;
4+
5+
import java.util.Stack;
6+
7+
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
8+
import org.eclipse.core.runtime.preferences.InstanceScope;
9+
import org.eclipse.swt.SWT;
10+
import org.eclipse.swt.custom.StyledText;
11+
import org.eclipse.swt.custom.VerifyKeyListener;
12+
import org.eclipse.swt.events.VerifyEvent;
13+
14+
public final class QInlineVerifyKeyListener implements VerifyKeyListener {
15+
private StyledText widget = null;
16+
17+
public QInlineVerifyKeyListener(final StyledText widget) {
18+
this.widget = widget;
19+
}
20+
21+
@Override
22+
public void verifyKey(final VerifyEvent event) {
23+
var qInvocationSessionInstance = QInvocationSession.getInstance();
24+
if (qInvocationSessionInstance == null || qInvocationSessionInstance.getState() != QInvocationSessionState.SUGGESTION_PREVIEWING) {
25+
return;
26+
}
27+
// We need to provide the reason for the caret movement. This way we can perform
28+
// subsequent actions accordingly:
29+
// - If the caret has been moved due to traversals (i.e. arrow keys or mouse
30+
// click) we would want to end the invocation session since that signifies the
31+
// user no longer has the intent for text input at its original location.
32+
// - And if the caret has been moved due to typing, we will need to determine if
33+
// it's appropriate to perform a "typeahead".
34+
//
35+
// In effect, we would need to fulfill the following responsibilities.
36+
// - We identify whether text is being entered and update a state that
37+
// accessible by other listeners.
38+
// - We shall also examine said text to see if it matches the beginning of the
39+
// suggestion (if there is one) so we can fulfill "typeahead" functionality.
40+
if (event.keyCode == SWT.ARROW_UP || event.keyCode == SWT.ARROW_DOWN || event.keyCode == SWT.ARROW_LEFT || event.keyCode == SWT.ARROW_RIGHT) {
41+
System.out.println("Movement key registered");
42+
qInvocationSessionInstance.setCaretMovementReason(CaretMovementReason.MOVEMENT_KEY);
43+
} else {
44+
qInvocationSessionInstance.setCaretMovementReason(CaretMovementReason.TEXT_INPUT);
45+
46+
// Here we conduct typeahead logic
47+
String currentSuggestion = qInvocationSessionInstance.getCurrentSuggestion().getInsertText().trim();
48+
int currentOffset = widget.getCaretOffset();
49+
qInvocationSessionInstance
50+
.setHasBeenTypedahead(currentOffset - qInvocationSessionInstance.getInvocationOffset() > 0);
51+
IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode("org.eclipse.jdt.ui");
52+
// This needs to be defaulted to true. This key is only present in the
53+
// preference store if it is set to false.
54+
// Therefore if you can't find it, it has been set to true.
55+
boolean isAutoClosingEnabled = preferences.getBoolean("closeBrackets", true);
56+
57+
// We are deliberately leaving out curly braces here (i.e. '{') because eclipse
58+
// does not auto close curly braces unless there is a new line entered.
59+
boolean isInputClosingBracket = (event.character == ')' || event.character == ']' || event.character == '"' || event.character == '>');
60+
boolean isInputOpeningBracket = (event.character == '(' || event.character == '[' || event.character == '"' || event.character == '<');
61+
Stack<Character> closingBrackets = qInvocationSessionInstance.getClosingBrackets();
62+
63+
if (isAutoClosingEnabled) {
64+
if (closingBrackets == null) {
65+
closingBrackets = new Stack<>();
66+
}
67+
if (event.character == '(') {
68+
closingBrackets.push(')');
69+
} else if (event.character == '[') {
70+
closingBrackets.push(']');
71+
} else if (event.character == '"') {
72+
closingBrackets.push('"');
73+
} else if (event.character == '<') {
74+
closingBrackets.push('>');
75+
} else if (isInputClosingBracket) {
76+
Character topClosingBracket = closingBrackets.pop();
77+
if (!topClosingBracket.equals(event.character)) {
78+
System.out.println("Session terminated due to mismatching closing bracket");
79+
qInvocationSessionInstance.transitionToDecisionMade();
80+
qInvocationSessionInstance.end();
81+
}
82+
}
83+
}
84+
85+
int distanceAdjustedForAutoClosingBrackets = (isAutoClosingEnabled && (isInputOpeningBracket || isInputClosingBracket)) ? 1 : 0;
86+
// Note that distanceTraversed here is the zero-based index
87+
// We need to subtract from leading whitespace skipped the indent the editor had
88+
// placed the caret after at the new line.
89+
int newLineOffset = 0;
90+
boolean isLastKeyNewLine = qInvocationSessionInstance.isLastKeyNewLine();
91+
int leadingWhitespaceSkipped = qInvocationSessionInstance.getLeadingWhitespaceSkipped();
92+
int invocationOffset = qInvocationSessionInstance.getInvocationOffset();
93+
if (isLastKeyNewLine) {
94+
int currentLineInEditor = widget.getLineAtOffset(currentOffset);
95+
newLineOffset = currentOffset - widget.getOffsetAtLine(currentLineInEditor);
96+
leadingWhitespaceSkipped -= newLineOffset;
97+
qInvocationSessionInstance.setLeadingWhitespaceSkipped(leadingWhitespaceSkipped);
98+
qInvocationSessionInstance.setIsLastKeyNewLine(false);
99+
}
100+
int distanceTraversed = currentOffset - invocationOffset + leadingWhitespaceSkipped - distanceAdjustedForAutoClosingBrackets;
101+
102+
// If we are traversing backwards, we need to undo the adjustments we had done
103+
// for the following items as we come across them:
104+
// - whitespace
105+
// - brackets (?)
106+
if (event.keyCode == SWT.BS && distanceTraversed > 0
107+
&& currentOffset - 1 <= qInvocationSessionInstance.getHeadOffsetAtLine(widget.getLineAtOffset(currentOffset))
108+
) {
109+
qInvocationSessionInstance.setLeadingWhitespaceSkipped(leadingWhitespaceSkipped - 1);
110+
return;
111+
}
112+
// System.out.println("Distance traversed: " + distanceTraversed);
113+
// System.out.println("Leading whitespace skipped: " + leadingWhitespaceSkipped);
114+
// System.out.println("Distance adjusted for auto closing brackets: " + distanceAdjustedForAutoClosingBrackets);
115+
// System.out.println("Character typed: " + event.character);
116+
// System.out.println("Current caret offset: " + currentOffset);
117+
// System.out.println("Is auto closing brackets enabled: " + isAutoClosingEnabled);
118+
119+
// Terminate the session under the right conditions. These are:
120+
// - If what has been typed does not match what has been suggested (we are going
121+
// to assume the user does not want the suggestion)
122+
// We are excluding modifier keys that do not produce text on the screen (e.g.
123+
// shift, ctrl, option).
124+
// - If the caret position has exceeded the invocation offset leftwards.
125+
// - If the caret position has exceeded that of the end of the suggestion.
126+
// - If the user has typehead to the end of the suggest (we would not just
127+
// terminate the
128+
if (event.character == '\0' || event.keyCode == SWT.ESC) {
129+
// We have ESC mapped to reject command. We'll let the command take care of it.
130+
return;
131+
}
132+
if (distanceTraversed >= currentSuggestion.length() || distanceTraversed < 0) {
133+
qInvocationSessionInstance.transitionToDecisionMade();
134+
qInvocationSessionInstance.end();
135+
return;
136+
}
137+
138+
char currentCharInSuggestion = currentSuggestion.charAt(distanceTraversed);
139+
if ((distanceTraversed <= 0 && event.keyCode == SWT.BS) || distanceTraversed > currentSuggestion.length()
140+
|| ((event.keyCode != SWT.BS && event.keyCode != SWT.CR) && currentCharInSuggestion != event.character)
141+
|| (event.keyCode == SWT.CR && (currentCharInSuggestion != '\n' && currentCharInSuggestion != '\r'))) {
142+
qInvocationSessionInstance.transitionToDecisionMade();
143+
qInvocationSessionInstance.end();
144+
}
145+
146+
if (event.keyCode == SWT.CR) {
147+
qInvocationSessionInstance.setIsLastKeyNewLine(true);
148+
}
149+
150+
// We would also need to consider scenarios where the suggestion contains
151+
// formatting whitespace.
152+
// Should we come across them, we would need to do the following:
153+
// - Examine if the last key registered was a CR, if it isn't we treat it as
154+
// normal and examine the input verbatim
155+
// - Otherwise, we shall skip ahead until the first non-whitespace character in
156+
// the suggestion and increment `leadingWhitespaceSkipped` accordingly.
157+
if (event.keyCode == SWT.CR && distanceTraversed < currentSuggestion.length() - 1
158+
&& Character.isWhitespace(currentSuggestion.charAt(distanceTraversed + 1))
159+
&& currentSuggestion.charAt(distanceTraversed + 1) != '\n' && currentSuggestion.charAt(distanceTraversed + 1) != '\r') {
160+
int newWs = 0;
161+
while (Character.isWhitespace(currentSuggestion.charAt(distanceTraversed + 1 + newWs))) {
162+
newWs++;
163+
if ((distanceTraversed + 1 + newWs) > currentSuggestion.length()) {
164+
break;
165+
}
166+
}
167+
leadingWhitespaceSkipped += newWs;
168+
qInvocationSessionInstance.setLeadingWhitespaceSkipped(leadingWhitespaceSkipped);
169+
}
170+
}
171+
}
172+
}
173+

0 commit comments

Comments
 (0)