Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bracket matching #50

Merged
merged 25 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
faa51d6
Adds abstractions for bracket matching
dingfeli Sep 26, 2024
b6485ab
Disables auto closing brackets during typeahead
dingfeli Sep 27, 2024
eb5a6f4
Adds segment factory
dingfeli Sep 27, 2024
a54beb4
Changes inline renderer listener to use segments to paint
dingfeli Sep 27, 2024
d470a7f
Separates rendering of brackets from normal buffer
dingfeli Sep 27, 2024
cac5764
Renders closing bracket separately from the rest of the suggestion
dingfeli Sep 30, 2024
0d3f437
Highlights bracket pairs
dingfeli Oct 1, 2024
f824ac5
Adds logic to insert closing bracket on premature preview termination
dingfeli Oct 1, 2024
e384878
Terminates session should caret have moved prior to suggestion is ren…
dingfeli Oct 1, 2024
d6ca4d1
Adds logic to check the number characters deleted
dingfeli Oct 2, 2024
00be4d3
Clears suggestion segments prior to priming for new suggestion
dingfeli Oct 2, 2024
ba5a871
Fixes premture preview termination
dingfeli Oct 2, 2024
a39e415
Dedupes typed ahead content from acceptance insertion
dingfeli Oct 2, 2024
721c2e7
Changes color of the auto closing brackets
dingfeli Oct 2, 2024
c4364f3
Corrects logic for backspacing during typeahead
dingfeli Oct 2, 2024
3312be7
Fixes caret jumps from editor breaking preview
dingfeli Oct 2, 2024
740b969
Untrims first line of suggestion
dingfeli Oct 2, 2024
1bed68c
Adds workbench listener to revert user settings changed on shutdown
dingfeli Oct 2, 2024
30a1ed1
Fixes checkstyle errors
dingfeli Oct 2, 2024
c814d7e
Accommodates for scenarios where editor and suggestions do not agree …
dingfeli Oct 3, 2024
380712f
Improves suggestion text sanitization logic
dingfeli Oct 3, 2024
e3ae679
Merge branch 'main' into bracket-matching
dingfeli Oct 3, 2024
45d58ba
Fixes checkstyle error
dingfeli Oct 3, 2024
066577e
Merge branch 'main' into bracket-matching
breedloj Oct 4, 2024
6c6f2ee
Chanages bracket type NADA member to NONE
dingfeli Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ public final Object execute(final ExecutionEvent event) throws ExecutionExceptio

private void insertSuggestion(final String suggestion) {
try {
var viewer = QInvocationSession.getInstance().getViewer();
var qSes = QInvocationSession.getInstance();
var viewer = qSes.getViewer();
IDocument doc = viewer.getDocument();
var widget = viewer.getTextWidget();
var insertOffset = widget.getCaretOffset();
doc.replace(insertOffset, 0, suggestion);
widget.setCaretOffset(insertOffset + suggestion.length());
int startIdx = widget.getCaretOffset() - qSes.getInvocationOffset();
String adjustedSuggestion = suggestion.substring(startIdx);
doc.replace(insertOffset, 0, adjustedSuggestion);
widget.setCaretOffset(insertOffset + adjustedSuggestion.length());
QInvocationSession.getInstance().getViewer().getTextWidget().redraw();
QInvocationSession.getInstance().executeCallbackForCodeReference();
QInvocationSession.getInstance().end();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
public enum CaretMovementReason {
UNEXAMINED,
MOVEMENT_KEY,
TEXT_INPUT
TEXT_INPUT,
MOUSE,
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.

package software.aws.toolkits.eclipse.amazonq.util;

import org.eclipse.ui.services.IDisposable;

public interface IQInlineBracket extends IDisposable {
void onTypeOver();

void onDelete();

void pairUp(IQInlineBracket partner);

boolean hasPairedUp();

String getAutoCloseContent(boolean isBracketSetToAutoClose, boolean isBracesSetToAutoClose,
boolean isStringSetToAutoClose);

int getRelevantOffset();

char getSymbol();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.

package software.aws.toolkits.eclipse.amazonq.util;

import org.eclipse.swt.graphics.GC;

public interface IQInlineSuggestionSegment {
void render(GC gc, int currentCaretOffset);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.

package software.aws.toolkits.eclipse.amazonq.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public final class IQInlineSuggestionSegmentFactory {

private IQInlineSuggestionSegmentFactory() {
}

private enum BracketType {
OPEN, CLOSE, NONE;
}

public static List<IQInlineSuggestionSegment> getSegmentsFromSuggestion(final QInvocationSession qSes) {
var suggestion = qSes.getCurrentSuggestion().getInsertText();
var suggestionLines = suggestion.split("\\R");
var res = new ArrayList<IQInlineSuggestionSegment>();
var widget = qSes.getViewer().getTextWidget();
int currentOffset = widget.getCaretOffset();
int distanceTraversed = 0;
Stack<QInlineSuggestionOpenBracketSegment> unresolvedBrackets = new Stack<>();
for (int i = 0; i < suggestionLines.length; i++) {
int startOffset;
int endOffset;
String currentLine = suggestionLines[i];
StringBuilder sb;

startOffset = currentOffset + distanceTraversed; // this line might not exist yet so we need to think of
// something more robust
sb = new StringBuilder(currentLine);

String currentIndent;
if (i == 0) {
int currentLineInDoc = widget.getLineAtOffset(currentOffset);
String content = widget.getLine(currentLineInDoc);
int leadingWhitespacePosition = !content.isEmpty() ? idxOfFirstNonwhiteSpace(content) : 0;
currentIndent = content.substring(0, leadingWhitespacePosition);
} else {
int leadingWhitespacePosition = idxOfFirstNonwhiteSpace(currentLine);
currentIndent = currentLine.substring(0, leadingWhitespacePosition);
}
for (int j = 0; j < currentLine.length(); j++) {
char c = currentLine.charAt(j);
switch (getBracketType(unresolvedBrackets, suggestion, distanceTraversed + j)) {
case OPEN:
var openBracket = new QInlineSuggestionOpenBracketSegment(startOffset + j, currentIndent, c);
unresolvedBrackets.push(openBracket);
break;
case CLOSE:
if (!unresolvedBrackets.isEmpty()) {
var closeBracket = new QInlineSuggestionCloseBracketSegment(startOffset + j, i,
currentLine.substring(0, j), c);
var top = unresolvedBrackets.pop();
if (top.isAMatch(closeBracket)) {
top.pairUp(closeBracket);
sb.setCharAt(j, ' ');
res.add(closeBracket);
res.add(top);
} else {
top.dispose();
closeBracket.dispose();
}
}
break;
case NONE:
default:
continue;
}
}
distanceTraversed += sb.length() + 1; // plus one because we got rid of a \\R when we split it
endOffset = startOffset + sb.length() - 1;
res.add(new QInlineSuggestionNormalSegment(startOffset, endOffset, i, sb.toString()));
}
return res;
}

private static BracketType getBracketType(final Stack<QInlineSuggestionOpenBracketSegment> unresolvedBrackets,
final String input, final int idx) {
if (isCloseBracket(input, idx, unresolvedBrackets)) {
// TODO: enrich logic here to eliminate false positive
return BracketType.CLOSE;
} else if (isOpenBracket(input, idx)) {
// TODO: enrich logic here to eliminate false positive
return BracketType.OPEN;
}
return BracketType.NONE;
}

private static boolean isCloseBracket(final String input, final int idx,
final Stack<QInlineSuggestionOpenBracketSegment> unresolvedBrackets) {
char c = input.charAt(idx);
boolean isBracket = c == ')' || c == ']' || c == '}' || c == '>' || c == '"' || c == '\'';
if (!isBracket) {
return false;
}
if (c == '"' || c == '\'') {
return !unresolvedBrackets.isEmpty() && unresolvedBrackets.peek().getSymbol() == c;
}
// TODO: enrich this check to eliminate false positives
if (idx > 0 && Character.isWhitespace(input.charAt(idx - 1)) && c == '>') {
return false;
}
return true;
}

private static boolean isOpenBracket(final String input, final int idx) {
char c = input.charAt(idx);
boolean isBracket = c == '(' || c == '[' || c == '{' || c == '<' || c == '"' || c == '\'';
if (!isBracket) {
return false;
}
// TODO: enrich this check to eliminate false postives
if (idx > 0 && Character.isWhitespace(input.charAt(idx - 1)) && c == '<') {
return false;
}
return true;
}

private static int idxOfFirstNonwhiteSpace(final String input) {
for (int i = 0; i < input.length(); i++) {
if (input.charAt(i) != ' ' && input.charAt(i) != '\t') {
return i;
}
}
return input.length();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

public final class QInlineCaretListener implements CaretListener {
private StyledText widget = null;
private int previousLine = -1;
private int previousLine;

public QInlineCaretListener(final StyledText widget) {
this.widget = widget;
Expand All @@ -27,12 +27,15 @@ public void caretMoved(final CaretEvent event) {
return;
}

if (qInvocationSessionInstance.isPreviewingSuggestions()) {
if (qInvocationSessionInstance.isPreviewingSuggestions()
&& caretMovementReason != CaretMovementReason.UNEXAMINED) {
qInvocationSessionInstance.transitionToDecisionMade(previousLine + 1);
qInvocationSessionInstance.end();
return;
}
}

previousLine = widget.getCaretOffset();
public int getLastKnownLine() {
return previousLine;
}
}
Loading