From f17c3650ec709796cf0592964cf94ed8cf0f1544 Mon Sep 17 00:00:00 2001 From: Felix Ding Date: Tue, 24 Sep 2024 20:54:05 -0700 Subject: [PATCH] Adds code reference view (#29) * Adds code reference view * Adds fqfn to file being changed * Separates model reference model types into their own class files * Includes changes to InlineCompletionItem --------- Co-authored-by: Jonathan Breedlove --- plugin/plugin.xml | 8 +++ .../handlers/QAcceptSuggestionsHandler.java | 4 +- .../lsp/model/InlineCompletionItem.java | 9 +++ .../lsp/model/InlineCompletionReference.java | 42 +++++++++++ .../InlineCompletionReferencePosition.java | 24 +++++++ .../util/CodeReferenceAcceptanceCallback.java | 8 +++ .../amazonq/util/QInlineRendererListener.java | 2 +- .../util/QInlineVerifyKeyListener.java | 2 +- .../amazonq/util/QInvocationSession.java | 24 +++++-- .../amazonq/util/QSuggestionContext.java | 12 ++-- .../views/AmazonQCodeReferenceView.java | 69 +++++++++++++++++++ 11 files changed, 190 insertions(+), 14 deletions(-) create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionReference.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionReferencePosition.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/util/CodeReferenceAcceptanceCallback.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQCodeReferenceView.java diff --git a/plugin/plugin.xml b/plugin/plugin.xml index f5737278..45fbcd51 100644 --- a/plugin/plugin.xml +++ b/plugin/plugin.xml @@ -24,6 +24,14 @@ category="amazonq" inject="true"> + + this.insertSuggestion(suggestion)); + display.syncExec(() -> this.insertSuggestion(suggestion.getInsertText())); return null; } @@ -37,6 +37,8 @@ private void insertSuggestion(final String suggestion) { doc.replace(insertOffset, 0, suggestion); widget.setCaretOffset(insertOffset + suggestion.length()); QInvocationSession.getInstance().transitionToDecisionMade(); + QInvocationSession.getInstance().getViewer().getTextWidget().redraw(); + QInvocationSession.getInstance().executeCallbackForCodeReference(); QInvocationSession.getInstance().end(); } catch (BadLocationException e) { PluginLogger.error(e.toString()); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionItem.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionItem.java index fe79cda6..86480b1e 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionItem.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionItem.java @@ -7,6 +7,7 @@ public class InlineCompletionItem { private String itemId; private String insertText; + private InlineCompletionReference[] references; public final String getItemId() { return itemId; @@ -23,4 +24,12 @@ public final String getInsertText() { public final void setInsertText(final String insertText) { this.insertText = insertText; } + + public final InlineCompletionReference[] getReferences() { + return references; + } + + public final void setReferences(final InlineCompletionReference[] references) { + this.references = references; + } } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionReference.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionReference.java new file mode 100644 index 00000000..e8bf42ab --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionReference.java @@ -0,0 +1,42 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.lsp.model; + +public class InlineCompletionReference { + private String referenceName; + private String referenceUrl; + private String licenseName; + private InlineCompletionReferencePosition position; + + public final void setReferenceName(final String referenceName) { + this.referenceName = referenceName; + } + + public final void setReferenceUrl(final String referenceUrl) { + this.referenceUrl = referenceUrl; + } + + public final void setLicenseName(final String licenseName) { + this.licenseName = licenseName; + } + + public final void setPosition(final InlineCompletionReferencePosition position) { + this.position = position; + } + + public final String getReferenceName() { + return referenceName; + } + + public final String getReferenceUrl() { + return referenceUrl; + } + + public final String getLicenseName() { + return licenseName; + } + + public final InlineCompletionReferencePosition getPosition() { + return position; + } +} diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionReferencePosition.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionReferencePosition.java new file mode 100644 index 00000000..7857a96c --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionReferencePosition.java @@ -0,0 +1,24 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.lsp.model; + +public class InlineCompletionReferencePosition { + private int startCharacter; + private int endCharacter; + + public final void setStartCharacter(final int startCharacter) { + this.startCharacter = startCharacter; + } + + public final void setEndCharacter(final int endCharacter) { + this.endCharacter = endCharacter; + } + + public final int getStartCharacter() { + return startCharacter; + } + + public final int getEndCharacter() { + return endCharacter; + } +} diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/CodeReferenceAcceptanceCallback.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/CodeReferenceAcceptanceCallback.java new file mode 100644 index 00000000..da4ff2b2 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/CodeReferenceAcceptanceCallback.java @@ -0,0 +1,8 @@ +package software.aws.toolkits.eclipse.amazonq.util; + +import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionItem; + +@FunctionalInterface +public interface CodeReferenceAcceptanceCallback { + void onCallback(InlineCompletionItem suggestionItem, int startLine); +} diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineRendererListener.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineRendererListener.java index 05b3534a..62304a51 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineRendererListener.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineRendererListener.java @@ -29,7 +29,7 @@ public final void paintControl(final PaintEvent e) { var widget = QInvocationSession.getInstance().getViewer().getTextWidget(); var location = widget.getLocationAtOffset(widget.getCaretOffset()); - var suggestion = QInvocationSession.getInstance().getCurrentSuggestion(); + var suggestion = QInvocationSession.getInstance().getCurrentSuggestion().getInsertText(); int invocationOffset = QInvocationSession.getInstance().getInvocationOffset(); var suggestionParts = suggestion.split("\\R"); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineVerifyKeyListener.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineVerifyKeyListener.java index 5f5484e8..461f3d31 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineVerifyKeyListener.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineVerifyKeyListener.java @@ -44,7 +44,7 @@ public void verifyKey(final VerifyEvent event) { qInvocationSessionInstance.setCaretMovementReason(CaretMovementReason.TEXT_INPUT); // Here we conduct typeahead logic - String currentSuggestion = qInvocationSessionInstance.getCurrentSuggestion().trim(); + String currentSuggestion = qInvocationSessionInstance.getCurrentSuggestion().getInsertText().trim(); int currentOffset = widget.getCaretOffset(); qInvocationSessionInstance .setHasBeenTypedahead(currentOffset - qInvocationSessionInstance.getInvocationOffset() > 0); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInvocationSession.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInvocationSession.java index 6f7fbec8..0f353758 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInvocationSession.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInvocationSession.java @@ -47,6 +47,7 @@ public final class QInvocationSession extends QResource { private boolean isLastKeyNewLine = false; private int[] headOffsetAtLine = new int[500]; private boolean hasBeenTypedahead = false; + private CodeReferenceAcceptanceCallback codeReferenceAcceptanceCallback = null; private Runnable unsetVerticalIndent; // Private constructor to prevent instantiation @@ -123,10 +124,8 @@ public void invoke() { AuthUtils.updateToken().get(); } - List newSuggestions = LspProvider.getAmazonQServer().get() - .inlineCompletionWithReferences(params).thenApply(result -> result.getItems().stream() - .map(InlineCompletionItem::getInsertText).collect(Collectors.toList())) - .get(); + List newSuggestions = LspProvider.getAmazonQServer().get() + .inlineCompletionWithReferences(params).thenApply(result -> result.getItems()).get(); Display.getDefault().asyncExec(() -> { if (newSuggestions == null || newSuggestions.isEmpty()) { @@ -287,7 +286,7 @@ public int getHeadOffsetAtLine(final int lineNum) throws IllegalArgumentExceptio return headOffsetAtLine[lineNum]; } - public String getCurrentSuggestion() { + public InlineCompletionItem getCurrentSuggestion() { if (suggestionsContext == null) { PluginLogger.warn("QSuggestion context is null"); return null; @@ -303,7 +302,7 @@ public String getCurrentSuggestion() { throw new IllegalStateException("QSuggestion showing discarded suggestions"); } - return details.get(index).getSuggestion(); + return details.get(index).getInlineCompletionItem(); } public void decrementCurrentSuggestionIndex() { @@ -328,6 +327,19 @@ public boolean hasBeenTypedahead() { return hasBeenTypedahead; } + public void registerCallbackForCodeReference(final CodeReferenceAcceptanceCallback codeReferenceAcceptanceCallback) { + this.codeReferenceAcceptanceCallback = codeReferenceAcceptanceCallback; + } + + public void executeCallbackForCodeReference() { + if (codeReferenceAcceptanceCallback != null) { + var selectedSuggestion = getCurrentSuggestion(); + var widget = viewer.getTextWidget(); + int startLine = widget.getLineAtOffset(invocationOffset); + codeReferenceAcceptanceCallback.onCallback(selectedSuggestion, startLine); + } + } + public void setVerticalIndent(int line, int height) { var widget = viewer.getTextWidget(); widget.setLineVerticalIndent(line, height); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QSuggestionContext.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QSuggestionContext.java index a655c9bd..91cb0157 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QSuggestionContext.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QSuggestionContext.java @@ -3,17 +3,19 @@ package software.aws.toolkits.eclipse.amazonq.util; +import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionItem; + public final class QSuggestionContext { - private String suggestion; + private InlineCompletionItem inlineCompletionItem; private QSuggestionState state; - public QSuggestionContext(final String suggestion) { - this.suggestion = suggestion; + public QSuggestionContext(final InlineCompletionItem inlineCompletionItem) { + this.inlineCompletionItem = inlineCompletionItem; state = QSuggestionState.UNSEEN; } - public String getSuggestion() { - return suggestion; + public InlineCompletionItem getInlineCompletionItem() { + return inlineCompletionItem; } public QSuggestionState getState() { diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQCodeReferenceView.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQCodeReferenceView.java new file mode 100644 index 00000000..8bd121cd --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQCodeReferenceView.java @@ -0,0 +1,69 @@ +package software.aws.toolkits.eclipse.amazonq.views; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.part.ViewPart; +import software.aws.toolkits.eclipse.amazonq.util.QInvocationSession; + +public final class AmazonQCodeReferenceView extends ViewPart { + + public static final String ID = "software.aws.toolkits.eclipse.amazonq.views.AmazonQCodeReferenceView"; + private static final String CR_TEMPLATE = """ + [%s] Accepted recommendation with code + %s + provided with reference under %s from repository %s. Added to %s (lines from %d to %d) + """; + + private StyledText textArea; + + @Override + public void createPartControl(final Composite parent) { + if (textArea == null) { + textArea = new StyledText(parent, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + } + + QInvocationSession qInvocationSessionInstance = QInvocationSession.getInstance(); + + qInvocationSessionInstance.registerCallbackForCodeReference((item, startLine) -> { + var references = item.getReferences(); + var editor = qInvocationSessionInstance.getEditor(); + String fqfn = editor.getTitle(); + if (references != null && references.length > 0) { + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy, HH:mm:ss a"); + String formattedDateTime = now.format(formatter); + int suggestionTextDepth = item.getInsertText().split("\n").length; + for (var reference : references) { + String itemToShow = String.format(CR_TEMPLATE, formattedDateTime, item.getInsertText(), + reference.getLicenseName(), reference.getReferenceUrl(), + fqfn, startLine, startLine + suggestionTextDepth); + int boldStart = textArea.getCharCount(); + int boldLength = itemToShow.split("\n", 2)[0].length(); + + StyleRange styleRange = new StyleRange(); + styleRange.start = boldStart; + styleRange.length = boldLength; + styleRange.fontStyle = SWT.BOLD; + + textArea.append(itemToShow); + textArea.append("\n"); + textArea.setStyleRange(styleRange); + + } + } + }); + + textArea.append( + "Your organization controls whether suggestions include code with references. To update these settings, please contact your admin.\n"); + } + + @Override + public void setFocus() { + return; + } +}