From 7f586da0c185253b8e399ec118ca441c8cfebf3d Mon Sep 17 00:00:00 2001 From: Carl-Robert Linnupuu Date: Mon, 5 Feb 2024 16:28:18 +0200 Subject: [PATCH] Revert "feat: code completion improvements" This reverts commit abc8dc8d071b87b57ce05c0702bf8053ffb88b38. --- .../carlrobert/codegpt/EncodingManager.java | 16 --- .../actions/DisableCompletionsAction.java | 4 +- .../actions/EnableCompletionsAction.java | 4 +- .../CodeCompletionEventListener.java | 5 +- .../CodeCompletionService.java | 85 +++++++++-- .../codecompletions/InfillRequestDetails.java | 30 ---- .../CodeCompletionServiceTest.java | 132 ++++++++++++------ .../codecompletions/code-completion-file.txt | 10 +- 8 files changed, 177 insertions(+), 109 deletions(-) diff --git a/src/main/java/ee/carlrobert/codegpt/EncodingManager.java b/src/main/java/ee/carlrobert/codegpt/EncodingManager.java index d4790dca..d245f66b 100644 --- a/src/main/java/ee/carlrobert/codegpt/EncodingManager.java +++ b/src/main/java/ee/carlrobert/codegpt/EncodingManager.java @@ -9,7 +9,6 @@ import com.knuddels.jtokkit.api.EncodingRegistry; import com.knuddels.jtokkit.api.EncodingType; import ee.carlrobert.codegpt.conversations.Conversation; import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionMessage; -import java.util.List; @Service public final class EncodingManager { @@ -53,19 +52,4 @@ public final class EncodingManager { return 0; } } - - /** - * Truncates the given text to the given number of tokens. - * - * @param text The text to truncate. - * @param maxTokens The maximum number of tokens to keep. - * @param fromStart Whether to truncate from the start or the end of the text. - * @return The truncated text. - */ - public String truncateText(String text, int maxTokens, boolean fromStart) { - List tokens = encoding.encode(text); - int tokensToRetrieve = Math.min(maxTokens, tokens.size()); - int startIndex = fromStart ? 0 : tokens.size() - tokensToRetrieve; - return encoding.decode(tokens.subList(startIndex, startIndex + tokensToRetrieve)); - } } diff --git a/src/main/java/ee/carlrobert/codegpt/actions/DisableCompletionsAction.java b/src/main/java/ee/carlrobert/codegpt/actions/DisableCompletionsAction.java index 4aa7382b..13e444d6 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/DisableCompletionsAction.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/DisableCompletionsAction.java @@ -1,8 +1,8 @@ package ee.carlrobert.codegpt.actions; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.DumbAwareAction; import ee.carlrobert.codegpt.codecompletions.CodeGPTEditorManager; import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; import org.jetbrains.annotations.NotNull; @@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull; /** * Disables code-completion.
Publishes message to {@link CodeCompletionEnabledListener#TOPIC} */ -public class DisableCompletionsAction extends DumbAwareAction { +public class DisableCompletionsAction extends AnAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { diff --git a/src/main/java/ee/carlrobert/codegpt/actions/EnableCompletionsAction.java b/src/main/java/ee/carlrobert/codegpt/actions/EnableCompletionsAction.java index e74fe41f..39fe132b 100644 --- a/src/main/java/ee/carlrobert/codegpt/actions/EnableCompletionsAction.java +++ b/src/main/java/ee/carlrobert/codegpt/actions/EnableCompletionsAction.java @@ -1,15 +1,15 @@ package ee.carlrobert.codegpt.actions; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.DumbAwareAction; import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; import org.jetbrains.annotations.NotNull; /** * Enables code-completion.
Publishes message to {@link CodeCompletionEnabledListener#TOPIC} */ -public class EnableCompletionsAction extends DumbAwareAction { +public class EnableCompletionsAction extends AnAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { diff --git a/src/main/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionEventListener.java b/src/main/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionEventListener.java index f6576e88..d20028a5 100644 --- a/src/main/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionEventListener.java +++ b/src/main/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionEventListener.java @@ -40,7 +40,8 @@ class CodeCompletionEventListener implements CompletionEventListener { progressIndicator.processFinish(); } - CodeGPTEditorManager.getInstance().disposeEditorInlays(editor); + var editorManager = CodeGPTEditorManager.getInstance(); + editorManager.disposeEditorInlays(editor); var inlayText = messageBuilder.toString(); if (!inlayText.isEmpty()) { @@ -59,7 +60,7 @@ class CodeCompletionEventListener implements CompletionEventListener { Notifications.Bus.notify(OverlayUtil.getDefaultNotification( String.format( CodeGPTBundle.get("notification.completionError.description"), - error.getMessage()), + ex.getMessage()), NotificationType.ERROR) .addAction(new OpenSettingsAction()), editor.getProject()); } diff --git a/src/main/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.java b/src/main/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.java index a982eb65..0b376346 100644 --- a/src/main/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.java +++ b/src/main/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionService.java @@ -6,7 +6,6 @@ import static ee.carlrobert.codegpt.CodeGPTKeys.SINGLE_LINE_INLAY; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; -import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.AnAction; @@ -25,9 +24,13 @@ import com.intellij.openapi.editor.InlayModel; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread; import com.intellij.util.concurrency.annotations.RequiresEdt; import com.intellij.util.concurrency.annotations.RequiresReadLock; import com.intellij.util.concurrency.annotations.RequiresWriteLock; @@ -35,12 +38,14 @@ import ee.carlrobert.codegpt.actions.CodeCompletionEnabledListener; import ee.carlrobert.codegpt.completions.CompletionRequestService; import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; import ee.carlrobert.codegpt.util.EditorUtil; +import ee.carlrobert.llm.completion.CompletionEventListener; import java.awt.event.KeyEvent; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; import javax.annotation.ParametersAreNonnullByDefault; import javax.swing.KeyStroke; +import okhttp3.sse.EventSource; import org.jetbrains.annotations.NotNull; @Service(PROJECT) @@ -48,6 +53,7 @@ import org.jetbrains.annotations.NotNull; public final class CodeCompletionService implements Disposable { public static final String APPLY_INLAY_ACTION_ID = "ApplyInlayAction"; + public static final int MAX_OFFSET = 4000; private static final Logger LOG = Logger.getInstance(CodeCompletionService.class); @@ -71,13 +77,16 @@ public final class CodeCompletionService implements Disposable { return project.getService(CodeCompletionService.class); } + public boolean isCompletionAllowed(PsiElement elementAtCaret) { + return elementAtCaret instanceof PsiWhiteSpace; + } + public void handleCompletions(Editor editor, int offset) { Project project = editor.getProject(); if (project == null || project.isDisposed() || !ConfigurationState.getInstance().isCodeCompletionsEnabled() || !EditorUtil.isSelectedEditor(editor) - || LookupManager.getActiveLookup(editor) != null || editor.isViewer() || editor.isOneLineMode() ) { @@ -90,16 +99,54 @@ public final class CodeCompletionService implements Disposable { return; } - var request = InfillRequestDetails.fromDocumentWithMaxOffset(document, offset); + PsiElement elementAtCaret = ReadAction.compute(() -> psiFile.findElementAt(offset)); + var completionService = CodeCompletionService.getInstance(project); + if (!completionService.isCompletionAllowed(elementAtCaret)) { + return; + } + callDebouncer.debounce( Void.class, - (progressIndicator) -> CompletionRequestService.getInstance().getCodeCompletionAsync( - request, + (progressIndicator) -> completionService.fetchCodeCompletion( + elementAtCaret, + offset, + document, new CodeCompletionEventListener(editor, offset, progressIndicator)), 500, TimeUnit.MILLISECONDS); } + /** + * Fetches code-completion (FIM) for the given position ({@code offsetInFile}) in the file.
+ * By default tries to find an enclosing {@link PsiMethod} or {@link PsiClass} for the given + * {@code offsetInFile} and only uses their content instead of the entire file's content. If no + * such enclosing {@link PsiElement} can be found, the file's entire content is used instead. + * + * @param elementAtCaret PsiElement at caret + * @param offsetInFile Global offset in the file. + * @param document If the offset is not enclosed in a {@link PsiMethod} nor a + * {@link PsiClass}, the entire file content is used for completion. + * @return Completion String + */ + @RequiresBackgroundThread + public EventSource fetchCodeCompletion( + PsiElement elementAtCaret, + int offsetInFile, + Document document, + CompletionEventListener eventListener) { + InfillRequestDetails requestDetails = tryFindEnclosingPsiElementTextRange( + List.of(PsiMethod.class, PsiClass.class), elementAtCaret) + .map(textRange -> createInfillRequest( + document, + offsetInFile, + textRange.getStartOffset(), + textRange.getEndOffset()) + ) + .orElse(createInfillRequest(document, offsetInFile)); + return CompletionRequestService.getInstance() + .getCodeCompletionAsync(requestDetails, eventListener); + } + @RequiresEdt public void addInlays(Editor editor, int caretOffset, String inlayText) { List linesList = inlayText.lines().collect(toList()); @@ -154,13 +201,11 @@ public final class CodeCompletionService implements Disposable { Document document = editor.getDocument(); document.insertString(offset, text); editor.getCaretModel().moveToOffset(offset + text.length()); - if (ConfigurationState.getInstance().isAutoFormattingEnabled()) { - EditorUtil.reformatDocument( - requireNonNull(editor.getProject()), - document, - offset, - offset + text.length()); - } + EditorUtil.reformatDocument( + requireNonNull(editor.getProject()), + document, + offset, + offset + text.length()); } @RequiresReadLock @@ -201,4 +246,20 @@ public final class CodeCompletionService implements Disposable { APPLY_INLAY_ACTION_ID, new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), null)); } + + private static InfillRequestDetails createInfillRequest(Document document, int offsetInFile) { + int begin = Integer.max(0, offsetInFile - MAX_OFFSET); + int end = Integer.min(document.getTextLength(), offsetInFile + MAX_OFFSET); + return createInfillRequest(document, offsetInFile, begin, end); + } + + private static InfillRequestDetails createInfillRequest( + Document document, + int caretOffset, + int start, + int end) { + return new InfillRequestDetails( + document.getText(new TextRange(start, caretOffset)), + document.getText(new TextRange(caretOffset, end))); + } } diff --git a/src/main/java/ee/carlrobert/codegpt/codecompletions/InfillRequestDetails.java b/src/main/java/ee/carlrobert/codegpt/codecompletions/InfillRequestDetails.java index 34e9cd73..de65554c 100644 --- a/src/main/java/ee/carlrobert/codegpt/codecompletions/InfillRequestDetails.java +++ b/src/main/java/ee/carlrobert/codegpt/codecompletions/InfillRequestDetails.java @@ -1,14 +1,7 @@ package ee.carlrobert.codegpt.codecompletions; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.TextRange; -import ee.carlrobert.codegpt.EncodingManager; - public class InfillRequestDetails { - private static final int MAX_OFFSET = 10_000; - private static final int MAX_PROMPT_TOKENS = 512; - private final String prefix; private final String suffix; @@ -17,12 +10,6 @@ public class InfillRequestDetails { this.suffix = suffix; } - public static InfillRequestDetails fromDocumentWithMaxOffset(Document document, int caretOffset) { - int start = Math.max(0, caretOffset - MAX_OFFSET); - int end = Math.min(document.getTextLength(), caretOffset + MAX_OFFSET); - return fromDocumentWithCustomRange(document, caretOffset, start, end); - } - public String getPrefix() { return prefix; } @@ -30,21 +17,4 @@ public class InfillRequestDetails { public String getSuffix() { return suffix; } - - private static InfillRequestDetails fromDocumentWithCustomRange( - Document document, - int caretOffset, - int start, - int end) { - var prefix = truncateText(document, start, caretOffset, false); - var suffix = truncateText(document, caretOffset, end, true); - return new InfillRequestDetails(prefix, suffix); - } - - private static String truncateText(Document document, int start, int end, boolean fromStart) { - return EncodingManager.getInstance().truncateText( - document.getText(new TextRange(start, end)), - MAX_PROMPT_TOKENS, - fromStart); - } } diff --git a/src/test/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.java b/src/test/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.java index bb7c9799..87c46ac7 100644 --- a/src/test/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.java +++ b/src/test/java/ee/carlrobert/codegpt/codecompletions/CodeCompletionServiceTest.java @@ -10,29 +10,40 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorCustomElementRenderer; +import com.intellij.openapi.editor.Inlay; import com.intellij.openapi.editor.VisualPosition; import com.intellij.openapi.util.TextRange; -import com.intellij.testFramework.PlatformTestUtil; -import ee.carlrobert.codegpt.settings.configuration.ConfigurationState; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import ee.carlrobert.llm.client.http.exchange.StreamHttpExchange; +import ee.carlrobert.llm.completion.CompletionEventListener; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import testsupport.IntegrationTest; public class CodeCompletionServiceTest extends IntegrationTest { - private final VisualPosition cursorPosition = new VisualPosition(3, 0); + private final VisualPosition cursorPosition = new VisualPosition(2, 8); public void testFetchCodeCompletionLlama() { useLlamaService(); - ConfigurationState.getInstance().setCodeCompletionsEnabled(true); - myFixture.configureByText( - "CompletionTest.java", - getResourceContent("/codecompletions/code-completion-file.txt")); + var codeCompletionService = CodeCompletionService.getInstance(getProject()); + String fileContents = getResourceContent( + "/codecompletions/code-completion-file.txt"); + PsiFile psiFile = myFixture.configureByText("CompletionTest.java", fileContents); Editor editor = myFixture.getEditor(); - var expectedCompletion = "TEST_SINGLE_LINE_OUTPUT\nTEST_MULTI_LINE_OUTPUT"; - var prefix = "z".repeat(1015) + "\n[INPUT]\n"; // 512 tokens - var suffix = "\n[\\INPUT]\n" + "z".repeat(1015); // 512 tokens + Document document = editor.getDocument(); + editor.getCaretModel().moveToVisualPosition(cursorPosition); + var prefix = "public static int gcd(int x, int y){\n"; + var suffix = "\n" + + " }"; + var expectedCompletion = "return xyz;"; expectLlama((StreamHttpExchange) request -> { assertThat(request.getUri().getPath()).isEqualTo("/completion"); assertThat(request.getMethod()).isEqualTo("POST"); @@ -42,43 +53,86 @@ public class CodeCompletionServiceTest extends IntegrationTest { return List.of(jsonMapResponse(e("content", expectedCompletion), e("stop", true))); }); - editor.getCaretModel().moveToVisualPosition(cursorPosition); + int caretOffset = editor.getCaretModel().getOffset(); + PsiElement elementAtCaret = ReadAction.compute(() -> psiFile.findElementAt(caretOffset)); - await().pollInSameThread().atMost(5, SECONDS) - .until(() -> { - PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue(); - var singleLineInlayElement = editor.getUserData(SINGLE_LINE_INLAY); - var multiLineInlayElement = editor.getUserData(MULTI_LINE_INLAY); - if (singleLineInlayElement != null && multiLineInlayElement != null) { - var singleLine = - ((InlayInlineElementRenderer) singleLineInlayElement.getRenderer()).getInlayText(); - var multiLine = - ((InlayBlockElementRenderer) multiLineInlayElement.getRenderer()).getInlayText(); - return "TEST_SINGLE_LINE_OUTPUT".equals(singleLine) - && "TEST_MULTI_LINE_OUTPUT".equals(multiLine); + StringBuilder actualCompletion = new StringBuilder(); + codeCompletionService.fetchCodeCompletion(elementAtCaret, caretOffset, document, + new CompletionEventListener() { + @Override + public void onComplete(StringBuilder messageBuilder) { + actualCompletion.append(messageBuilder); } - return false; }); + await().atMost(2, SECONDS) + .until(() -> actualCompletion.length() > 0); + assertEquals(expectedCompletion, actualCompletion.toString()); } - public void testApplyInlayAction() { - ConfigurationState.getInstance().setAutoFormattingEnabled(false); - myFixture.configureByText( - "CompletionTest.java", - getResourceContent("/codecompletions/code-completion-file.txt")); - var editor = myFixture.getEditor(); + public void testAddInlaysSingleLine() { + var codeCompletionService = setupTestCodeCompletion(); + Editor editor = myFixture.getEditor(); editor.getCaretModel().moveToVisualPosition(cursorPosition); - var expectedSingleLineInlay = "FIRST_LINE"; - var expectedMultiLineInlay = "SECOND_LINE\nTHIRD_LINE"; - var expectedInlay = expectedSingleLineInlay + "\n" + expectedMultiLineInlay; - int cursorOffsetBeforeApply = editor.getCaretModel().getOffset(); - CodeCompletionService.getInstance(getProject()) - .addInlays(editor, cursorOffsetBeforeApply, expectedInlay); + var expectedInlay = " return xyz;"; + int caretOffset = editor.getCaretModel().getOffset(); + codeCompletionService.addInlays(editor, caretOffset, expectedInlay); + + checkInlay(editor.getUserData(SINGLE_LINE_INLAY), InlayInlineElementRenderer.class, + expectedInlay, caretOffset); + checkPerformInlayAction(editor.getDocument(), cursorPosition.line, cursorPosition.line, + expectedInlay); + ActionManager.getInstance().unregisterAction(APPLY_INLAY_ACTION_ID); + } + + public void testAddInlaysMultiLine() { + var codeCompletionService = setupTestCodeCompletion(); + Editor editor = myFixture.getEditor(); + editor.getCaretModel().moveToVisualPosition(cursorPosition); + var expectedInlay = " int z = 1;\n z = 2 + 3;\n return xyz;"; + int caretOffset = editor.getCaretModel().getOffset(); + + codeCompletionService.addInlays(editor, caretOffset, expectedInlay); + + // First line of inlay + checkInlay(editor.getUserData(SINGLE_LINE_INLAY), InlayInlineElementRenderer.class, + expectedInlay.substring(0, expectedInlay.indexOf("\n")), caretOffset); + // Other lines of inlay + checkInlay(editor.getUserData(MULTI_LINE_INLAY), InlayBlockElementRenderer.class, + expectedInlay.substring(expectedInlay.indexOf("\n") + 1), caretOffset); + checkPerformInlayAction(editor.getDocument(), cursorPosition.line, cursorPosition.line + 2, + expectedInlay); + ActionManager.getInstance().unregisterAction(APPLY_INLAY_ACTION_ID); + } + + private CodeCompletionService setupTestCodeCompletion() { + useLlamaService(); + var codeCompletionService = CodeCompletionService.getInstance(getProject()); + String fileContents = getResourceContent( + "/codecompletions/code-completion-file.txt"); + myFixture.configureByText("CompletionTest.java", fileContents); + return codeCompletionService; + } + + private void checkInlay(Inlay inlay, + Class clazz, String expectedText, int expectedOffset) { + assertNotNull(inlay); + assertTrue(clazz.isInstance(inlay.getRenderer())); + InlayElementRenderer renderer = (InlayElementRenderer) inlay.getRenderer(); + assertEquals(expectedText, renderer.getInlayText()); + assertEquals(expectedOffset, inlay.getOffset()); + } + + private void checkPerformInlayAction(Document document, int startLine, int endLine, + String expectedText) { + AnAction applyInlayAction = ActionManager.getInstance().getAction(APPLY_INLAY_ACTION_ID); + assertNotNull(applyInlayAction); myFixture.performEditorAction(APPLY_INLAY_ACTION_ID); - var newTextRange = new TextRange(cursorOffsetBeforeApply, editor.getCaretModel().getOffset()); - var appliedInlay = editor.getDocument().getText(newTextRange); - assertThat(appliedInlay).isEqualTo(expectedInlay); + TextRange inlayTextRange = new TextRange(document.getLineStartOffset(startLine), + document.getLineEndOffset(endLine)); + assertEquals(expectedText, document.getText(inlayTextRange)); } + + } diff --git a/src/test/resources/codecompletions/code-completion-file.txt b/src/test/resources/codecompletions/code-completion-file.txt index d9fe637b..801cb3c1 100644 --- a/src/test/resources/codecompletions/code-completion-file.txt +++ b/src/test/resources/codecompletions/code-completion-file.txt @@ -1,7 +1,5 @@ -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz -[INPUT] +public class CompletionTest { + public static int gcd(int x, int y){ -[\INPUT] -zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ No newline at end of file + } +} \ No newline at end of file