Cody JetBrains: enable Java formatting and add CI workflow (#52446)

## Test plan

Green CI with the new cody-jetbrains workflow.
<!-- All pull requests REQUIRE a test plan:
https://docs.sourcegraph.com/dev/background-information/testing_principles
-->
This commit is contained in:
Ólafur Páll Geirsson 2023-05-26 12:47:06 +02:00 committed by GitHub
parent e02a5f4b2d
commit ea9ef9cc64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 2483 additions and 2185 deletions

25
.github/workflows/cody-jetbrains.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Cody JetBrains Tests
on:
pull_request:
paths:
- client/cody-jetbrains/**
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: client/cody-jetbrains
steps:
- uses: actions/checkout@v3
- name: Gradle Wrapper Validation
uses: gradle/wrapper-validation-action@v1.0.4
- name: Setup Java
uses: actions/setup-java@v2
with:
distribution: zulu
java-version: 11
cache: gradle
- run: ./gradlew test
- run: ./gradlew buildPlugin
- run: ./gradlew spotlessCheck

View File

@ -21,6 +21,10 @@ New issues and feature requests can be filed through our [issue tracker](https:/
- Clone `https://github.com/sourcegraph/sourcegraph` (on Windows, you'll need to use WSL2)
- Go to `client/cody-jetbrains/` and run the plugin in a sandboxed IDE by running `./gradlew :runIde`. This will start the platform with the versions defined in `gradle.properties`, [here](https://github.com/sourcegraph/sourcegraph/blob/main/client/cody-jetbrains/gradle.properties#L14-L16).
- Build a deployable plugin artifact by running `./gradlew buildPlugin`. The output file is `build/distributions/Cody.zip`.
- Reformat the codebase with `./gradlew spotlessApply`.
- Install the google-java-format plugin
https://plugins.jetbrains.com/plugin/8527-google-java-format and configure
IntelliJ's file save actions to format.
## Publishing a new version

View File

@ -9,6 +9,7 @@ plugins {
id("org.jetbrains.kotlin.jvm") version "1.7.0"
id("org.jetbrains.intellij") version "1.13.3"
id("org.jetbrains.changelog") version "1.3.1"
id("com.diffplug.spotless") version "6.19.0"
}
group = properties("pluginGroup")
@ -37,6 +38,24 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
}
spotless {
java {
target("src/*/java/**/*.java")
importOrder()
removeUnusedImports()
googleJavaFormat()
}
}
java {
toolchain {
// Always compile the codebase with Java 11 regardless of what Java
// version is installed on the computer. Gradle will download Java 11
// even if you already have it installed on your computer.
languageVersion.set(JavaLanguageVersion.of(11))
}
}
tasks {
// Set the JVM compatibility versions
properties("javaVersion").let {

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,89 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -9,19 +9,20 @@ import com.intellij.openapi.project.DumbAwareAction;
import org.jetbrains.annotations.NotNull;
public class CodyAction extends DumbAwareAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Notification notification = new Notification("Cody errors", "Cody",
"Test", NotificationType.WARNING);
AnAction dismissAction = new DumbAwareAction("Dismiss") {
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
notification.expire();
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Notification notification =
new Notification("Cody errors", "Cody", "Test", NotificationType.WARNING);
AnAction dismissAction =
new DumbAwareAction("Dismiss") {
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
notification.expire();
}
};
notification.setIcon(Icons.CodyLogo);
notification.addAction(dismissAction);
Notifications.Bus.notify(notification);
notification.notify(e.getProject());
}
notification.setIcon(Icons.CodyLogo);
notification.addAction(dismissAction);
Notifications.Bus.notify(notification);
notification.notify(e.getProject());
}
}

View File

@ -14,154 +14,179 @@ import com.sourcegraph.cody.config.ConfigUtil;
import com.sourcegraph.cody.config.SettingsComponent;
import com.sourcegraph.cody.editor.EditorContextGetter;
import com.sourcegraph.cody.recipes.RecipeRunner;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.AdjustmentListener;
import java.util.ArrayList;
import javax.swing.*;
import org.jetbrains.annotations.NotNull;
class CodyToolWindowContent implements UpdatableChat {
private final @NotNull JBTabbedPane tabbedPane = new JBTabbedPane();
private final @NotNull JPanel contentPanel = new JPanel();
private final @NotNull JPanel recipesPanel = new JPanel();
private final @NotNull JPanel messagesPanel = new JPanel();
private final @NotNull JTextField messageField;
private boolean needScrollingDown = true;
private final @NotNull JBTabbedPane tabbedPane = new JBTabbedPane();
public CodyToolWindowContent(@NotNull Project project) {
// Tabs
tabbedPane.insertTab("Chat", null, contentPanel, null, 0);
tabbedPane.insertTab("Recipes", null, recipesPanel, null, 1);
private final @NotNull JPanel contentPanel = new JPanel();
private final @NotNull JPanel recipesPanel = new JPanel();
private final @NotNull JPanel messagesPanel = new JPanel();
private final @NotNull JTextField messageField;
private boolean needScrollingDown = true;
// Recipes panel
RecipeRunner recipeRunner = new RecipeRunner(project, this);
JButton explainCodeDetailedButton = new JButton("Explain selected code (detailed)");
explainCodeDetailedButton.addActionListener(e -> recipeRunner.runExplainCodeDetailed());
JButton explainCodeHighLevelButton = new JButton("Explain selected code (high level)");
explainCodeHighLevelButton.addActionListener(e -> recipeRunner.runExplainCodeHighLevel());
JButton generateUnitTestButton = new JButton("Generate a unit test");
generateUnitTestButton.addActionListener(e -> recipeRunner.runGenerateUnitTest());
JButton generateDocstringButton = new JButton("Generate a docstring");
generateDocstringButton.addActionListener(e -> recipeRunner.runGenerateDocstring());
JButton improveVariableNamesButton = new JButton("Improve variable names");
improveVariableNamesButton.addActionListener(e -> recipeRunner.runImproveVariableNames());
JButton translateToLanguageButton = new JButton("Translate to different language");
translateToLanguageButton.addActionListener(e -> recipeRunner.runTranslateToLanguage());
JButton gitHistoryButton = new JButton("Summarize recent code changes");
gitHistoryButton.addActionListener(e -> recipeRunner.runGitHistory());
JButton findCodeSmellsButton = new JButton("Smell code");
findCodeSmellsButton.addActionListener(e -> recipeRunner.runFindCodeSmells());
JButton fixupButton = new JButton("Fixup code from inline instructions");
fixupButton.addActionListener(e -> recipeRunner.runFixup());
JButton contextSearchButton = new JButton("Codebase context search");
contextSearchButton.addActionListener(e -> recipeRunner.runContextSearch());
JButton releaseNotesButton = new JButton("Generate release notes");
releaseNotesButton.addActionListener(e -> recipeRunner.runReleaseNotes());
recipesPanel.add(explainCodeDetailedButton);
recipesPanel.add(explainCodeHighLevelButton);
recipesPanel.add(generateUnitTestButton);
recipesPanel.add(generateDocstringButton);
recipesPanel.add(improveVariableNamesButton);
recipesPanel.add(translateToLanguageButton);
recipesPanel.add(gitHistoryButton);
recipesPanel.add(findCodeSmellsButton);
recipesPanel.add(fixupButton);
recipesPanel.add(contextSearchButton);
recipesPanel.add(releaseNotesButton);
public CodyToolWindowContent(@NotNull Project project) {
// Tabs
tabbedPane.insertTab("Chat", null, contentPanel, null, 0);
tabbedPane.insertTab("Recipes", null, recipesPanel, null, 1);
// Chat panel
messagesPanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 10, true, true));
JBScrollPane chatPanel = new JBScrollPane(messagesPanel, JBScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JBScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
// Recipes panel
RecipeRunner recipeRunner = new RecipeRunner(project, this);
JButton explainCodeDetailedButton = new JButton("Explain selected code (detailed)");
explainCodeDetailedButton.addActionListener(e -> recipeRunner.runExplainCodeDetailed());
JButton explainCodeHighLevelButton = new JButton("Explain selected code (high level)");
explainCodeHighLevelButton.addActionListener(e -> recipeRunner.runExplainCodeHighLevel());
JButton generateUnitTestButton = new JButton("Generate a unit test");
generateUnitTestButton.addActionListener(e -> recipeRunner.runGenerateUnitTest());
JButton generateDocstringButton = new JButton("Generate a docstring");
generateDocstringButton.addActionListener(e -> recipeRunner.runGenerateDocstring());
JButton improveVariableNamesButton = new JButton("Improve variable names");
improveVariableNamesButton.addActionListener(e -> recipeRunner.runImproveVariableNames());
JButton translateToLanguageButton = new JButton("Translate to different language");
translateToLanguageButton.addActionListener(e -> recipeRunner.runTranslateToLanguage());
JButton gitHistoryButton = new JButton("Summarize recent code changes");
gitHistoryButton.addActionListener(e -> recipeRunner.runGitHistory());
JButton findCodeSmellsButton = new JButton("Smell code");
findCodeSmellsButton.addActionListener(e -> recipeRunner.runFindCodeSmells());
JButton fixupButton = new JButton("Fixup code from inline instructions");
fixupButton.addActionListener(e -> recipeRunner.runFixup());
JButton contextSearchButton = new JButton("Codebase context search");
contextSearchButton.addActionListener(e -> recipeRunner.runContextSearch());
JButton releaseNotesButton = new JButton("Generate release notes");
releaseNotesButton.addActionListener(e -> recipeRunner.runReleaseNotes());
recipesPanel.add(explainCodeDetailedButton);
recipesPanel.add(explainCodeHighLevelButton);
recipesPanel.add(generateUnitTestButton);
recipesPanel.add(generateDocstringButton);
recipesPanel.add(improveVariableNamesButton);
recipesPanel.add(translateToLanguageButton);
recipesPanel.add(gitHistoryButton);
recipesPanel.add(findCodeSmellsButton);
recipesPanel.add(fixupButton);
recipesPanel.add(contextSearchButton);
recipesPanel.add(releaseNotesButton);
// Scroll all the way down after each message
AdjustmentListener scrollAdjustmentListener = e -> {
if (needScrollingDown) {
e.getAdjustable().setValue(e.getAdjustable().getMaximum());
needScrollingDown = false;
}
// Chat panel
messagesPanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 10, true, true));
JBScrollPane chatPanel =
new JBScrollPane(
messagesPanel,
JBScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JBScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
// Scroll all the way down after each message
AdjustmentListener scrollAdjustmentListener =
e -> {
if (needScrollingDown) {
e.getAdjustable().setValue(e.getAdjustable().getMaximum());
needScrollingDown = false;
}
};
chatPanel.getVerticalScrollBar().addAdjustmentListener(scrollAdjustmentListener);
chatPanel.getVerticalScrollBar().addAdjustmentListener(scrollAdjustmentListener);
// Controls panel
JPanel controlsPanel = new JPanel();
controlsPanel.setLayout(new BoxLayout(controlsPanel, BoxLayout.X_AXIS));
messageField = new JTextField();
controlsPanel.add(messageField);
messageField.addActionListener(e -> sendMessage(project)); // TODO: Disable the button while sending, then re-enable it
JButton sendButton = new JButton("Send");
sendButton.addActionListener(e -> sendMessage(project));
controlsPanel.add(sendButton);
// Controls panel
JPanel controlsPanel = new JPanel();
controlsPanel.setLayout(new BoxLayout(controlsPanel, BoxLayout.X_AXIS));
messageField = new JTextField();
controlsPanel.add(messageField);
messageField.addActionListener(
e -> sendMessage(project)); // TODO: Disable the button while sending, then re-enable it
JButton sendButton = new JButton("Send");
sendButton.addActionListener(e -> sendMessage(project));
controlsPanel.add(sendButton);
// Main content panel
contentPanel.setLayout(new BorderLayout(0, 20));
contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
contentPanel.add(chatPanel, BorderLayout.CENTER);
contentPanel.add(controlsPanel, BorderLayout.SOUTH);
// Main content panel
contentPanel.setLayout(new BorderLayout(0, 20));
contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
contentPanel.add(chatPanel, BorderLayout.CENTER);
contentPanel.add(controlsPanel, BorderLayout.SOUTH);
// Add welcome message
var welcomeText = "Hello! I'm Cody. I can write code and answer questions for you. See [Cody documentation](https://docs.sourcegraph.com/cody) for help and tips.";
addMessage(ChatMessage.createAssistantMessage(welcomeText));
}
// Add welcome message
var welcomeText =
"Hello! I'm Cody. I can write code and answer questions for you. See [Cody documentation](https://docs.sourcegraph.com/cody) for help and tips.";
addMessage(ChatMessage.createAssistantMessage(welcomeText));
}
public synchronized void addMessage(@NotNull ChatMessage message) {
ApplicationManager.getApplication().invokeLater(() -> {
boolean isHuman = message.getSpeaker() == Speaker.HUMAN;
public synchronized void addMessage(@NotNull ChatMessage message) {
ApplicationManager.getApplication()
.invokeLater(
() -> {
boolean isHuman = message.getSpeaker() == Speaker.HUMAN;
// Bubble panel
var bubblePanel = new JPanel();
bubblePanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false));
bubblePanel.setBorder(BorderFactory.createEmptyBorder(0, isHuman ? JBUIScale.scale(20) : 0, 0, !isHuman ? JBUIScale.scale(20) : 0));
// Bubble panel
var bubblePanel = new JPanel();
bubblePanel.setLayout(
new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false));
bubblePanel.setBorder(
BorderFactory.createEmptyBorder(
0, isHuman ? JBUIScale.scale(20) : 0, 0, !isHuman ? JBUIScale.scale(20) : 0));
// Chat bubble
ChatBubble bubble = new ChatBubble(10, message);
bubblePanel.add(bubble, VerticalFlowLayout.TOP);
messagesPanel.add(bubblePanel);
messagesPanel.revalidate();
messagesPanel.repaint();
// Chat bubble
ChatBubble bubble = new ChatBubble(10, message);
bubblePanel.add(bubble, VerticalFlowLayout.TOP);
messagesPanel.add(bubblePanel);
messagesPanel.revalidate();
messagesPanel.repaint();
// Need this hacky solution to scroll all the way down after each message
ApplicationManager.getApplication().invokeLater(() -> {
needScrollingDown = true;
messagesPanel.revalidate();
messagesPanel.repaint();
// Need this hacky solution to scroll all the way down after each message
ApplicationManager.getApplication()
.invokeLater(
() -> {
needScrollingDown = true;
messagesPanel.revalidate();
messagesPanel.repaint();
});
});
});
}
}
public synchronized void updateLastMessage(@NotNull ChatMessage message) {
ApplicationManager.getApplication().invokeLater(() -> {
if (messagesPanel.getComponentCount() > 0) {
JPanel lastBubblePanel = (JPanel) messagesPanel.getComponent(messagesPanel.getComponentCount() - 1);
public synchronized void updateLastMessage(@NotNull ChatMessage message) {
ApplicationManager.getApplication()
.invokeLater(
() -> {
if (messagesPanel.getComponentCount() > 0) {
JPanel lastBubblePanel =
(JPanel) messagesPanel.getComponent(messagesPanel.getComponentCount() - 1);
ChatBubble lastBubble = (ChatBubble) lastBubblePanel.getComponent(0);
lastBubble.updateText(message.getDisplayText());
messagesPanel.revalidate();
messagesPanel.repaint();
}
});
}
}
});
}
private void sendMessage(@NotNull Project project) {
// Build message
boolean isEnterprise = ConfigUtil.getInstanceType(project).equals(SettingsComponent.InstanceType.ENTERPRISE);
String instanceUrl = isEnterprise ? ConfigUtil.getEnterpriseUrl(project) : "https://sourcegraph.com/";
String accessToken = isEnterprise ? ConfigUtil.getEnterpriseAccessToken(project) : ConfigUtil.getDotcomAccessToken(project);
private void sendMessage(@NotNull Project project) {
// Build message
boolean isEnterprise =
ConfigUtil.getInstanceType(project).equals(SettingsComponent.InstanceType.ENTERPRISE);
String instanceUrl =
isEnterprise ? ConfigUtil.getEnterpriseUrl(project) : "https://sourcegraph.com/";
String accessToken =
isEnterprise
? ConfigUtil.getEnterpriseAccessToken(project)
: ConfigUtil.getDotcomAccessToken(project);
var chat = new Chat("", instanceUrl, accessToken != null ? accessToken : "");
ArrayList<String> contextFiles = EditorContextGetter.getEditorContext(project).getCurrentFileContentAsArrayList();
ChatMessage humanMessage = ChatMessage.createHumanMessage(messageField.getText(), contextFiles);
addMessage(humanMessage);
var chat = new Chat("", instanceUrl, accessToken != null ? accessToken : "");
ArrayList<String> contextFiles =
EditorContextGetter.getEditorContext(project).getCurrentFileContentAsArrayList();
ChatMessage humanMessage = ChatMessage.createHumanMessage(messageField.getText(), contextFiles);
addMessage(humanMessage);
// Get assistant message
// Note: A separate thread is needed because it's a long-running task. If we did the back-end call
// in the main thread and then waited, we wouldn't see the messages streamed back to us.
new Thread(() -> {
chat.sendMessage(humanMessage, "", this); // TODO: Use prefix
}).start();
}
// Get assistant message
// Note: A separate thread is needed because it's a long-running task. If we did the back-end
// call
// in the main thread and then waited, we wouldn't see the messages streamed back to us.
new Thread(
() -> {
chat.sendMessage(humanMessage, "", this); // TODO: Use prefix
})
.start();
}
public @NotNull JComponent getContentPanel() {
return tabbedPane;
}
public @NotNull JComponent getContentPanel() {
return tabbedPane;
}
}

View File

@ -9,16 +9,18 @@ import com.intellij.ui.content.ContentFactory;
import org.jetbrains.annotations.NotNull;
public class CodyToolWindowFactory implements ToolWindowFactory, DumbAware {
@Override
public boolean isApplicable(@NotNull Project project) {
return ToolWindowFactory.super.isApplicable(project);
}
@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
CodyToolWindowContent toolWindowContent = new CodyToolWindowContent(project);
Content content = ContentFactory.SERVICE.getInstance().createContent(toolWindowContent.getContentPanel(), "", false);
toolWindow.getContentManager().addContent(content);
}
@Override
public boolean isApplicable(@NotNull Project project) {
return ToolWindowFactory.super.isApplicable(project);
}
@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
CodyToolWindowContent toolWindowContent = new CodyToolWindowContent(project);
Content content =
ContentFactory.SERVICE
.getInstance()
.createContent(toolWindowContent.getContentPanel(), "", false);
toolWindow.getContentManager().addContent(content);
}
}

View File

@ -1,9 +1,8 @@
package com.sourcegraph.cody;
import com.intellij.openapi.util.IconLoader;
import javax.swing.*;
public interface Icons {
Icon CodyLogo = IconLoader.getIcon("/icons/codyLogo.svg", Icons.class);
Icon CodyLogo = IconLoader.getIcon("/icons/codyLogo.svg", Icons.class);
}

View File

@ -4,32 +4,28 @@ import org.jetbrains.annotations.NotNull;
public class TruncationUtils {
public static final int MAX_PROMPT_TOKEN_LENGTH = 7000;
public static final int SOLUTION_TOKEN_LENGTH = 1000;
public static final int MAX_HUMAN_INPUT_TOKENS = 1000;
public static final int MAX_RECIPE_INPUT_TOKENS = 2000;
public static final int MAX_CURRENT_FILE_TOKENS = 1000;
public static final int MAX_RECIPE_SURROUNDING_TOKENS = 500;
public static final int MAX_AVAILABLE_PROMPT_LENGTH = MAX_PROMPT_TOKEN_LENGTH - SOLUTION_TOKEN_LENGTH;
public static final int CHARS_PER_TOKEN = 4;
/**
* The number of code lines to include in the preceding and following texts near the selection
*/
public static final int SURROUNDING_LINES = 50;
public static final int MAX_PROMPT_TOKEN_LENGTH = 7000;
public static final int SOLUTION_TOKEN_LENGTH = 1000;
public static final int MAX_HUMAN_INPUT_TOKENS = 1000;
public static final int MAX_RECIPE_INPUT_TOKENS = 2000;
public static final int MAX_CURRENT_FILE_TOKENS = 1000;
public static final int MAX_RECIPE_SURROUNDING_TOKENS = 500;
public static final int MAX_AVAILABLE_PROMPT_LENGTH =
MAX_PROMPT_TOKEN_LENGTH - SOLUTION_TOKEN_LENGTH;
public static final int CHARS_PER_TOKEN = 4;
/**
* Truncates text to the given number of tokens, keeping the start of the text.
*/
public static String truncateText(@NotNull String text, int maxTokens) {
int maxLength = maxTokens * CHARS_PER_TOKEN;
return text.length() <= maxLength ? text : text.substring(0, maxLength);
}
/** The number of code lines to include in the preceding and following texts near the selection */
public static final int SURROUNDING_LINES = 50;
/**
* Truncates text to the given number of tokens, keeping the end of the text.
*/
public static String truncateTextStart(@NotNull String text, int maxTokens) {
int maxLength = maxTokens * CHARS_PER_TOKEN;
return text.length() <= maxLength ? text : text.substring(text.length() - maxLength);
}
/** Truncates text to the given number of tokens, keeping the start of the text. */
public static String truncateText(@NotNull String text, int maxTokens) {
int maxLength = maxTokens * CHARS_PER_TOKEN;
return text.length() <= maxLength ? text : text.substring(0, maxLength);
}
/** Truncates text to the given number of tokens, keeping the end of the text. */
public static String truncateTextStart(@NotNull String text, int maxTokens) {
int maxLength = maxTokens * CHARS_PER_TOKEN;
return text.length() <= maxLength ? text : text.substring(text.length() - maxLength);
}
}

View File

@ -4,7 +4,7 @@ import com.sourcegraph.cody.chat.ChatMessage;
import org.jetbrains.annotations.NotNull;
public interface UpdatableChat {
void addMessage(@NotNull ChatMessage message);
void addMessage(@NotNull ChatMessage message);
void updateLastMessage(@NotNull ChatMessage message);
void updateLastMessage(@NotNull ChatMessage message);
}

View File

@ -3,44 +3,44 @@ package com.sourcegraph.cody.chat;
import com.sourcegraph.cody.UpdatableChat;
import com.sourcegraph.cody.completions.*;
import com.sourcegraph.cody.prompts.Preamble;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class Chat {
private final @Nullable String codebase;
private final @NotNull CompletionsService completionsService;
private final @Nullable String codebase;
private final @NotNull CompletionsService completionsService;
public Chat(@Nullable String codebase, @NotNull String instanceUrl, @NotNull String accessToken) {
this.codebase = codebase;
completionsService = new CompletionsService(instanceUrl, accessToken);
public Chat(@Nullable String codebase, @NotNull String instanceUrl, @NotNull String accessToken) {
this.codebase = codebase;
completionsService = new CompletionsService(instanceUrl, accessToken);
}
public void sendMessage(
@NotNull ChatMessage humanMessage, @Nullable String prefix, @NotNull UpdatableChat chat) {
List<Message> preamble = Preamble.getPreamble(codebase);
// TODO: Use the context getting logic from VS Code
var codeContext = "";
if (humanMessage.getContextFiles().size() == 0) {
codeContext = "I have no file open in the editor right now.";
} else {
codeContext = "Here is my current file\n" + humanMessage.getContextFiles().get(0);
}
public void sendMessage(@NotNull ChatMessage humanMessage, @Nullable String prefix, @NotNull UpdatableChat chat) {
List<Message> preamble = Preamble.getPreamble(codebase);
var input = new CompletionsInput(new ArrayList<>(), 0.5f, 1000, -1, -1);
input.addMessages(preamble);
input.addMessage(Speaker.HUMAN, codeContext);
input.addMessage(Speaker.ASSISTANT, "Ok.");
input.addMessage(Speaker.HUMAN, humanMessage.getText());
input.addMessage(Speaker.ASSISTANT, "");
// TODO: Use the context getting logic from VS Code
var codeContext = "";
if (humanMessage.getContextFiles().size() == 0) {
codeContext = "I have no file open in the editor right now.";
} else {
codeContext = "Here is my current file\n" + humanMessage.getContextFiles().get(0);
}
input.messages.forEach(System.out::println);
var input = new CompletionsInput(new ArrayList<>(), 0.5f, 1000, -1, -1);
input.addMessages(preamble);
input.addMessage(Speaker.HUMAN, codeContext);
input.addMessage(Speaker.ASSISTANT, "Ok.");
input.addMessage(Speaker.HUMAN, humanMessage.getText());
input.addMessage(Speaker.ASSISTANT, "");
// ConfigUtil.getAccessToken(project) TODO: Get the access token from the plugin config
// TODO: Don't create this each time
input.messages.forEach(System.out::println);
// ConfigUtil.getAccessToken(project) TODO: Get the access token from the plugin config
// TODO: Don't create this each time
completionsService.streamCompletion(input, new ChatUpdaterCallbacks(chat, prefix));
}
completionsService.streamCompletion(input, new ChatUpdaterCallbacks(chat, prefix));
}
}

View File

@ -4,60 +4,62 @@ import com.intellij.ui.JBColor;
import com.intellij.util.ui.JBInsets;
import com.intellij.util.ui.UIUtil;
import com.sourcegraph.cody.completions.Speaker;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
public class ChatBubble extends JPanel {
private final int radius;
private final int radius;
public ChatBubble(int radius, @NotNull ChatMessage message) {
super();
public ChatBubble(int radius, @NotNull ChatMessage message) {
super();
boolean isHuman = message.getSpeaker() == Speaker.HUMAN;
JBColor background = isHuman ? JBColor.BLUE : JBColor.GRAY;
this.radius = radius;
this.setBackground(background);
this.setLayout(new BorderLayout());
this.setBorder(new EmptyBorder(new JBInsets(10, 10, 10, 10)));
boolean isHuman = message.getSpeaker() == Speaker.HUMAN;
JBColor background = isHuman ? JBColor.BLUE : JBColor.GRAY;
this.radius = radius;
this.setBackground(background);
this.setLayout(new BorderLayout());
this.setBorder(new EmptyBorder(new JBInsets(10, 10, 10, 10)));
JEditorPane pane = new JEditorPane();
pane.setContentType("text/html");
pane.setText(convertToHtml(message.getDisplayText()));
pane.setEditable(false);
pane.setFont(UIUtil.getLabelFont());
pane.setBackground(background);
pane.setForeground(isHuman ? JBColor.WHITE : JBColor.BLACK);
pane.setComponentOrientation(isHuman ? ComponentOrientation.RIGHT_TO_LEFT : ComponentOrientation.LEFT_TO_RIGHT);
pane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
this.add(pane, BorderLayout.CENTER);
}
JEditorPane pane = new JEditorPane();
pane.setContentType("text/html");
pane.setText(convertToHtml(message.getDisplayText()));
pane.setEditable(false);
pane.setFont(UIUtil.getLabelFont());
pane.setBackground(background);
pane.setForeground(isHuman ? JBColor.WHITE : JBColor.BLACK);
pane.setComponentOrientation(
isHuman ? ComponentOrientation.RIGHT_TO_LEFT : ComponentOrientation.LEFT_TO_RIGHT);
pane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
this.add(pane, BorderLayout.CENTER);
}
public void updateText(@NotNull String newMarkdownText) {
JEditorPane pane = (JEditorPane) this.getComponent(0);
pane.setText(convertToHtml(newMarkdownText));
}
public void updateText(@NotNull String newMarkdownText) {
JEditorPane pane = (JEditorPane) this.getComponent(0);
pane.setText(convertToHtml(newMarkdownText));
}
private @NotNull String convertToHtml(@NotNull String markdown) {
// Parse markdown
Parser parser = Parser.builder().build();
Node node = parser.parse(markdown);
HtmlRenderer renderer = HtmlRenderer.builder().build();
String messageAsHtml = renderer.render(node);
private @NotNull String convertToHtml(@NotNull String markdown) {
// Parse markdown
Parser parser = Parser.builder().build();
Node node = parser.parse(markdown);
HtmlRenderer renderer = HtmlRenderer.builder().build();
String messageAsHtml = renderer.render(node);
// Build HTML
return "<html data-gramm=\"false\"><head><style>p { margin:0; }</style></head><body>" + messageAsHtml + "</body></html>";
}
// Build HTML
return "<html data-gramm=\"false\"><head><style>p { margin:0; }</style></head><body>"
+ messageAsHtml
+ "</body></html>";
}
@Override
protected void paintComponent(@NotNull Graphics g) {
final Graphics2D g2d = (Graphics2D) g;
g2d.setColor(getBackground());
g2d.fillRoundRect(0, 0, this.getWidth() - 1, this.getHeight() - 1, this.radius, this.radius);
}
@Override
protected void paintComponent(@NotNull Graphics g) {
final Graphics2D g2d = (Graphics2D) g;
g2d.setColor(getBackground());
g2d.fillRoundRect(0, 0, this.getWidth() - 1, this.getHeight() - 1, this.radius, this.radius);
}
}

View File

@ -2,35 +2,39 @@ package com.sourcegraph.cody.chat;
import com.sourcegraph.cody.completions.Message;
import com.sourcegraph.cody.completions.Speaker;
import java.util.ArrayList;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public class ChatMessage extends Message {
private final @NotNull String displayText;
private final @NotNull ArrayList<String> contextFiles;
private final @NotNull String displayText;
private final @NotNull ArrayList<String> contextFiles;
private ChatMessage(@NotNull Speaker speaker, @NotNull String text, @NotNull String displayText, @NotNull ArrayList<String> contextFiles) {
super(speaker, text);
this.displayText = displayText;
this.contextFiles = contextFiles;
}
private ChatMessage(
@NotNull Speaker speaker,
@NotNull String text,
@NotNull String displayText,
@NotNull ArrayList<String> contextFiles) {
super(speaker, text);
this.displayText = displayText;
this.contextFiles = contextFiles;
}
public static @NotNull ChatMessage createAssistantMessage(@NotNull String text) {
return new ChatMessage(Speaker.ASSISTANT, text, text, new ArrayList<>());
}
public static @NotNull ChatMessage createAssistantMessage(@NotNull String text) {
return new ChatMessage(Speaker.ASSISTANT, text, text, new ArrayList<>());
}
public static @NotNull ChatMessage createHumanMessage(@NotNull String text, @NotNull ArrayList<String> contextFiles) {
return new ChatMessage(Speaker.HUMAN, text, text, contextFiles);
}
public static @NotNull ChatMessage createHumanMessage(
@NotNull String text, @NotNull ArrayList<String> contextFiles) {
return new ChatMessage(Speaker.HUMAN, text, text, contextFiles);
}
@NotNull
public String getDisplayText() {
return displayText;
}
@NotNull
public String getDisplayText() {
return displayText;
}
@NotNull
public ArrayList<String> getContextFiles() {
return contextFiles;
}
@NotNull
public ArrayList<String> getContextFiles() {
return contextFiles;
}
}

View File

@ -2,71 +2,75 @@ package com.sourcegraph.cody.completions;
import com.sourcegraph.cody.UpdatableChat;
import com.sourcegraph.cody.chat.ChatMessage;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ChatUpdaterCallbacks implements CompletionsCallbacks {
private final UpdatableChat chat;
private final String prefix;
private boolean gotFirstMessage = false;
private final UpdatableChat chat;
private final String prefix;
private boolean gotFirstMessage = false;
public ChatUpdaterCallbacks(@NotNull UpdatableChat chat, @Nullable String prefix) {
this.chat = chat;
this.prefix = prefix;
public ChatUpdaterCallbacks(@NotNull UpdatableChat chat, @Nullable String prefix) {
this.chat = chat;
this.prefix = prefix;
}
@Override
public void onSubscribed() {
System.out.println("Subscribed to completions.");
}
@Override
public void onData(@Nullable String data) {
if (data == null) {
return;
}
// print date/time and msg
// System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd
// HH:mm:ss.SSS").format(LocalDateTime.now()) + " Data received by callback: " + data);
if (!gotFirstMessage) {
chat.addMessage(ChatMessage.createAssistantMessage(reformatBotMessage(data, prefix)));
gotFirstMessage = true;
} else {
chat.updateLastMessage(ChatMessage.createAssistantMessage(reformatBotMessage(data, prefix)));
}
}
@Override
public void onError(@NotNull Throwable error) {
if (error.getMessage().equals("Connection refused")) {
chat.addMessage(
ChatMessage.createAssistantMessage(
"I'm sorry, I can't connect to the server. Please make sure that the server is running and try again."));
} else {
chat.addMessage(
ChatMessage.createAssistantMessage(
"I'm sorry, something wet wrong. Please try again. The error message I got was: \""
+ error.getMessage()
+ "\"."));
}
System.err.println("Error: " + error);
}
@Override
public void onComplete() {
System.out.println("Streaming completed.");
}
private static @NotNull String reformatBotMessage(@NotNull String text, @Nullable String prefix) {
String STOP_SEQUENCE_REGEXP = "(H|Hu|Hum|Huma|Human|Human:)$";
Pattern stopSequencePattern = Pattern.compile(STOP_SEQUENCE_REGEXP);
String reformattedMessage = (prefix != null ? prefix : "") + text.stripTrailing();
Matcher stopSequenceMatcher = stopSequencePattern.matcher(reformattedMessage);
if (stopSequenceMatcher.find()) {
reformattedMessage = reformattedMessage.substring(0, stopSequenceMatcher.start());
}
@Override
public void onSubscribed() {
System.out.println("Subscribed to completions.");
}
@Override
public void onData(@Nullable String data) {
if(data == null) {
return;
}
// print date/time and msg
// System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(LocalDateTime.now()) + " Data received by callback: " + data);
if (!gotFirstMessage) {
chat.addMessage(ChatMessage.createAssistantMessage(reformatBotMessage(data, prefix)));
gotFirstMessage = true;
} else {
chat.updateLastMessage(ChatMessage.createAssistantMessage(reformatBotMessage(data, prefix)));
}
}
@Override
public void onError(@NotNull Throwable error) {
if (error.getMessage().equals("Connection refused")) {
chat.addMessage(ChatMessage.createAssistantMessage("I'm sorry, I can't connect to the server. Please make sure that the server is running and try again."));
} else {
chat.addMessage(ChatMessage.createAssistantMessage("I'm sorry, something wet wrong. Please try again. The error message I got was: \"" + error.getMessage() + "\"."));
}
System.err.println("Error: " + error);
}
@Override
public void onComplete() {
System.out.println("Streaming completed.");
}
private static @NotNull String reformatBotMessage(@NotNull String text, @Nullable String prefix) {
String STOP_SEQUENCE_REGEXP = "(H|Hu|Hum|Huma|Human|Human:)$";
Pattern stopSequencePattern = Pattern.compile(STOP_SEQUENCE_REGEXP);
String reformattedMessage = (prefix != null ? prefix : "") + text.stripTrailing();
Matcher stopSequenceMatcher = stopSequencePattern.matcher(reformattedMessage);
if (stopSequenceMatcher.find()) {
reformattedMessage = reformattedMessage.substring(0, stopSequenceMatcher.start());
}
return reformattedMessage;
}
return reformattedMessage;
}
}

View File

@ -2,11 +2,11 @@ package com.sourcegraph.cody.completions;
// Define a callback interface to handle events
public interface CompletionsCallbacks {
void onSubscribed();
void onSubscribed();
void onData(String data);
void onData(String data);
void onError(Throwable error);
void onError(Throwable error);
void onComplete();
void onComplete();
}

View File

@ -1,32 +1,34 @@
package com.sourcegraph.cody.completions;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Input for the completions request.
*/
/** Input for the completions request. */
public class CompletionsInput {
public @NotNull List<Message> messages;
public float temperature;
public int maxTokensToSample;
public int topK;
public int topP;
public @NotNull List<Message> messages;
public float temperature;
public int maxTokensToSample;
public int topK;
public int topP;
public CompletionsInput(@NotNull List<Message> messages, float temperature, int maxTokensToSample, int topK, int topP) {
this.messages = messages;
this.temperature = temperature;
this.maxTokensToSample = maxTokensToSample;
this.topK = topK;
this.topP = topP;
}
public CompletionsInput(
@NotNull List<Message> messages,
float temperature,
int maxTokensToSample,
int topK,
int topP) {
this.messages = messages;
this.temperature = temperature;
this.maxTokensToSample = maxTokensToSample;
this.topK = topK;
this.topP = topP;
}
public void addMessage(@NotNull Speaker speaker, @NotNull String text) {
messages.add(new Message(speaker, text));
}
public void addMessage(@NotNull Speaker speaker, @NotNull String text) {
messages.add(new Message(speaker, text));
}
public void addMessages(@NotNull List<Message> messages) {
this.messages.addAll(messages);
}
public void addMessages(@NotNull List<Message> messages) {
this.messages.addAll(messages);
}
}

View File

@ -4,49 +4,52 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.sourcegraph.api.GraphQlClient;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
public class CompletionsService {
private final String instanceUrl;
private final String accessToken;
private final String instanceUrl;
private final String accessToken;
public CompletionsService(@NotNull String instanceUrl, @NotNull String accessToken) {
this.instanceUrl = instanceUrl;
this.accessToken = accessToken;
}
public CompletionsService(@NotNull String instanceUrl, @NotNull String accessToken) {
this.instanceUrl = instanceUrl;
this.accessToken = accessToken;
}
/**
* Sends a completions request to the Sourcegraph instance, and returns the response.
*/
public String getCompletion(@NotNull CompletionsInput input) throws IOException, InterruptedException {
Gson gson = new GsonBuilder()
/** Sends a completions request to the Sourcegraph instance, and returns the response. */
public String getCompletion(@NotNull CompletionsInput input)
throws IOException, InterruptedException {
Gson gson =
new GsonBuilder()
.registerTypeAdapter(Speaker.class, new SpeakerLowercaseSerializer())
.create();
String query = "query completions($input: CompletionsInput!) { completions(input: $input) }";
var variables = new JsonObject();
variables.add("input", gson.toJsonTree(input));
String query = "query completions($input: CompletionsInput!) { completions(input: $input) }";
var variables = new JsonObject();
variables.add("input", gson.toJsonTree(input));
var response = GraphQlClient.callGraphQLService(instanceUrl, accessToken, null, query, variables);
return response
.getBodyAsJson()
.getAsJsonObject("data")
.getAsJsonPrimitive("completions")
.getAsString();
}
var response =
GraphQlClient.callGraphQLService(instanceUrl, accessToken, null, query, variables);
return response
.getBodyAsJson()
.getAsJsonObject("data")
.getAsJsonPrimitive("completions")
.getAsString();
}
/**
* Sends a completions request to the Sourcegraph instance, and receives the response in a streaming fashion.
*/
public void streamCompletion(@NotNull CompletionsInput input, @NotNull CompletionsCallbacks cb) {
Gson gson = new GsonBuilder()
/**
* Sends a completions request to the Sourcegraph instance, and receives the response in a
* streaming fashion.
*/
public void streamCompletion(@NotNull CompletionsInput input, @NotNull CompletionsCallbacks cb) {
Gson gson =
new GsonBuilder()
.registerTypeAdapter(Speaker.class, new SpeakerLowercaseSerializer())
.create();
String body = gson.toJsonTree(input).getAsJsonObject().toString();
SSEClient sseClient = new SSEClient(instanceUrl + ".api/completions/stream", accessToken, body, cb);
sseClient.start();
}
String body = gson.toJsonTree(input).getAsJsonObject().toString();
SSEClient sseClient =
new SSEClient(instanceUrl + ".api/completions/stream", accessToken, body, cb);
sseClient.start();
}
}

View File

@ -1,32 +1,27 @@
package com.sourcegraph.cody.completions;
import com.google.gson.Gson;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
/**
* Wrapper for GraphQL requests that has a query and a list of variables.
*/
/** Wrapper for GraphQL requests that has a query and a list of variables. */
class GraphQLWrapper {
@NotNull
public String query;
@NotNull
public Map<String, Object> variables = new HashMap<>();
@NotNull public String query;
@NotNull public Map<String, Object> variables = new HashMap<>();
public GraphQLWrapper(@NotNull String query) {
this.query = query;
}
public GraphQLWrapper(@NotNull String query) {
this.query = query;
}
public GraphQLWrapper withVariable(@NotNull String key, @NotNull Object variable) {
this.variables.put(key, variable);
return this;
}
public GraphQLWrapper withVariable(@NotNull String key, @NotNull Object variable) {
this.variables.put(key, variable);
return this;
}
@NotNull
public String toJsonString() {
Gson gson = new Gson();
return gson.toJson(this);
}
@NotNull
public String toJsonString() {
Gson gson = new Gson();
return gson.toJson(this);
}
}

View File

@ -3,24 +3,24 @@ package com.sourcegraph.cody.completions;
import org.jetbrains.annotations.NotNull;
public class Message {
protected final @NotNull Speaker speaker;
protected final @NotNull String text;
protected final @NotNull Speaker speaker;
protected final @NotNull String text;
public Message(@NotNull Speaker speaker, @NotNull String text) {
this.speaker = speaker;
this.text = text;
}
public Message(@NotNull Speaker speaker, @NotNull String text) {
this.speaker = speaker;
this.text = text;
}
public @NotNull Speaker getSpeaker() {
return speaker;
}
public @NotNull Speaker getSpeaker() {
return speaker;
}
public @NotNull String getText() {
return text;
}
public @NotNull String getText() {
return text;
}
@Override
public @NotNull String toString() {
return String.format("Message { speaker=%s, text='%s'}", speaker, text);
}
@Override
public @NotNull String toString() {
return String.format("Message { speaker=%s, text='%s'}", speaker, text);
}
}

View File

@ -3,10 +3,6 @@ package com.sourcegraph.cody.completions;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.net.URI;
import java.net.http.HttpClient;
@ -16,114 +12,124 @@ import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Objects;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.jetbrains.annotations.NotNull;
public class SSEClient {
private final String url;
private final String accessToken;
private final String body;
private final CompletionsCallbacks cb;
private final String url;
private final String accessToken;
private final String body;
private final CompletionsCallbacks cb;
private InputStream inputStream;
private InputStream inputStream;
public SSEClient(@NotNull String url, @NotNull String accessToken, @NotNull String body, @NotNull CompletionsCallbacks cb) {
this.url = url;
this.body = body;
this.accessToken = accessToken;
this.cb = cb;
}
public SSEClient(
@NotNull String url,
@NotNull String accessToken,
@NotNull String body,
@NotNull CompletionsCallbacks cb) {
this.url = url;
this.body = body;
this.accessToken = accessToken;
this.cb = cb;
}
public void start() {
try {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(new URI(url))
.version(HttpClient.Version.HTTP_2)
.header("Content-Type", "application/json; charset=utf-8")
.header("X-Sourcegraph-Should-Trace", "false")
.header("Accept", "text/event-stream")
.header("Cache-Control", "no-cache")
.header("Authorization", "token " + accessToken)
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(body));
HttpRequest request = requestBuilder.build();
public void start() {
try {
HttpRequest.Builder requestBuilder =
HttpRequest.newBuilder()
.uri(new URI(url))
.version(HttpClient.Version.HTTP_2)
.header("Content-Type", "application/json; charset=utf-8")
.header("X-Sourcegraph-Should-Trace", "false")
.header("Accept", "text/event-stream")
.header("Cache-Control", "no-cache")
.header("Authorization", "token " + accessToken)
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(body));
HttpRequest request = requestBuilder.build();
HttpResponse<InputStream> response = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build()
.send(request, BodyHandlers.ofInputStream());
HttpResponse<InputStream> response =
HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build()
.send(request, BodyHandlers.ofInputStream());
if (response.statusCode() == HttpStatus.SC_OK) {
cb.onSubscribed();
inputStream = response.body();
handleResponse(inputStream);
} else {
String result;
try (InputStream ignored = response.body()) {
result = IOUtils.toString(response.body(), StandardCharsets.UTF_8);
}
cb.onError(new Error("Got error response " + response.statusCode() + ": " + result));
}
} catch (Exception e) {
if (e.getCause() instanceof InterruptedException) {
cb.onError(e); // TODO: Handle interruptions
} else {
cb.onError(e);
}
if (response.statusCode() == HttpStatus.SC_OK) {
cb.onSubscribed();
inputStream = response.body();
handleResponse(inputStream);
} else {
String result;
try (InputStream ignored = response.body()) {
result = IOUtils.toString(response.body(), StandardCharsets.UTF_8);
}
cb.onError(new Error("Got error response " + response.statusCode() + ": " + result));
}
} catch (Exception e) {
if (e.getCause() instanceof InterruptedException) {
cb.onError(e); // TODO: Handle interruptions
} else {
cb.onError(e);
}
}
}
public void stopCurrentRequest() {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
System.err.println("Got error stopCurrentRequest: " + e.getMessage());
}
}
public void stopCurrentRequest() {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
System.err.println("Got error stopCurrentRequest: " + e.getMessage());
}
}
}
private void handleResponse(@NotNull InputStream inputStream) {
/*
* Streams go like this:
* event: completion
* data: Hello
*
* event: completion
* data: Hello, there
*
* event: done
* data:
*/
String eventName = null;
try (BufferedInputStream in = IOUtils.buffer(inputStream)) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
String line;
StringBuilder messageBuilder = new StringBuilder();
while ((line = reader.readLine()) != null) {
if (line.startsWith("data:")) {
messageBuilder.append(line.substring(5));
} else if (line.startsWith("event:")) {
eventName = line.substring(6).trim();
}
if (line.trim().isEmpty() && messageBuilder.length() > 0) {
String message = messageBuilder.toString();
if (Objects.equals(eventName, "completion")) { // Completion
JsonObject json = new Gson().fromJson(message, JsonObject.class);
JsonPrimitive completion = json.getAsJsonPrimitive("completion");
cb.onData(completion != null ? completion.getAsString().trim() : null);
} else if (Objects.equals(eventName, "done")) { // Done
stopCurrentRequest();
cb.onComplete();
return;
}
messageBuilder = new StringBuilder();
}
}
if (messageBuilder.length() > 0) {
System.out.println("Non-processed data: {}" + messageBuilder);
}
private void handleResponse(@NotNull InputStream inputStream) {
/*
* Streams go like this:
* event: completion
* data: Hello
*
* event: completion
* data: Hello, there
*
* event: done
* data:
*/
String eventName = null;
try (BufferedInputStream in = IOUtils.buffer(inputStream)) {
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
String line;
StringBuilder messageBuilder = new StringBuilder();
while ((line = reader.readLine()) != null) {
if (line.startsWith("data:")) {
messageBuilder.append(line.substring(5));
} else if (line.startsWith("event:")) {
eventName = line.substring(6).trim();
}
if (line.trim().isEmpty() && messageBuilder.length() > 0) {
String message = messageBuilder.toString();
if (Objects.equals(eventName, "completion")) { // Completion
JsonObject json = new Gson().fromJson(message, JsonObject.class);
JsonPrimitive completion = json.getAsJsonPrimitive("completion");
cb.onData(completion != null ? completion.getAsString().trim() : null);
} else if (Objects.equals(eventName, "done")) { // Done
stopCurrentRequest();
cb.onComplete();
return;
}
} catch (Exception e) {
cb.onError(e);
messageBuilder = new StringBuilder();
}
}
if (messageBuilder.length() > 0) {
System.out.println("Non-processed data: {}" + messageBuilder);
}
}
} catch (Exception e) {
cb.onError(e);
}
}
}

View File

@ -1,6 +1,6 @@
package com.sourcegraph.cody.completions;
public enum Speaker {
HUMAN,
ASSISTANT
HUMAN,
ASSISTANT
}

View File

@ -4,12 +4,11 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
public class SpeakerLowercaseSerializer implements JsonSerializer<Speaker> {
@Override
public JsonElement serialize(Speaker src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.name().toLowerCase());
}
@Override
public JsonElement serialize(Speaker src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.name().toLowerCase());
}
}

View File

@ -6,42 +6,52 @@ import com.google.gson.JsonSyntaxException;
import com.intellij.openapi.diagnostic.Logger;
import com.sourcegraph.api.GraphQlClient;
import com.sourcegraph.api.GraphQlResponse;
import java.io.IOException;
import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.function.Consumer;
public class ApiAuthenticator {
private final static Logger logger = Logger.getInstance(ApiAuthenticator.class);
private static final Logger logger = Logger.getInstance(ApiAuthenticator.class);
public static void testConnection(@NotNull String instanceUrl, @Nullable String accessToken, @Nullable String customRequestHeaders, @NotNull Consumer<ConnectionStatus> callback) {
new Thread(() -> {
String query = "query {" +
" currentUser {" +
" id" +
" }" +
"}";
public static void testConnection(
@NotNull String instanceUrl,
@Nullable String accessToken,
@Nullable String customRequestHeaders,
@NotNull Consumer<ConnectionStatus> callback) {
new Thread(
() -> {
String query = "query { currentUser { id } }";
try {
GraphQlResponse response = GraphQlClient.callGraphQLService(instanceUrl, accessToken, customRequestHeaders, query, new JsonObject());
try {
GraphQlResponse response =
GraphQlClient.callGraphQLService(
instanceUrl, accessToken, customRequestHeaders, query, new JsonObject());
if (response.getStatusCode() == 200) {
JsonElement id = response.getBodyAsJson().getAsJsonObject("data").getAsJsonObject("currentUser").get("id");
callback.accept(id.isJsonNull() ? ConnectionStatus.COULD_CONNECT_BUT_NOT_AUTHENTICATED : ConnectionStatus.AUTHENTICATED);
JsonElement id =
response
.getBodyAsJson()
.getAsJsonObject("data")
.getAsJsonObject("currentUser")
.get("id");
callback.accept(
id.isJsonNull()
? ConnectionStatus.COULD_CONNECT_BUT_NOT_AUTHENTICATED
: ConnectionStatus.AUTHENTICATED);
} else {
callback.accept(ConnectionStatus.COULD_NOT_CONNECT);
callback.accept(ConnectionStatus.COULD_NOT_CONNECT);
}
} catch (IOException | JsonSyntaxException e) {
} catch (IOException | JsonSyntaxException e) {
callback.accept(ConnectionStatus.COULD_NOT_CONNECT);
logger.info(e);
}
}).start();
}
})
.start();
}
}
enum ConnectionStatus {
AUTHENTICATED,
COULD_NOT_CONNECT,
COULD_CONNECT_BUT_NOT_AUTHENTICATED
}
enum ConnectionStatus {
AUTHENTICATED,
COULD_NOT_CONNECT,
COULD_CONNECT_BUT_NOT_AUTHENTICATED
}
}

View File

@ -1,54 +1,50 @@
package com.sourcegraph.cody.config;
import com.intellij.openapi.options.Configurable;
import javax.swing.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* Provides controller functionality for application settings.
*/
/** Provides controller functionality for application settings. */
public class ApplicationSettingsConfigurable implements Configurable {
private SettingsComponent mySettingsComponent;
private SettingsComponent mySettingsComponent;
@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return "Cody";
}
@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return "Cody";
}
@Override
public JComponent getPreferredFocusedComponent() {
return mySettingsComponent.getPreferredFocusedComponent();
}
@Override
public JComponent getPreferredFocusedComponent() {
return mySettingsComponent.getPreferredFocusedComponent();
}
@Nullable
@Override
public JComponent createComponent() {
mySettingsComponent = new SettingsComponent();
return mySettingsComponent.getPanel();
}
@Nullable
@Override
public JComponent createComponent() {
mySettingsComponent = new SettingsComponent();
return mySettingsComponent.getPanel();
}
@Override
public boolean isModified() {
return SettingsConfigurableHelper.isModified(null, mySettingsComponent);
}
@Override
public boolean isModified() {
return SettingsConfigurableHelper.isModified(null, mySettingsComponent);
}
@Override
public void apply() {
SettingsConfigurableHelper.apply(null, mySettingsComponent);
}
@Override
public void apply() {
SettingsConfigurableHelper.apply(null, mySettingsComponent);
}
@Override
public void reset() {
SettingsConfigurableHelper.reset(null, mySettingsComponent);
}
@Override
public void disposeUIResources() {
mySettingsComponent.dispose();
mySettingsComponent = null;
}
@Override
public void reset() {
SettingsConfigurableHelper.reset(null, mySettingsComponent);
}
@Override
public void disposeUIResources() {
mySettingsComponent.dispose();
mySettingsComponent = null;
}
}

View File

@ -10,134 +10,127 @@ import org.jetbrains.annotations.Nullable;
@State(
name = "ApplicationConfig",
storages = {@Storage("cody.xml")})
public class CodyApplicationService implements CodyService, PersistentStateComponent<CodyApplicationService> {
@Nullable
private String instanceType;
@Nullable
private String dotcomAccessToken;
@Nullable
private String enterpriseUrl;
@Nullable
private String enterpriseAccessToken;
@Nullable
private String customRequestHeaders;
@Nullable
private String codebase;
@Nullable
private String anonymousUserId;
private boolean isInstallEventLogged;
private boolean areChatPredictionsEnabled;
@Nullable
private Boolean authenticationFailedLastTime;
public class CodyApplicationService
implements CodyService, PersistentStateComponent<CodyApplicationService> {
@Nullable private String instanceType;
@Nullable private String dotcomAccessToken;
@Nullable private String enterpriseUrl;
@Nullable private String enterpriseAccessToken;
@Nullable private String customRequestHeaders;
@Nullable private String codebase;
@Nullable private String anonymousUserId;
private boolean isInstallEventLogged;
private boolean areChatPredictionsEnabled;
@Nullable private Boolean authenticationFailedLastTime;
@NotNull
public static CodyApplicationService getInstance() {
return ApplicationManager.getApplication().getService(CodyApplicationService.class);
}
@NotNull
public static CodyApplicationService getInstance() {
return ApplicationManager.getApplication().getService(CodyApplicationService.class);
}
@Nullable
public String getInstanceType() {
return instanceType;
}
@Nullable
public String getInstanceType() {
return instanceType;
}
public void setInstanceType(@Nullable String instanceType) {
this.instanceType = instanceType;
}
public void setInstanceType(@Nullable String instanceType) {
this.instanceType = instanceType;
}
@Nullable
public String getDotcomAccessToken() {
return dotcomAccessToken;
}
@Nullable
public String getDotcomAccessToken() {
return dotcomAccessToken;
}
public void setDotcomAccessToken(@Nullable String dotcomAccessToken) {
this.dotcomAccessToken = dotcomAccessToken;
}
public void setDotcomAccessToken(@Nullable String dotcomAccessToken) {
this.dotcomAccessToken = dotcomAccessToken;
}
@Nullable
public String getEnterpriseUrl() {
return enterpriseUrl;
}
@Nullable
public String getEnterpriseUrl() {
return enterpriseUrl;
}
public void setEnterpriseUrl(@Nullable String enterpriseUrl) {
this.enterpriseUrl = enterpriseUrl;
}
public void setEnterpriseUrl(@Nullable String enterpriseUrl) {
this.enterpriseUrl = enterpriseUrl;
}
@Nullable
public String getEnterpriseAccessToken() {
return enterpriseAccessToken;
}
@Nullable
public String getEnterpriseAccessToken() {
return enterpriseAccessToken;
}
public void setEnterpriseAccessToken(@Nullable String enterpriseAccessToken) {
this.enterpriseAccessToken = enterpriseAccessToken;
}
public void setEnterpriseAccessToken(@Nullable String enterpriseAccessToken) {
this.enterpriseAccessToken = enterpriseAccessToken;
}
@Nullable
public String getCustomRequestHeaders() {
return customRequestHeaders;
}
@Nullable
public String getCustomRequestHeaders() {
return customRequestHeaders;
}
public void setCustomRequestHeaders(@Nullable String customRequestHeaders) {
this.customRequestHeaders = customRequestHeaders;
}
public void setCustomRequestHeaders(@Nullable String customRequestHeaders) {
this.customRequestHeaders = customRequestHeaders;
}
@Nullable
public String getCodebase() {
return codebase;
}
@Nullable
public String getCodebase() {
return codebase;
}
public void setCodebase(@Nullable String codebase) {
this.codebase = codebase;
}
public void setCodebase(@Nullable String codebase) {
this.codebase = codebase;
}
@Nullable
public String getAnonymousUserId() {
return anonymousUserId;
}
@Nullable
public String getAnonymousUserId() {
return anonymousUserId;
}
public void setAnonymousUserId(@Nullable String anonymousUserId) {
this.anonymousUserId = anonymousUserId;
}
public void setAnonymousUserId(@Nullable String anonymousUserId) {
this.anonymousUserId = anonymousUserId;
}
public boolean isInstallEventLogged() {
return isInstallEventLogged;
}
public boolean isInstallEventLogged() {
return isInstallEventLogged;
}
public void setInstallEventLogged(boolean installEventLogged) {
isInstallEventLogged = installEventLogged;
}
public void setInstallEventLogged(boolean installEventLogged) {
isInstallEventLogged = installEventLogged;
}
public Boolean areChatPredictionsEnabled() {
return areChatPredictionsEnabled;
}
public Boolean areChatPredictionsEnabled() {
return areChatPredictionsEnabled;
}
public void setChatPredictionsEnabled(Boolean areChatPredictionsEnabled) {
this.areChatPredictionsEnabled = areChatPredictionsEnabled;
}
public void setChatPredictionsEnabled(Boolean areChatPredictionsEnabled) {
this.areChatPredictionsEnabled = areChatPredictionsEnabled;
}
@Nullable
public Boolean getAuthenticationFailedLastTime() {
return authenticationFailedLastTime;
}
@Nullable
public Boolean getAuthenticationFailedLastTime() {
return authenticationFailedLastTime;
}
public void setAuthenticationFailedLastTime(@Nullable Boolean authenticationFailedLastTime) {
this.authenticationFailedLastTime = authenticationFailedLastTime;
}
public void setAuthenticationFailedLastTime(@Nullable Boolean authenticationFailedLastTime) {
this.authenticationFailedLastTime = authenticationFailedLastTime;
}
@Nullable
@Override
public CodyApplicationService getState() {
return this;
}
@Nullable
@Override
public CodyApplicationService getState() {
return this;
}
@Override
public void loadState(@NotNull CodyApplicationService settings) {
this.instanceType = settings.instanceType;
this.enterpriseUrl = settings.enterpriseUrl;
this.enterpriseAccessToken = settings.enterpriseAccessToken;
this.customRequestHeaders = settings.customRequestHeaders;
this.codebase = settings.codebase;
this.anonymousUserId = settings.anonymousUserId;
this.areChatPredictionsEnabled = settings.areChatPredictionsEnabled;
this.authenticationFailedLastTime = settings.authenticationFailedLastTime;
}
@Override
public void loadState(@NotNull CodyApplicationService settings) {
this.instanceType = settings.instanceType;
this.enterpriseUrl = settings.enterpriseUrl;
this.enterpriseAccessToken = settings.enterpriseAccessToken;
this.customRequestHeaders = settings.customRequestHeaders;
this.codebase = settings.codebase;
this.anonymousUserId = settings.anonymousUserId;
this.areChatPredictionsEnabled = settings.areChatPredictionsEnabled;
this.authenticationFailedLastTime = settings.authenticationFailedLastTime;
}
}

View File

@ -10,107 +10,101 @@ import org.jetbrains.annotations.Nullable;
@State(
name = "Config",
storages = {@Storage("cody.xml")})
public class CodyProjectService implements CodyService, PersistentStateComponent<CodyProjectService> {
@Nullable
private String instanceType;
@Nullable
private String dotcomAccessToken;
@Nullable
private String enterpriseUrl;
@Nullable
private String enterpriseAccessToken;
@Nullable
private String customRequestHeaders;
@Nullable
private String defaultBranch;
@Nullable
private String codebase;
@Nullable
private Boolean areChatPredictionsEnabled;
public class CodyProjectService
implements CodyService, PersistentStateComponent<CodyProjectService> {
@Nullable private String instanceType;
@Nullable private String dotcomAccessToken;
@Nullable private String enterpriseUrl;
@Nullable private String enterpriseAccessToken;
@Nullable private String customRequestHeaders;
@Nullable private String defaultBranch;
@Nullable private String codebase;
@Nullable private Boolean areChatPredictionsEnabled;
@NotNull
public static CodyProjectService getInstance(@NotNull Project project) {
return project.getService(CodyProjectService.class);
}
@NotNull
public static CodyProjectService getInstance(@NotNull Project project) {
public void setInstanceType(@Nullable String instanceType) {
this.instanceType = instanceType;
}
return project.getService(CodyProjectService.class);
}
@Nullable
public String getInstanceType() {
return instanceType;
}
public void setInstanceType(@Nullable String instanceType) {
this.instanceType = instanceType;
}
@Nullable
public String getDotcomAccessToken() {
return dotcomAccessToken;
}
@Nullable
public String getInstanceType() {
return instanceType;
}
public void setDotcomAccessToken(@Nullable String dotcomAccessToken) {
this.dotcomAccessToken = dotcomAccessToken;
}
@Nullable
public String getDotcomAccessToken() {
return dotcomAccessToken;
}
@Nullable
public String getEnterpriseUrl() {
return enterpriseUrl;
}
public void setDotcomAccessToken(@Nullable String dotcomAccessToken) {
this.dotcomAccessToken = dotcomAccessToken;
}
public void setEnterpriseUrl(@Nullable String enterpriseUrl) {
this.enterpriseUrl = enterpriseUrl;
}
@Nullable
public String getEnterpriseUrl() {
return enterpriseUrl;
}
@Nullable
public String getEnterpriseAccessToken() {
return enterpriseAccessToken;
}
public void setEnterpriseUrl(@Nullable String enterpriseUrl) {
this.enterpriseUrl = enterpriseUrl;
}
public void setEnterpriseAccessToken(@Nullable String enterpriseAccessToken) {
this.enterpriseAccessToken = enterpriseAccessToken;
}
@Nullable
public String getEnterpriseAccessToken() {
return enterpriseAccessToken;
}
@Nullable
public String getCustomRequestHeaders() {
return customRequestHeaders;
}
public void setEnterpriseAccessToken(@Nullable String enterpriseAccessToken) {
this.enterpriseAccessToken = enterpriseAccessToken;
}
public void setCustomRequestHeaders(@Nullable String customRequestHeaders) {
this.customRequestHeaders = customRequestHeaders;
}
@Nullable
public String getCustomRequestHeaders() {
return customRequestHeaders;
}
@Nullable
public String getCodebase() {
return codebase;
}
public void setCustomRequestHeaders(@Nullable String customRequestHeaders) {
this.customRequestHeaders = customRequestHeaders;
}
public void setCodebase(@Nullable String codebase) {
this.codebase = codebase;
}
@Nullable
public String getCodebase() {
return codebase;
}
@Nullable
@Override
public CodyProjectService getState() {
return this;
}
public void setCodebase(@Nullable String codebase) {
this.codebase = codebase;
}
@Nullable
public Boolean areChatPredictionsEnabled() {
return areChatPredictionsEnabled;
}
@Nullable
@Override
public CodyProjectService getState() {
return this;
}
public void setChatPredictionsEnabled(@Nullable Boolean areChatPredictionsEnabled) {
this.areChatPredictionsEnabled = areChatPredictionsEnabled;
}
@Nullable
public Boolean areChatPredictionsEnabled() {
return areChatPredictionsEnabled;
}
@Override
public void loadState(@NotNull CodyProjectService settings) {
this.instanceType = settings.instanceType;
this.dotcomAccessToken = settings.dotcomAccessToken;
this.enterpriseUrl = settings.enterpriseUrl;
this.enterpriseAccessToken = settings.enterpriseAccessToken;
this.customRequestHeaders = settings.customRequestHeaders;
this.defaultBranch = settings.defaultBranch;
this.codebase = settings.codebase;
this.areChatPredictionsEnabled = settings.areChatPredictionsEnabled;
}
public void setChatPredictionsEnabled(@Nullable Boolean areChatPredictionsEnabled) {
this.areChatPredictionsEnabled = areChatPredictionsEnabled;
}
@Override
public void loadState(@NotNull CodyProjectService settings) {
this.instanceType = settings.instanceType;
this.dotcomAccessToken = settings.dotcomAccessToken;
this.enterpriseUrl = settings.enterpriseUrl;
this.enterpriseAccessToken = settings.enterpriseAccessToken;
this.customRequestHeaders = settings.customRequestHeaders;
this.defaultBranch = settings.defaultBranch;
this.codebase = settings.codebase;
this.areChatPredictionsEnabled = settings.areChatPredictionsEnabled;
}
}

View File

@ -3,37 +3,38 @@ package com.sourcegraph.cody.config;
import org.jetbrains.annotations.Nullable;
public interface CodyService {
@Nullable
String getInstanceType();
@Nullable
String getInstanceType();
void setInstanceType(@Nullable String instanceType);
void setInstanceType(@Nullable String instanceType);
@Nullable
String getDotcomAccessToken();
@Nullable
String getDotcomAccessToken();
void setDotcomAccessToken(@Nullable String dotcomAccessToken);
void setDotcomAccessToken(@Nullable String dotcomAccessToken);
@Nullable
String getEnterpriseUrl();
@Nullable
String getEnterpriseUrl();
void setEnterpriseUrl(@Nullable String enterpriseUrl);
void setEnterpriseUrl(@Nullable String enterpriseUrl);
@Nullable
String getEnterpriseAccessToken();
@Nullable
String getEnterpriseAccessToken();
void setEnterpriseAccessToken(@Nullable String enterpriseAccessToken);
void setEnterpriseAccessToken(@Nullable String enterpriseAccessToken);
@Nullable
String getCustomRequestHeaders();
@Nullable
String getCustomRequestHeaders();
void setCustomRequestHeaders(@Nullable String customRequestHeaders);
void setCustomRequestHeaders(@Nullable String customRequestHeaders);
@Nullable
String getCodebase();
@Nullable
String getCodebase();
void setCodebase(@Nullable String codebase);
void setCodebase(@Nullable String codebase);
@Nullable Boolean areChatPredictionsEnabled();
@Nullable
Boolean areChatPredictionsEnabled();
void setChatPredictionsEnabled(@Nullable Boolean areChatPredictionsEnabled);
void setChatPredictionsEnabled(@Nullable Boolean areChatPredictionsEnabled);
}

View File

@ -1,170 +1,183 @@
package com.sourcegraph.cody.config;
import com.intellij.openapi.project.Project;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class ConfigUtil {
public static final String DOTCOM_URL = "https://sourcegraph.com/";
public static final String DOTCOM_URL = "https://sourcegraph.com/";
@NotNull
public static SettingsComponent.InstanceType getInstanceType(@Nullable Project project) {
// Project level
if (project != null) {
String projectLevelSetting = getProjectLevelConfig(project).getInstanceType();
if (projectLevelSetting != null && !projectLevelSetting.isEmpty()) {
return projectLevelSetting.equals(SettingsComponent.InstanceType.ENTERPRISE.name())
? SettingsComponent.InstanceType.ENTERPRISE : SettingsComponent.InstanceType.DOTCOM;
}
}
// Application level
String applicationLevelSetting = getApplicationLevelConfig().getInstanceType();
if (applicationLevelSetting != null && !applicationLevelSetting.isEmpty()) {
return applicationLevelSetting.equals(SettingsComponent.InstanceType.ENTERPRISE.name())
? SettingsComponent.InstanceType.ENTERPRISE : SettingsComponent.InstanceType.DOTCOM;
}
// Use default
String enterpriseUrl = getEnterpriseUrl(project);
return (enterpriseUrl.equals("") || enterpriseUrl.startsWith(DOTCOM_URL))
? SettingsComponent.InstanceType.DOTCOM : SettingsComponent.InstanceType.ENTERPRISE;
@NotNull
public static SettingsComponent.InstanceType getInstanceType(@Nullable Project project) {
// Project level
if (project != null) {
String projectLevelSetting = getProjectLevelConfig(project).getInstanceType();
if (projectLevelSetting != null && !projectLevelSetting.isEmpty()) {
return projectLevelSetting.equals(SettingsComponent.InstanceType.ENTERPRISE.name())
? SettingsComponent.InstanceType.ENTERPRISE
: SettingsComponent.InstanceType.DOTCOM;
}
}
@NotNull
public static String getSourcegraphUrl(@Nullable Project project) {
if (getInstanceType(project) == SettingsComponent.InstanceType.DOTCOM) {
return DOTCOM_URL;
} else {
String enterpriseUrl = getEnterpriseUrl(project);
return !enterpriseUrl.isEmpty() ? enterpriseUrl : DOTCOM_URL;
}
// Application level
String applicationLevelSetting = getApplicationLevelConfig().getInstanceType();
if (applicationLevelSetting != null && !applicationLevelSetting.isEmpty()) {
return applicationLevelSetting.equals(SettingsComponent.InstanceType.ENTERPRISE.name())
? SettingsComponent.InstanceType.ENTERPRISE
: SettingsComponent.InstanceType.DOTCOM;
}
@NotNull
public static String getEnterpriseUrl(@Nullable Project project) {
// Project level
if (project != null) {
String projectLevelUrl = getProjectLevelConfig(project).getEnterpriseUrl();
if (projectLevelUrl != null && projectLevelUrl.length() > 0) {
return addSlashIfNeeded(projectLevelUrl);
}
}
// Use default
String enterpriseUrl = getEnterpriseUrl(project);
return (enterpriseUrl.equals("") || enterpriseUrl.startsWith(DOTCOM_URL))
? SettingsComponent.InstanceType.DOTCOM
: SettingsComponent.InstanceType.ENTERPRISE;
}
// Application level
String applicationLevelUrl = getApplicationLevelConfig().getEnterpriseUrl();
if (applicationLevelUrl != null && applicationLevelUrl.length() > 0) {
return addSlashIfNeeded(applicationLevelUrl);
}
@NotNull
public static String getSourcegraphUrl(@Nullable Project project) {
if (getInstanceType(project) == SettingsComponent.InstanceType.DOTCOM) {
return DOTCOM_URL;
} else {
String enterpriseUrl = getEnterpriseUrl(project);
return !enterpriseUrl.isEmpty() ? enterpriseUrl : DOTCOM_URL;
}
}
// Use default
return "";
@NotNull
public static String getEnterpriseUrl(@Nullable Project project) {
// Project level
if (project != null) {
String projectLevelUrl = getProjectLevelConfig(project).getEnterpriseUrl();
if (projectLevelUrl != null && projectLevelUrl.length() > 0) {
return addSlashIfNeeded(projectLevelUrl);
}
}
@Nullable
public static String getDotcomAccessToken(@Nullable Project project) {
// Project level application level
String accessToken = project != null ? getProjectLevelConfig(project).getDotcomAccessToken() : null;
return accessToken != null ? accessToken : getApplicationLevelConfig().getDotcomAccessToken();
// Application level
String applicationLevelUrl = getApplicationLevelConfig().getEnterpriseUrl();
if (applicationLevelUrl != null && applicationLevelUrl.length() > 0) {
return addSlashIfNeeded(applicationLevelUrl);
}
@Nullable
public static String getEnterpriseAccessToken(@Nullable Project project) {
// Project level application level
String accessToken = project != null ? getProjectLevelConfig(project).getEnterpriseAccessToken() : null;
return accessToken != null ? accessToken : getApplicationLevelConfig().getEnterpriseAccessToken();
// Use default
return "";
}
@Nullable
public static String getDotcomAccessToken(@Nullable Project project) {
// Project level application level
String accessToken =
project != null ? getProjectLevelConfig(project).getDotcomAccessToken() : null;
return accessToken != null ? accessToken : getApplicationLevelConfig().getDotcomAccessToken();
}
@Nullable
public static String getEnterpriseAccessToken(@Nullable Project project) {
// Project level application level
String accessToken =
project != null ? getProjectLevelConfig(project).getEnterpriseAccessToken() : null;
return accessToken != null
? accessToken
: getApplicationLevelConfig().getEnterpriseAccessToken();
}
@NotNull
public static String getCustomRequestHeaders(@Nullable Project project) {
// Project level
if (project != null) {
String projectLevelCustomRequestHeaders =
getProjectLevelConfig(project).getCustomRequestHeaders();
if (projectLevelCustomRequestHeaders != null
&& projectLevelCustomRequestHeaders.length() > 0) {
return projectLevelCustomRequestHeaders;
}
}
@NotNull
public static String getCustomRequestHeaders(@Nullable Project project) {
// Project level
if (project != null) {
String projectLevelCustomRequestHeaders = getProjectLevelConfig(project).getCustomRequestHeaders();
if (projectLevelCustomRequestHeaders != null && projectLevelCustomRequestHeaders.length() > 0) {
return projectLevelCustomRequestHeaders;
}
}
// Application level
String applicationLevelCustomRequestHeaders = getApplicationLevelConfig().getCustomRequestHeaders();
if (applicationLevelCustomRequestHeaders != null && applicationLevelCustomRequestHeaders.length() > 0) {
return applicationLevelCustomRequestHeaders;
}
// Default
return "";
// Application level
String applicationLevelCustomRequestHeaders =
getApplicationLevelConfig().getCustomRequestHeaders();
if (applicationLevelCustomRequestHeaders != null
&& applicationLevelCustomRequestHeaders.length() > 0) {
return applicationLevelCustomRequestHeaders;
}
@NotNull
public static String getCodebase(@Nullable Project project) {
// Project level
if (project != null) {
String projectLevelCodebase = getProjectLevelConfig(project).getCodebase();
if (projectLevelCodebase != null && projectLevelCodebase.length() > 0) {
return projectLevelCodebase;
}
}
// Default
return "";
}
// Application level
String applicationLevelCodebase = getApplicationLevelConfig().getCodebase();
if (applicationLevelCodebase != null && applicationLevelCodebase.length() > 0) {
return applicationLevelCodebase;
}
// Use default
return "";
@NotNull
public static String getCodebase(@Nullable Project project) {
// Project level
if (project != null) {
String projectLevelCodebase = getProjectLevelConfig(project).getCodebase();
if (projectLevelCodebase != null && projectLevelCodebase.length() > 0) {
return projectLevelCodebase;
}
}
@Nullable
public static String getAnonymousUserId() {
return getApplicationLevelConfig().getAnonymousUserId();
// Application level
String applicationLevelCodebase = getApplicationLevelConfig().getCodebase();
if (applicationLevelCodebase != null && applicationLevelCodebase.length() > 0) {
return applicationLevelCodebase;
}
public static void setAnonymousUserId(@Nullable String anonymousUserId) {
getApplicationLevelConfig().setAnonymousUserId(anonymousUserId);
}
// Use default
return "";
}
public static boolean isInstallEventLogged() {
return getApplicationLevelConfig().isInstallEventLogged();
}
@Nullable
public static String getAnonymousUserId() {
return getApplicationLevelConfig().getAnonymousUserId();
}
public static void setInstallEventLogged(boolean value) {
getApplicationLevelConfig().setInstallEventLogged(value);
}
public static void setAnonymousUserId(@Nullable String anonymousUserId) {
getApplicationLevelConfig().setAnonymousUserId(anonymousUserId);
}
public static boolean areChatPredictionsEnabled(@Nullable Project project) {
// Project level application level
Boolean areChatPredictionsEnabled = project != null ? getProjectLevelConfig(project).areChatPredictionsEnabled() : null;
return areChatPredictionsEnabled != null ? areChatPredictionsEnabled : Boolean.TRUE.equals(getApplicationLevelConfig().areChatPredictionsEnabled());
}
public static boolean isInstallEventLogged() {
return getApplicationLevelConfig().isInstallEventLogged();
}
public static void setAreChatPredictionsEnabled(boolean value) {
getApplicationLevelConfig().setChatPredictionsEnabled(value);
}
public static void setInstallEventLogged(boolean value) {
getApplicationLevelConfig().setInstallEventLogged(value);
}
@NotNull
private static String addSlashIfNeeded(@NotNull String url) {
return url.endsWith("/") ? url : url + "/";
}
public static boolean areChatPredictionsEnabled(@Nullable Project project) {
// Project level application level
Boolean areChatPredictionsEnabled =
project != null ? getProjectLevelConfig(project).areChatPredictionsEnabled() : null;
return areChatPredictionsEnabled != null
? areChatPredictionsEnabled
: Boolean.TRUE.equals(getApplicationLevelConfig().areChatPredictionsEnabled());
}
public static boolean didAuthenticationFailLastTime() {
Boolean failedLastTime = getApplicationLevelConfig().getAuthenticationFailedLastTime();
return failedLastTime != null ? failedLastTime : true;
}
public static void setAreChatPredictionsEnabled(boolean value) {
getApplicationLevelConfig().setChatPredictionsEnabled(value);
}
public static void setAuthenticationFailedLastTime(boolean value) {
CodyApplicationService.getInstance().setAuthenticationFailedLastTime(value);
}
@NotNull
private static String addSlashIfNeeded(@NotNull String url) {
return url.endsWith("/") ? url : url + "/";
}
@NotNull
private static CodyApplicationService getApplicationLevelConfig() {
return Objects.requireNonNull(CodyApplicationService.getInstance());
}
public static boolean didAuthenticationFailLastTime() {
Boolean failedLastTime = getApplicationLevelConfig().getAuthenticationFailedLastTime();
return failedLastTime != null ? failedLastTime : true;
}
@NotNull
private static CodyProjectService getProjectLevelConfig(@NotNull Project project) {
return Objects.requireNonNull(CodyProjectService.getInstance(project));
}
public static void setAuthenticationFailedLastTime(boolean value) {
CodyApplicationService.getInstance().setAuthenticationFailedLastTime(value);
}
@NotNull
private static CodyApplicationService getApplicationLevelConfig() {
return Objects.requireNonNull(CodyApplicationService.getInstance());
}
@NotNull
private static CodyProjectService getProjectLevelConfig(@NotNull Project project) {
return Objects.requireNonNull(CodyProjectService.getInstance(project));
}
}

View File

@ -12,36 +12,42 @@ import com.sourcegraph.cody.Icons;
import org.jetbrains.annotations.NotNull;
public class NotificationActivity implements StartupActivity.DumbAware {
@Override
public void runActivity(@NotNull Project project) {
String url = ConfigUtil.getEnterpriseUrl(project);
if (url.length() == 0 || url.startsWith(ConfigUtil.DOTCOM_URL)) {
notifyAboutSourcegraphUrl();
}
@Override
public void runActivity(@NotNull Project project) {
String url = ConfigUtil.getEnterpriseUrl(project);
if (url.length() == 0 || url.startsWith(ConfigUtil.DOTCOM_URL)) {
notifyAboutSourcegraphUrl();
}
}
private void notifyAboutSourcegraphUrl() {
// Display notification
Notification notification = new Notification("Sourcegraph access", "Cody",
"A custom Sourcegraph URL is not set. Cody can only access public repos. Do you want to set your custom URL?", NotificationType.INFORMATION);
AnAction setUrlAction = new OpenPluginSettingsAction("Set URL");
AnAction cancelAction = new DumbAwareAction("Do Not Set") {
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
notification.expire();
}
private void notifyAboutSourcegraphUrl() {
// Display notification
Notification notification =
new Notification(
"Sourcegraph access",
"Cody",
"A custom Sourcegraph URL is not set. Cody can only access public repos. Do you want to set your custom URL?",
NotificationType.INFORMATION);
AnAction setUrlAction = new OpenPluginSettingsAction("Set URL");
AnAction cancelAction =
new DumbAwareAction("Do Not Set") {
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
notification.expire();
}
};
AnAction neverShowAgainAction = new DumbAwareAction("Never Show Again") {
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
notification.expire();
ConfigUtil.setAreChatPredictionsEnabled(true);
}
AnAction neverShowAgainAction =
new DumbAwareAction("Never Show Again") {
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
notification.expire();
ConfigUtil.setAreChatPredictionsEnabled(true);
}
};
notification.setIcon(Icons.CodyLogo);
notification.addAction(setUrlAction);
notification.addAction(cancelAction);
notification.addAction(neverShowAgainAction);
Notifications.Bus.notify(notification);
}
notification.setIcon(Icons.CodyLogo);
notification.addAction(setUrlAction);
notification.addAction(cancelAction);
notification.addAction(neverShowAgainAction);
Notifications.Bus.notify(notification);
}
}

View File

@ -8,16 +8,17 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class OpenPluginSettingsAction extends DumbAwareAction {
public OpenPluginSettingsAction() {
super();
}
public OpenPluginSettingsAction() {
super();
}
public OpenPluginSettingsAction(@Nullable @NlsActions.ActionText String text) {
super(text);
}
public OpenPluginSettingsAction(@Nullable @NlsActions.ActionText String text) {
super(text);
}
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
ShowSettingsUtil.getInstance().showSettingsDialog(event.getProject(), ProjectSettingsConfigurable.class);
}
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
ShowSettingsUtil.getInstance()
.showSettingsDialog(event.getProject(), ProjectSettingsConfigurable.class);
}
}

View File

@ -5,10 +5,10 @@ import org.jetbrains.annotations.NotNull;
public interface PluginSettingChangeActionNotifier {
Topic<PluginSettingChangeActionNotifier> TOPIC = Topic.create("Cody plugin settings have changed", PluginSettingChangeActionNotifier.class);
Topic<PluginSettingChangeActionNotifier> TOPIC =
Topic.create("Cody plugin settings have changed", PluginSettingChangeActionNotifier.class);
void beforeAction(@NotNull PluginSettingChangeContext context);
void beforeAction(@NotNull PluginSettingChangeContext context);
void afterAction(@NotNull PluginSettingChangeContext context);
void afterAction(@NotNull PluginSettingChangeContext context);
}

View File

@ -3,40 +3,34 @@ package com.sourcegraph.cody.config;
import org.jetbrains.annotations.Nullable;
public class PluginSettingChangeContext {
@Nullable
public final String oldDotcomAccessToken;
@Nullable public final String oldDotcomAccessToken;
@Nullable
public final String oldEnterpriseUrl;
@Nullable public final String oldEnterpriseUrl;
@Nullable
public final String oldEnterpriseAccessToken;
@Nullable public final String oldEnterpriseAccessToken;
@Nullable
public final String newDotcomAccessToken;
@Nullable public final String newDotcomAccessToken;
@Nullable
public final String newEnterpriseUrl;
@Nullable public final String newEnterpriseUrl;
@Nullable
public final String newEnterpriseAccessToken;
@Nullable public final String newEnterpriseAccessToken;
@Nullable
public final String newCustomRequestHeaders;
@Nullable public final String newCustomRequestHeaders;
public PluginSettingChangeContext(@Nullable String oldDotcomAccessToken,
@Nullable String oldEnterpriseUrl,
@Nullable String oldEnterpriseAccessToken,
@Nullable String newDotcomAccessToken,
@Nullable String newEnterpriseUrl,
@Nullable String newEnterpriseAccessToken,
@Nullable String newCustomRequestHeaders) {
this.oldDotcomAccessToken = oldDotcomAccessToken;
this.oldEnterpriseUrl = oldEnterpriseUrl;
this.oldEnterpriseAccessToken = oldEnterpriseAccessToken;
this.newDotcomAccessToken = newDotcomAccessToken;
this.newEnterpriseUrl = newEnterpriseUrl;
this.newEnterpriseAccessToken = newEnterpriseAccessToken;
this.newCustomRequestHeaders = newCustomRequestHeaders;
}
public PluginSettingChangeContext(
@Nullable String oldDotcomAccessToken,
@Nullable String oldEnterpriseUrl,
@Nullable String oldEnterpriseAccessToken,
@Nullable String newDotcomAccessToken,
@Nullable String newEnterpriseUrl,
@Nullable String newEnterpriseAccessToken,
@Nullable String newCustomRequestHeaders) {
this.oldDotcomAccessToken = oldDotcomAccessToken;
this.oldEnterpriseUrl = oldEnterpriseUrl;
this.oldEnterpriseAccessToken = oldEnterpriseAccessToken;
this.newDotcomAccessToken = newDotcomAccessToken;
this.newEnterpriseUrl = newEnterpriseUrl;
this.newEnterpriseAccessToken = newEnterpriseAccessToken;
this.newCustomRequestHeaders = newCustomRequestHeaders;
}
}

View File

@ -2,59 +2,55 @@ package com.sourcegraph.cody.config;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.project.Project;
import javax.swing.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* Provides controller functionality for application settings.
*/
/** Provides controller functionality for application settings. */
public class ProjectSettingsConfigurable implements Configurable {
private final Project project;
private SettingsComponent mySettingsComponent;
private final Project project;
private SettingsComponent mySettingsComponent;
public ProjectSettingsConfigurable(Project project) {
this.project = project;
}
public ProjectSettingsConfigurable(Project project) {
this.project = project;
}
@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return "Cody (Project)";
}
@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return "Cody (Project)";
}
@Override
public JComponent getPreferredFocusedComponent() {
return mySettingsComponent.getPreferredFocusedComponent();
}
@Override
public JComponent getPreferredFocusedComponent() {
return mySettingsComponent.getPreferredFocusedComponent();
}
@Nullable
@Override
public JComponent createComponent() {
mySettingsComponent = new SettingsComponent();
return mySettingsComponent.getPanel();
}
@Nullable
@Override
public JComponent createComponent() {
mySettingsComponent = new SettingsComponent();
return mySettingsComponent.getPanel();
}
@Override
public boolean isModified() {
return SettingsConfigurableHelper.isModified(project, mySettingsComponent);
}
@Override
public boolean isModified() {
return SettingsConfigurableHelper.isModified(project, mySettingsComponent);
}
@Override
public void apply() {
SettingsConfigurableHelper.apply(project, mySettingsComponent);
}
@Override
public void apply() {
SettingsConfigurableHelper.apply(project, mySettingsComponent);
}
@Override
public void reset() {
SettingsConfigurableHelper.reset(project, mySettingsComponent);
}
@Override
public void disposeUIResources() {
mySettingsComponent.dispose();
mySettingsComponent = null;
}
@Override
public void reset() {
SettingsConfigurableHelper.reset(project, mySettingsComponent);
}
@Override
public void disposeUIResources() {
mySettingsComponent.dispose();
mySettingsComponent = null;
}
}

View File

@ -11,68 +11,81 @@ import com.intellij.openapi.project.Project;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusConnection;
import com.sourcegraph.cody.telemetry.GraphQlLogger;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
/**
* Listens to changes in the plugin settings and:
* - logs install/uninstall events.
* - notifies the user about a successful connection.
* Listens to changes in the plugin settings and: - logs install/uninstall events. - notifies the
* user about a successful connection.
*/
public class SettingsChangeListener implements Disposable {
private final MessageBusConnection connection;
private final MessageBusConnection connection;
public SettingsChangeListener(@NotNull Project project) {
MessageBus bus = project.getMessageBus();
public SettingsChangeListener(@NotNull Project project) {
MessageBus bus = project.getMessageBus();
connection = bus.connect();
connection.subscribe(PluginSettingChangeActionNotifier.TOPIC, new PluginSettingChangeActionNotifier() {
@Override
public void beforeAction(@NotNull PluginSettingChangeContext context) {
if (!Objects.equals(context.oldEnterpriseUrl, context.newEnterpriseUrl)) {
GraphQlLogger.logUninstallEvent(project);
ConfigUtil.setInstallEventLogged(false);
}
connection = bus.connect();
connection.subscribe(
PluginSettingChangeActionNotifier.TOPIC,
new PluginSettingChangeActionNotifier() {
@Override
public void beforeAction(@NotNull PluginSettingChangeContext context) {
if (!Objects.equals(context.oldEnterpriseUrl, context.newEnterpriseUrl)) {
GraphQlLogger.logUninstallEvent(project);
ConfigUtil.setInstallEventLogged(false);
}
}
@Override
public void afterAction(@NotNull PluginSettingChangeContext context) {
// Log install events
if (!Objects.equals(context.oldEnterpriseUrl, context.newEnterpriseUrl)) {
GraphQlLogger.logInstallEvent(project, ConfigUtil::setInstallEventLogged);
} else if (!Objects.equals(
context.oldEnterpriseAccessToken, context.newEnterpriseAccessToken)
&& !ConfigUtil.isInstallEventLogged()) {
GraphQlLogger.logInstallEvent(project, ConfigUtil::setInstallEventLogged);
}
@Override
public void afterAction(@NotNull PluginSettingChangeContext context) {
// Log install events
if (!Objects.equals(context.oldEnterpriseUrl, context.newEnterpriseUrl)) {
GraphQlLogger.logInstallEvent(project, ConfigUtil::setInstallEventLogged);
} else if (!Objects.equals(context.oldEnterpriseAccessToken, context.newEnterpriseAccessToken) && !ConfigUtil.isInstallEventLogged()) {
GraphQlLogger.logInstallEvent(project, ConfigUtil::setInstallEventLogged);
}
// Notify user about a successful connection
if (context.newEnterpriseUrl != null) {
ApiAuthenticator.testConnection(context.newEnterpriseUrl, context.newEnterpriseAccessToken, context.newCustomRequestHeaders, (status) -> {
if (ConfigUtil.didAuthenticationFailLastTime() && status == ApiAuthenticator.ConnectionStatus.AUTHENTICATED) {
notifyAboutSuccessfulConnection();
}
ConfigUtil.setAuthenticationFailedLastTime(status != ApiAuthenticator.ConnectionStatus.AUTHENTICATED);
});
}
// Notify user about a successful connection
if (context.newEnterpriseUrl != null) {
ApiAuthenticator.testConnection(
context.newEnterpriseUrl,
context.newEnterpriseAccessToken,
context.newCustomRequestHeaders,
(status) -> {
if (ConfigUtil.didAuthenticationFailLastTime()
&& status == ApiAuthenticator.ConnectionStatus.AUTHENTICATED) {
notifyAboutSuccessfulConnection();
}
ConfigUtil.setAuthenticationFailedLastTime(
status != ApiAuthenticator.ConnectionStatus.AUTHENTICATED);
});
}
}
});
}
}
private void notifyAboutSuccessfulConnection() {
Notification notification = new Notification("Cody Sourcegraph access", "Cody authentication success",
"Cody successfully connected to your Sourcegraph account.", NotificationType.INFORMATION);
AnAction dismissAction = new DumbAwareAction("Dismiss") {
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
notification.expire();
}
private void notifyAboutSuccessfulConnection() {
Notification notification =
new Notification(
"Cody Sourcegraph access",
"Cody authentication success",
"Cody successfully connected to your Sourcegraph account.",
NotificationType.INFORMATION);
AnAction dismissAction =
new DumbAwareAction("Dismiss") {
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
notification.expire();
}
};
notification.addAction(dismissAction);
Notifications.Bus.notify(notification);
}
notification.addAction(dismissAction);
Notifications.Bus.notify(notification);
}
@Override
public void dispose() {
connection.disconnect();
}
@Override
public void dispose() {
connection.disconnect();
}
}

View File

@ -11,181 +11,218 @@ import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.jsonSchema.settings.mappings.JsonSchemaConfigurable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Enumeration;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Supports creating and managing a {@link JPanel} for the Settings Dialog.
*/
/** Supports creating and managing a {@link JPanel} for the Settings Dialog. */
public class SettingsComponent implements Disposable {
private final JPanel panel;
private ButtonGroup instanceTypeButtonGroup;
private JBLabel dotcomAccessTokenLinkComment;
private JBTextField dotcomAccessTokenTextField;
private JBTextField enterpriseUrlTextField;
private JBTextField enterpriseAccessTokenTextField;
private JBLabel userDocsLinkComment;
private JBLabel enterpriseAccessTokenLinkComment;
private JBTextField customRequestHeadersTextField;
private JBTextField codebaseTextField;
private JBCheckBox areChatPredictionsEnabledCheckBox;
private final JPanel panel;
private ButtonGroup instanceTypeButtonGroup;
private JBLabel dotcomAccessTokenLinkComment;
private JBTextField dotcomAccessTokenTextField;
private JBTextField enterpriseUrlTextField;
private JBTextField enterpriseAccessTokenTextField;
private JBLabel userDocsLinkComment;
private JBLabel enterpriseAccessTokenLinkComment;
private JBTextField customRequestHeadersTextField;
private JBTextField codebaseTextField;
private JBCheckBox areChatPredictionsEnabledCheckBox;
public JComponent getPreferredFocusedComponent() {
return codebaseTextField;
}
public JComponent getPreferredFocusedComponent() {
return codebaseTextField;
}
public SettingsComponent() {
JPanel userAuthenticationPanel = createAuthenticationPanel();
JPanel navigationSettingsPanel = createOtherSettingsPanel();
public SettingsComponent() {
JPanel userAuthenticationPanel = createAuthenticationPanel();
JPanel navigationSettingsPanel = createOtherSettingsPanel();
panel = FormBuilder.createFormBuilder()
panel =
FormBuilder.createFormBuilder()
.addComponent(userAuthenticationPanel)
.addComponent(navigationSettingsPanel)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
public JPanel getPanel() {
return panel;
}
@NotNull
public InstanceType getInstanceType() {
return instanceTypeButtonGroup
.getSelection()
.getActionCommand()
.equals(InstanceType.DOTCOM.name())
? InstanceType.DOTCOM
: InstanceType.ENTERPRISE;
}
public void setInstanceType(@NotNull InstanceType instanceType) {
for (Enumeration<AbstractButton> buttons = instanceTypeButtonGroup.getElements();
buttons.hasMoreElements(); ) {
AbstractButton button = buttons.nextElement();
button.setSelected(button.getActionCommand().equals(instanceType.name()));
}
public JPanel getPanel() {
return panel;
}
setDotcomSettingsEnabled(instanceType == InstanceType.DOTCOM);
setEnterpriseSettingsEnabled(instanceType == InstanceType.ENTERPRISE);
}
@NotNull
public InstanceType getInstanceType() {
return instanceTypeButtonGroup.getSelection().getActionCommand().equals(InstanceType.DOTCOM.name()) ? InstanceType.DOTCOM : InstanceType.ENTERPRISE;
}
@NotNull
private JPanel createAuthenticationPanel() {
public void setInstanceType(@NotNull InstanceType instanceType) {
for (Enumeration<AbstractButton> buttons = instanceTypeButtonGroup.getElements(); buttons.hasMoreElements(); ) {
AbstractButton button = buttons.nextElement();
button.setSelected(button.getActionCommand().equals(instanceType.name()));
}
setDotcomSettingsEnabled(instanceType == InstanceType.DOTCOM);
setEnterpriseSettingsEnabled(instanceType == InstanceType.ENTERPRISE);
}
@NotNull
private JPanel createAuthenticationPanel() {
// Create dotcom access token field
JBLabel dotcomAccessTokenLabel = new JBLabel("Access token:");
dotcomAccessTokenTextField = new JBTextField();
dotcomAccessTokenTextField.getEmptyText().setText("Paste your access token here");
addValidation(dotcomAccessTokenTextField, () ->
// Create dotcom access token field
JBLabel dotcomAccessTokenLabel = new JBLabel("Access token:");
dotcomAccessTokenTextField = new JBTextField();
dotcomAccessTokenTextField.getEmptyText().setText("Paste your access token here");
addValidation(
dotcomAccessTokenTextField,
() ->
isValidAccessToken(dotcomAccessTokenTextField.getText())
? null
: new ValidationInfo("Invalid access token", dotcomAccessTokenTextField));
dotcomAccessTokenLinkComment = new JBLabel("<html><body>Have no token yet? Create one <a href=\"https://sourcegraph.com/user/settings/tokens/new\">here</a>.</body></html>", UIUtil.ComponentStyle.SMALL, UIUtil.FontColor.BRIGHTER);
//
dotcomAccessTokenLinkComment =
new JBLabel(
"<html><body>Have no token yet? Create one <a href=\"https://sourcegraph.com/user/settings/tokens/new\">here</a>.</body></html>",
UIUtil.ComponentStyle.SMALL,
UIUtil.FontColor.BRIGHTER);
//
// Create URL field for the enterprise section
JBLabel urlLabel = new JBLabel("Sourcegraph URL:");
enterpriseUrlTextField = new JBTextField();
//noinspection DialogTitleCapitalization
enterpriseUrlTextField.getEmptyText().setText("https://sourcegraph.example.com");
enterpriseUrlTextField.setToolTipText("The default is \"" + ConfigUtil.DOTCOM_URL + "\".");
addValidation(enterpriseUrlTextField, () ->
enterpriseUrlTextField.getText().length() == 0 ? new ValidationInfo("Missing URL", enterpriseUrlTextField)
: (!JsonSchemaConfigurable.isValidURL(enterpriseUrlTextField.getText()) ? new ValidationInfo("This is an invalid URL", enterpriseUrlTextField)
: null));
addDocumentListener(enterpriseUrlTextField, e -> updateAccessTokenLinkCommentText());
// Create URL field for the enterprise section
JBLabel urlLabel = new JBLabel("Sourcegraph URL:");
enterpriseUrlTextField = new JBTextField();
//noinspection DialogTitleCapitalization
enterpriseUrlTextField.getEmptyText().setText("https://sourcegraph.example.com");
enterpriseUrlTextField.setToolTipText("The default is \"" + ConfigUtil.DOTCOM_URL + "\".");
addValidation(
enterpriseUrlTextField,
() ->
enterpriseUrlTextField.getText().length() == 0
? new ValidationInfo("Missing URL", enterpriseUrlTextField)
: (!JsonSchemaConfigurable.isValidURL(enterpriseUrlTextField.getText())
? new ValidationInfo("This is an invalid URL", enterpriseUrlTextField)
: null));
addDocumentListener(enterpriseUrlTextField, e -> updateAccessTokenLinkCommentText());
// Create enterprise access token field
JBLabel enterpriseAccessTokenLabel = new JBLabel("Access token:");
enterpriseAccessTokenTextField = new JBTextField();
enterpriseAccessTokenTextField.getEmptyText().setText("Paste your access token here");
addValidation(enterpriseAccessTokenTextField, () ->
// Create enterprise access token field
JBLabel enterpriseAccessTokenLabel = new JBLabel("Access token:");
enterpriseAccessTokenTextField = new JBTextField();
enterpriseAccessTokenTextField.getEmptyText().setText("Paste your access token here");
addValidation(
enterpriseAccessTokenTextField,
() ->
isValidAccessToken(enterpriseAccessTokenTextField.getText())
? null
: new ValidationInfo("Invalid access token", enterpriseAccessTokenTextField));
// Create comments
userDocsLinkComment = new JBLabel("<html><body>You will need an access token to sign in. See our <a href=\"https://docs.sourcegraph.com/cli/how-tos/creating_an_access_token\">user docs</a> for a video guide</body></html>", UIUtil.ComponentStyle.SMALL, UIUtil.FontColor.BRIGHTER);
userDocsLinkComment.setBorder(JBUI.Borders.emptyLeft(10));
enterpriseAccessTokenLinkComment = new JBLabel("", UIUtil.ComponentStyle.SMALL, UIUtil.FontColor.BRIGHTER);
enterpriseAccessTokenLinkComment.setBorder(JBUI.Borders.emptyLeft(10));
// Create comments
userDocsLinkComment =
new JBLabel(
"<html><body>You will need an access token to sign in. See our <a href=\"https://docs.sourcegraph.com/cli/how-tos/creating_an_access_token\">user docs</a> for a video guide</body></html>",
UIUtil.ComponentStyle.SMALL,
UIUtil.FontColor.BRIGHTER);
userDocsLinkComment.setBorder(JBUI.Borders.emptyLeft(10));
enterpriseAccessTokenLinkComment =
new JBLabel("", UIUtil.ComponentStyle.SMALL, UIUtil.FontColor.BRIGHTER);
enterpriseAccessTokenLinkComment.setBorder(JBUI.Borders.emptyLeft(10));
// Set up radio buttons
ActionListener actionListener = event -> {
String actionCommand = event.getActionCommand();
setDotcomSettingsEnabled(actionCommand.equals(InstanceType.DOTCOM.name()));
setEnterpriseSettingsEnabled(actionCommand.equals(InstanceType.ENTERPRISE.name()));
// Set up radio buttons
ActionListener actionListener =
event -> {
String actionCommand = event.getActionCommand();
setDotcomSettingsEnabled(actionCommand.equals(InstanceType.DOTCOM.name()));
setEnterpriseSettingsEnabled(actionCommand.equals(InstanceType.ENTERPRISE.name()));
};
JRadioButton sourcegraphDotComRadioButton = new JRadioButton("Use sourcegraph.com");
sourcegraphDotComRadioButton.setMnemonic(KeyEvent.VK_C);
sourcegraphDotComRadioButton.setActionCommand(InstanceType.DOTCOM.name());
sourcegraphDotComRadioButton.addActionListener(actionListener);
JRadioButton enterpriseInstanceRadioButton = new JRadioButton("Use an enterprise instance");
enterpriseInstanceRadioButton.setMnemonic(KeyEvent.VK_E);
enterpriseInstanceRadioButton.setActionCommand(InstanceType.ENTERPRISE.name());
enterpriseInstanceRadioButton.addActionListener(actionListener);
instanceTypeButtonGroup = new ButtonGroup();
instanceTypeButtonGroup.add(sourcegraphDotComRadioButton);
instanceTypeButtonGroup.add(enterpriseInstanceRadioButton);
JRadioButton sourcegraphDotComRadioButton = new JRadioButton("Use sourcegraph.com");
sourcegraphDotComRadioButton.setMnemonic(KeyEvent.VK_C);
sourcegraphDotComRadioButton.setActionCommand(InstanceType.DOTCOM.name());
sourcegraphDotComRadioButton.addActionListener(actionListener);
JRadioButton enterpriseInstanceRadioButton = new JRadioButton("Use an enterprise instance");
enterpriseInstanceRadioButton.setMnemonic(KeyEvent.VK_E);
enterpriseInstanceRadioButton.setActionCommand(InstanceType.ENTERPRISE.name());
enterpriseInstanceRadioButton.addActionListener(actionListener);
instanceTypeButtonGroup = new ButtonGroup();
instanceTypeButtonGroup.add(sourcegraphDotComRadioButton);
instanceTypeButtonGroup.add(enterpriseInstanceRadioButton);
// Assemble the two main panels
JBLabel dotComComment = new JBLabel("Cody for open source code is available to all users with a Sourcegraph.com account",
UIUtil.ComponentStyle.SMALL, UIUtil.FontColor.BRIGHTER);
dotComComment.setBorder(JBUI.Borders.emptyLeft(20));
JPanel dotcomPanelContent = FormBuilder.createFormBuilder()
// Assemble the two main panels
JBLabel dotComComment =
new JBLabel(
"Cody for open source code is available to all users with a Sourcegraph.com account",
UIUtil.ComponentStyle.SMALL,
UIUtil.FontColor.BRIGHTER);
dotComComment.setBorder(JBUI.Borders.emptyLeft(20));
JPanel dotcomPanelContent =
FormBuilder.createFormBuilder()
.addLabeledComponent(dotcomAccessTokenLabel, dotcomAccessTokenTextField, 1)
.addComponentToRightColumn(dotcomAccessTokenLinkComment, 1)
.getPanel();
dotcomPanelContent.setBorder(IdeBorderFactory.createEmptyBorder(JBUI.insets(1, 30, 0, 0)));
JPanel dotComPanel = FormBuilder.createFormBuilder()
dotcomPanelContent.setBorder(IdeBorderFactory.createEmptyBorder(JBUI.insets(1, 30, 0, 0)));
JPanel dotComPanel =
FormBuilder.createFormBuilder()
.addComponent(sourcegraphDotComRadioButton, 1)
.addComponentToRightColumn(dotComComment, 2)
.addComponent(dotcomPanelContent, 1)
.getPanel();
JPanel enterprisePanelContent = FormBuilder.createFormBuilder()
JPanel enterprisePanelContent =
FormBuilder.createFormBuilder()
.addLabeledComponent(urlLabel, enterpriseUrlTextField, 1)
.addTooltip("If your company uses a private Sourcegraph instance, set its URL here")
.addLabeledComponent(enterpriseAccessTokenLabel, enterpriseAccessTokenTextField, 1)
.addComponentToRightColumn(userDocsLinkComment, 1)
.addComponentToRightColumn(enterpriseAccessTokenLinkComment, 1)
.getPanel();
enterprisePanelContent.setBorder(IdeBorderFactory.createEmptyBorder(JBUI.insets(1, 30, 0, 0)));
JPanel enterprisePanel = FormBuilder.createFormBuilder()
enterprisePanelContent.setBorder(IdeBorderFactory.createEmptyBorder(JBUI.insets(1, 30, 0, 0)));
JPanel enterprisePanel =
FormBuilder.createFormBuilder()
.addComponent(enterpriseInstanceRadioButton, 1)
.addComponent(enterprisePanelContent, 1)
.getPanel();
// Create the "Request headers" text box
JBLabel customRequestHeadersLabel = new JBLabel("Custom headers:");
customRequestHeadersTextField = new JBTextField();
customRequestHeadersTextField.getEmptyText().setText("Client-ID, client-one, X-Extra, some metadata");
customRequestHeadersTextField.setToolTipText("You can even overwrite \"Authorization\" that Access token sets above.");
addValidation(customRequestHeadersTextField, () -> {
if (customRequestHeadersTextField.getText().length() == 0) {
return null;
}
String[] pairs = customRequestHeadersTextField.getText().split(",");
if (pairs.length % 2 != 0) {
return new ValidationInfo("Must be a comma-separated list of pairs", customRequestHeadersTextField);
}
for (int i = 0; i < pairs.length; i += 2) {
String headerName = pairs[i].trim();
if (!headerName.matches("[\\w-]+")) {
return new ValidationInfo("Invalid HTTP header name: " + headerName, customRequestHeadersTextField);
}
}
// Create the "Request headers" text box
JBLabel customRequestHeadersLabel = new JBLabel("Custom headers:");
customRequestHeadersTextField = new JBTextField();
customRequestHeadersTextField
.getEmptyText()
.setText("Client-ID, client-one, X-Extra, some metadata");
customRequestHeadersTextField.setToolTipText(
"You can even overwrite \"Authorization\" that Access token sets above.");
addValidation(
customRequestHeadersTextField,
() -> {
if (customRequestHeadersTextField.getText().length() == 0) {
return null;
}
String[] pairs = customRequestHeadersTextField.getText().split(",");
if (pairs.length % 2 != 0) {
return new ValidationInfo(
"Must be a comma-separated list of pairs", customRequestHeadersTextField);
}
for (int i = 0; i < pairs.length; i += 2) {
String headerName = pairs[i].trim();
if (!headerName.matches("[\\w-]+")) {
return new ValidationInfo(
"Invalid HTTP header name: " + headerName, customRequestHeadersTextField);
}
}
return null;
});
// Assemble the main panel
JPanel userAuthenticationPanel = FormBuilder.createFormBuilder()
// Assemble the main panel
JPanel userAuthenticationPanel =
FormBuilder.createFormBuilder()
.addComponent(dotComPanel)
.addComponent(enterprisePanel, 5)
.addLabeledComponent(customRequestHeadersLabel, customRequestHeadersTextField)
@ -193,162 +230,177 @@ public class SettingsComponent implements Disposable {
.addTooltip("Use any number of pairs: \"header1, value1, header2, value2, ...\".")
.addTooltip("Whitespace around commas doesn't matter.")
.getPanel();
userAuthenticationPanel.setBorder(IdeBorderFactory.createTitledBorder("Authentication", true, JBUI.insetsTop(8)));
userAuthenticationPanel.setBorder(
IdeBorderFactory.createTitledBorder("Authentication", true, JBUI.insetsTop(8)));
return userAuthenticationPanel;
return userAuthenticationPanel;
}
@NotNull
public String getDotcomAccessToken() {
return dotcomAccessTokenTextField.getText();
}
public void setDotcomAccessToken(@NotNull String value) {
dotcomAccessTokenTextField.setText(value);
}
@NotNull
public String getEnterpriseUrl() {
return enterpriseUrlTextField.getText();
}
public void setEnterpriseUrl(@Nullable String value) {
enterpriseUrlTextField.setText(value != null ? value : "");
}
@NotNull
public String getEnterpriseAccessToken() {
return enterpriseAccessTokenTextField.getText();
}
public void setEnterpriseAccessToken(@NotNull String value) {
enterpriseAccessTokenTextField.setText(value);
}
@NotNull
public String getCustomRequestHeaders() {
return customRequestHeadersTextField.getText();
}
public void setCustomRequestHeaders(@NotNull String customRequestHeaders) {
this.customRequestHeadersTextField.setText(customRequestHeaders);
}
@NotNull
public String getCodebase() {
return codebaseTextField.getText();
}
public void setCodebase(@NotNull String value) {
codebaseTextField.setText(value);
}
public boolean areChatPredictionsEnabled() {
return areChatPredictionsEnabledCheckBox != null
&& areChatPredictionsEnabledCheckBox.isSelected();
}
public void setAreChatPredictionsEnabled(boolean value) {
if (areChatPredictionsEnabledCheckBox != null) {
areChatPredictionsEnabledCheckBox.setSelected(value);
}
}
@NotNull
public String getDotcomAccessToken() {
return dotcomAccessTokenTextField.getText();
}
private void setDotcomSettingsEnabled(boolean enable) {
dotcomAccessTokenTextField.setEnabled(enable);
dotcomAccessTokenLinkComment.setEnabled(enable);
dotcomAccessTokenLinkComment.setCopyable(enable);
}
public void setDotcomAccessToken(@NotNull String value) {
dotcomAccessTokenTextField.setText(value);
}
private void setEnterpriseSettingsEnabled(boolean enable) {
enterpriseUrlTextField.setEnabled(enable);
enterpriseAccessTokenTextField.setEnabled(enable);
userDocsLinkComment.setEnabled(enable);
userDocsLinkComment.setCopyable(enable);
enterpriseAccessTokenLinkComment.setEnabled(enable);
enterpriseAccessTokenLinkComment.setCopyable(enable);
}
@NotNull
public String getEnterpriseUrl() {
return enterpriseUrlTextField.getText();
}
@Override
public void dispose() {
instanceTypeButtonGroup = null;
enterpriseUrlTextField = null;
enterpriseAccessTokenTextField = null;
customRequestHeadersTextField = null;
codebaseTextField = null;
areChatPredictionsEnabledCheckBox = null;
userDocsLinkComment = null;
enterpriseAccessTokenLinkComment = null;
}
public void setEnterpriseUrl(@Nullable String value) {
enterpriseUrlTextField.setText(value != null ? value : "");
}
public enum InstanceType {
DOTCOM,
ENTERPRISE,
}
@NotNull
public String getEnterpriseAccessToken() {
return enterpriseAccessTokenTextField.getText();
}
private void addValidation(
@NotNull JTextComponent component, @NotNull Supplier<ValidationInfo> validator) {
new ComponentValidator(this).withValidator(validator).installOn(component);
addDocumentListener(
component,
e -> ComponentValidator.getInstance(component).ifPresent(ComponentValidator::revalidate));
}
public void setEnterpriseAccessToken(@NotNull String value) {
enterpriseAccessTokenTextField.setText(value);
}
@NotNull
public String getCustomRequestHeaders() {
return customRequestHeadersTextField.getText();
}
public void setCustomRequestHeaders(@NotNull String customRequestHeaders) {
this.customRequestHeadersTextField.setText(customRequestHeaders);
}
@NotNull
public String getCodebase() {
return codebaseTextField.getText();
}
public void setCodebase(@NotNull String value) {
codebaseTextField.setText(value);
}
public boolean areChatPredictionsEnabled() {
return areChatPredictionsEnabledCheckBox != null && areChatPredictionsEnabledCheckBox.isSelected();
}
public void setAreChatPredictionsEnabled(boolean value) {
if (areChatPredictionsEnabledCheckBox != null) {
areChatPredictionsEnabledCheckBox.setSelected(value);
}
}
private void setDotcomSettingsEnabled(boolean enable) {
dotcomAccessTokenTextField.setEnabled(enable);
dotcomAccessTokenLinkComment.setEnabled(enable);
dotcomAccessTokenLinkComment.setCopyable(enable);
}
private void setEnterpriseSettingsEnabled(boolean enable) {
enterpriseUrlTextField.setEnabled(enable);
enterpriseAccessTokenTextField.setEnabled(enable);
userDocsLinkComment.setEnabled(enable);
userDocsLinkComment.setCopyable(enable);
enterpriseAccessTokenLinkComment.setEnabled(enable);
enterpriseAccessTokenLinkComment.setCopyable(enable);
}
@Override
public void dispose() {
instanceTypeButtonGroup = null;
enterpriseUrlTextField = null;
enterpriseAccessTokenTextField = null;
customRequestHeadersTextField = null;
codebaseTextField = null;
areChatPredictionsEnabledCheckBox = null;
userDocsLinkComment = null;
enterpriseAccessTokenLinkComment = null;
}
public enum InstanceType {
DOTCOM,
ENTERPRISE,
}
private void addValidation(@NotNull JTextComponent component, @NotNull Supplier<ValidationInfo> validator) {
new ComponentValidator(this).withValidator(validator).installOn(component);
addDocumentListener(component, e -> ComponentValidator.getInstance(component).ifPresent(ComponentValidator::revalidate));
}
private void addDocumentListener(@NotNull JTextComponent textComponent, @NotNull Consumer<ComponentValidator> function) {
textComponent.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
private void addDocumentListener(
@NotNull JTextComponent textComponent, @NotNull Consumer<ComponentValidator> function) {
textComponent
.getDocument()
.addDocumentListener(
new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
ComponentValidator.getInstance(textComponent).ifPresent(function);
}
}
@Override
public void removeUpdate(DocumentEvent e) {
@Override
public void removeUpdate(DocumentEvent e) {
ComponentValidator.getInstance(textComponent).ifPresent(function);
}
}
@Override
public void changedUpdate(DocumentEvent e) {
@Override
public void changedUpdate(DocumentEvent e) {
ComponentValidator.getInstance(textComponent).ifPresent(function);
}
});
}
}
});
}
private void updateAccessTokenLinkCommentText() {
String baseUrl = enterpriseUrlTextField.getText();
String settingsUrl = (baseUrl.endsWith("/") ? baseUrl : baseUrl + "/") + "settings";
enterpriseAccessTokenLinkComment.setText(isUrlValid(baseUrl)
? "<html><body>or go to <a href=\"" + settingsUrl + "\">" + settingsUrl + "</a> | \"Access tokens\" to create one.</body></html>"
private void updateAccessTokenLinkCommentText() {
String baseUrl = enterpriseUrlTextField.getText();
String settingsUrl = (baseUrl.endsWith("/") ? baseUrl : baseUrl + "/") + "settings";
enterpriseAccessTokenLinkComment.setText(
isUrlValid(baseUrl)
? "<html><body>or go to <a href=\""
+ settingsUrl
+ "\">"
+ settingsUrl
+ "</a> | \"Access tokens\" to create one.</body></html>"
: "");
}
}
private boolean isValidAccessToken(@NotNull String accessToken) {
return accessToken.isEmpty() ||
accessToken.length() == 40 ||
(accessToken.startsWith("sgp_") && accessToken.length() == 44);
}
private boolean isValidAccessToken(@NotNull String accessToken) {
return accessToken.isEmpty()
|| accessToken.length() == 40
|| (accessToken.startsWith("sgp_") && accessToken.length() == 44);
}
private boolean isUrlValid(@NotNull String url) {
return JsonSchemaConfigurable.isValidURL(url);
}
private boolean isUrlValid(@NotNull String url) {
return JsonSchemaConfigurable.isValidURL(url);
}
@NotNull
private JPanel createOtherSettingsPanel() {
JBLabel codebaseLabel = new JBLabel("Codebase:");
codebaseTextField = new JBTextField();
//noinspection DialogTitleCapitalization
codebaseTextField.getEmptyText().setText("github.com/sourcegraph/sourcegraph");
@NotNull
private JPanel createOtherSettingsPanel() {
JBLabel codebaseLabel = new JBLabel("Codebase:");
codebaseTextField = new JBTextField();
//noinspection DialogTitleCapitalization
codebaseTextField.getEmptyText().setText("github.com/sourcegraph/sourcegraph");
//// Always disabled for now
//areChatPredictionsEnabledCheckBox = new JBCheckBox("Experimental: Chat predictions");
//areChatPredictionsEnabledCheckBox.setEnabled(false);
//// Always disabled for now
// areChatPredictionsEnabledCheckBox = new JBCheckBox("Experimental: Chat predictions");
// areChatPredictionsEnabledCheckBox.setEnabled(false);
//noinspection DialogTitleCapitalization
JPanel otherSettingsPanel = FormBuilder.createFormBuilder()
//noinspection DialogTitleCapitalization
JPanel otherSettingsPanel =
FormBuilder.createFormBuilder()
.addLabeledComponent(codebaseLabel, codebaseTextField)
.addTooltip("The name of the embedded repository that Cody will use to gather context")
.addTooltip("for its responses. This is automatically inferred from your Git metadata,")
.addTooltip("but you can use this option if you need to override the default.")
//.addComponent(areChatPredictionsEnabledCheckBox, 10)
//.addTooltip("Adds suggestions of possible relevant messages in the chat window")
// .addComponent(areChatPredictionsEnabledCheckBox, 10)
// .addTooltip("Adds suggestions of possible relevant messages in the chat window")
.getPanel();
otherSettingsPanel.setBorder(IdeBorderFactory.createTitledBorder("Other Settings", true, JBUI.insetsTop(8)));
return otherSettingsPanel;
}
otherSettingsPanel.setBorder(
IdeBorderFactory.createTitledBorder("Other Settings", true, JBUI.insetsTop(8)));
return otherSettingsPanel;
}
}

View File

@ -6,73 +6,125 @@ import com.intellij.util.messages.MessageBus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Provides controller functionality for application settings.
*/
/** Provides controller functionality for application settings. */
public class SettingsConfigurableHelper {
public static boolean isModified(@Nullable Project project, @NotNull SettingsComponent newSettings) {
CodyService oldSettings = project != null ? CodyProjectService.getInstance(project) :CodyApplicationService.getInstance();
return !newSettings.getInstanceType().toString().equals(oldSettings.getInstanceType() != null ? oldSettings.getInstanceType() : SettingsComponent.InstanceType.DOTCOM.toString())
|| !(newSettings.getDotcomAccessToken().equals(oldSettings.getDotcomAccessToken()) || newSettings.getDotcomAccessToken().isEmpty() && oldSettings.getDotcomAccessToken() == null)
|| !newSettings.getEnterpriseUrl().equals(oldSettings.getEnterpriseUrl() != null ? oldSettings.getEnterpriseUrl() : "")
|| !(newSettings.getEnterpriseAccessToken().equals(oldSettings.getEnterpriseAccessToken()) || newSettings.getEnterpriseAccessToken().isEmpty() && oldSettings.getEnterpriseAccessToken() == null)
|| !newSettings.getCustomRequestHeaders().equals(oldSettings.getCustomRequestHeaders() != null ? oldSettings.getCustomRequestHeaders() : "")
|| !newSettings.getCodebase().equals(oldSettings.getCodebase() != null ? oldSettings.getCodebase() : "")
|| newSettings.areChatPredictionsEnabled() != Boolean.TRUE.equals(oldSettings.areChatPredictionsEnabled());
}
public static boolean isModified(
@Nullable Project project, @NotNull SettingsComponent newSettings) {
CodyService oldSettings =
project != null
? CodyProjectService.getInstance(project)
: CodyApplicationService.getInstance();
return !newSettings
.getInstanceType()
.toString()
.equals(
oldSettings.getInstanceType() != null
? oldSettings.getInstanceType()
: SettingsComponent.InstanceType.DOTCOM.toString())
|| !(newSettings.getDotcomAccessToken().equals(oldSettings.getDotcomAccessToken())
|| newSettings.getDotcomAccessToken().isEmpty()
&& oldSettings.getDotcomAccessToken() == null)
|| !newSettings
.getEnterpriseUrl()
.equals(oldSettings.getEnterpriseUrl() != null ? oldSettings.getEnterpriseUrl() : "")
|| !(newSettings.getEnterpriseAccessToken().equals(oldSettings.getEnterpriseAccessToken())
|| newSettings.getEnterpriseAccessToken().isEmpty()
&& oldSettings.getEnterpriseAccessToken() == null)
|| !newSettings
.getCustomRequestHeaders()
.equals(
oldSettings.getCustomRequestHeaders() != null
? oldSettings.getCustomRequestHeaders()
: "")
|| !newSettings
.getCodebase()
.equals(oldSettings.getCodebase() != null ? oldSettings.getCodebase() : "")
|| newSettings.areChatPredictionsEnabled()
!= Boolean.TRUE.equals(oldSettings.areChatPredictionsEnabled());
}
public static void apply(@Nullable Project project, @NotNull SettingsComponent settings) {
// Get message bus and publisher
MessageBus bus = project != null ? project.getMessageBus() : ApplicationManager.getApplication().getMessageBus();
PluginSettingChangeActionNotifier publisher = bus.syncPublisher(PluginSettingChangeActionNotifier.TOPIC);
public static void apply(@Nullable Project project, @NotNull SettingsComponent settings) {
// Get message bus and publisher
MessageBus bus =
project != null
? project.getMessageBus()
: ApplicationManager.getApplication().getMessageBus();
PluginSettingChangeActionNotifier publisher =
bus.syncPublisher(PluginSettingChangeActionNotifier.TOPIC);
// Select settings service: application or project
CodyService apSettings = project != null ? CodyProjectService.getInstance(project) : CodyApplicationService.getInstance();
// Select settings service: application or project
CodyService apSettings =
project != null
? CodyProjectService.getInstance(project)
: CodyApplicationService.getInstance();
// Get old and new settings
String oldDotcomAccessToken = ConfigUtil.getDotcomAccessToken(project);
String oldEnterpriseUrl = ConfigUtil.getSourcegraphUrl(project);
String oldEnterpriseAccessToken = ConfigUtil.getEnterpriseAccessToken(project);
String newDotcomAccessToken = settings.getDotcomAccessToken();
String newEnterpriseUrl = settings.getEnterpriseUrl();
String newEnterpriseAccessToken = settings.getEnterpriseAccessToken();
String newCustomRequestHeaders = settings.getCustomRequestHeaders();
// Get old and new settings
String oldDotcomAccessToken = ConfigUtil.getDotcomAccessToken(project);
String oldEnterpriseUrl = ConfigUtil.getSourcegraphUrl(project);
String oldEnterpriseAccessToken = ConfigUtil.getEnterpriseAccessToken(project);
String newDotcomAccessToken = settings.getDotcomAccessToken();
String newEnterpriseUrl = settings.getEnterpriseUrl();
String newEnterpriseAccessToken = settings.getEnterpriseAccessToken();
String newCustomRequestHeaders = settings.getCustomRequestHeaders();
// Create context
PluginSettingChangeContext context = new PluginSettingChangeContext(oldDotcomAccessToken, oldEnterpriseUrl, oldEnterpriseAccessToken,
newEnterpriseUrl, newDotcomAccessToken, newEnterpriseAccessToken, newCustomRequestHeaders);
// Create context
PluginSettingChangeContext context =
new PluginSettingChangeContext(
oldDotcomAccessToken,
oldEnterpriseUrl,
oldEnterpriseAccessToken,
newEnterpriseUrl,
newDotcomAccessToken,
newEnterpriseAccessToken,
newCustomRequestHeaders);
// Notify listeners
publisher.beforeAction(context);
// Notify listeners
publisher.beforeAction(context);
// Update settings
String instanceTypeName = settings.getInstanceType().name();
apSettings.setInstanceType(instanceTypeName.equals(SettingsComponent.InstanceType.ENTERPRISE.name()) || !newDotcomAccessToken.equals("")
? instanceTypeName : null);
apSettings.setDotcomAccessToken(!newDotcomAccessToken.equals("") ? newDotcomAccessToken : null);
apSettings.setEnterpriseUrl(!newEnterpriseUrl.equals("") ? newEnterpriseUrl : null);
apSettings.setEnterpriseAccessToken(!newEnterpriseAccessToken.equals("") ? newEnterpriseAccessToken : null);
apSettings.setCustomRequestHeaders(settings.getCustomRequestHeaders());
apSettings.setCodebase(!settings.getCodebase().equals("") ? settings.getCodebase() : null);
apSettings.setChatPredictionsEnabled(settings.areChatPredictionsEnabled());
// Update settings
String instanceTypeName = settings.getInstanceType().name();
apSettings.setInstanceType(
instanceTypeName.equals(SettingsComponent.InstanceType.ENTERPRISE.name())
|| !newDotcomAccessToken.equals("")
? instanceTypeName
: null);
apSettings.setDotcomAccessToken(!newDotcomAccessToken.equals("") ? newDotcomAccessToken : null);
apSettings.setEnterpriseUrl(!newEnterpriseUrl.equals("") ? newEnterpriseUrl : null);
apSettings.setEnterpriseAccessToken(
!newEnterpriseAccessToken.equals("") ? newEnterpriseAccessToken : null);
apSettings.setCustomRequestHeaders(settings.getCustomRequestHeaders());
apSettings.setCodebase(!settings.getCodebase().equals("") ? settings.getCodebase() : null);
apSettings.setChatPredictionsEnabled(settings.areChatPredictionsEnabled());
// Notify listeners
publisher.afterAction(context);
}
// Notify listeners
publisher.afterAction(context);
}
public static void reset(@Nullable Project project, @NotNull SettingsComponent mySettingsComponent) {
CodyService settings = project != null ? CodyProjectService.getInstance(project) :CodyApplicationService.getInstance();
String instanceType = settings.getInstanceType();
mySettingsComponent.setInstanceType(instanceType != null ? instanceType.equals(SettingsComponent.InstanceType.ENTERPRISE.name())
? SettingsComponent.InstanceType.ENTERPRISE : SettingsComponent.InstanceType.DOTCOM : SettingsComponent.InstanceType.DOTCOM);
String dotcomAccessToken = settings.getDotcomAccessToken();
mySettingsComponent.setDotcomAccessToken(dotcomAccessToken != null ? dotcomAccessToken : "");
mySettingsComponent.setEnterpriseUrl(settings.getEnterpriseUrl());
String enterpriseAccessToken = settings.getEnterpriseAccessToken();
mySettingsComponent.setEnterpriseAccessToken(enterpriseAccessToken != null ? enterpriseAccessToken : "");
mySettingsComponent.setCustomRequestHeaders(settings.getCustomRequestHeaders() != null ? settings.getCustomRequestHeaders() : "");
String codebase = settings.getCodebase();
mySettingsComponent.setCodebase(codebase != null ? codebase : "");
mySettingsComponent.setAreChatPredictionsEnabled(settings.areChatPredictionsEnabled() != null && Boolean.TRUE.equals(settings.areChatPredictionsEnabled()));
}
public static void reset(
@Nullable Project project, @NotNull SettingsComponent mySettingsComponent) {
CodyService settings =
project != null
? CodyProjectService.getInstance(project)
: CodyApplicationService.getInstance();
String instanceType = settings.getInstanceType();
mySettingsComponent.setInstanceType(
instanceType != null
? instanceType.equals(SettingsComponent.InstanceType.ENTERPRISE.name())
? SettingsComponent.InstanceType.ENTERPRISE
: SettingsComponent.InstanceType.DOTCOM
: SettingsComponent.InstanceType.DOTCOM);
String dotcomAccessToken = settings.getDotcomAccessToken();
mySettingsComponent.setDotcomAccessToken(dotcomAccessToken != null ? dotcomAccessToken : "");
mySettingsComponent.setEnterpriseUrl(settings.getEnterpriseUrl());
String enterpriseAccessToken = settings.getEnterpriseAccessToken();
mySettingsComponent.setEnterpriseAccessToken(
enterpriseAccessToken != null ? enterpriseAccessToken : "");
mySettingsComponent.setCustomRequestHeaders(
settings.getCustomRequestHeaders() != null ? settings.getCustomRequestHeaders() : "");
String codebase = settings.getCodebase();
mySettingsComponent.setCodebase(codebase != null ? codebase : "");
mySettingsComponent.setAreChatPredictionsEnabled(
settings.areChatPredictionsEnabled() != null
&& Boolean.TRUE.equals(settings.areChatPredictionsEnabled()));
}
}

View File

@ -4,25 +4,26 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ContextFile {
private final @NotNull String fileName;
private final @Nullable String repoName;
private final @Nullable String revision;
private final @NotNull String fileName;
private final @Nullable String repoName;
private final @Nullable String revision;
public ContextFile(@NotNull String fileName, @Nullable String repoName, @Nullable String revision) {
this.fileName = fileName;
this.repoName = repoName;
this.revision = revision;
}
public ContextFile(
@NotNull String fileName, @Nullable String repoName, @Nullable String revision) {
this.fileName = fileName;
this.repoName = repoName;
this.revision = revision;
}
public @NotNull String getFileName() {
return fileName;
}
public @NotNull String getFileName() {
return fileName;
}
public @Nullable String getRepoName() {
return repoName;
}
public @Nullable String getRepoName() {
return repoName;
}
public @Nullable String getRevision() {
return revision;
}
public @Nullable String getRevision() {
return revision;
}
}

View File

@ -1,27 +1,29 @@
package com.sourcegraph.cody.context;
import com.sourcegraph.cody.context.embeddings.EmbeddingsSearcher;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
// TODO: Use this class to get context
public class ContextGetter {
private final @NotNull String codebase;
private final @NotNull String codebase;
public ContextGetter(@NotNull String codebase) {
this.codebase = codebase;
}
public ContextGetter(@NotNull String codebase) {
this.codebase = codebase;
}
public @NotNull List<ContextMessage> getContextMessages(@NotNull String query, int codeResultCount, int textResultCount, @NotNull String useContext) throws IOException {
if (useContext.equals("embeddings")) {
return EmbeddingsSearcher.getContextMessages(codebase, query, codeResultCount, textResultCount);
} else {
// TODO: Add keyword search if embeddings are not available
//return KeywordSearcher.getContextMessages(query, codeResultCount, textResultCount);
return new ArrayList<>();
}
public @NotNull List<ContextMessage> getContextMessages(
@NotNull String query, int codeResultCount, int textResultCount, @NotNull String useContext)
throws IOException {
if (useContext.equals("embeddings")) {
return EmbeddingsSearcher.getContextMessages(
codebase, query, codeResultCount, textResultCount);
} else {
// TODO: Add keyword search if embeddings are not available
// return KeywordSearcher.getContextMessages(query, codeResultCount, textResultCount);
return new ArrayList<>();
}
}
}

View File

@ -6,23 +6,24 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ContextMessage extends Message {
@Nullable
private final ContextFile file;
@Nullable private final ContextFile file;
public ContextMessage(@NotNull Speaker speaker, @NotNull String text, @Nullable ContextFile file) {
super(speaker, text);
this.file = file;
}
public ContextMessage(
@NotNull Speaker speaker, @NotNull String text, @Nullable ContextFile file) {
super(speaker, text);
this.file = file;
}
public @Nullable ContextFile getFile() {
return file;
}
public @Nullable ContextFile getFile() {
return file;
}
public static @NotNull ContextMessage createHumanMessage(@NotNull String text, @NotNull ContextFile file) {
return new ContextMessage(Speaker.HUMAN, text, file);
}
public static @NotNull ContextMessage createHumanMessage(
@NotNull String text, @NotNull ContextFile file) {
return new ContextMessage(Speaker.HUMAN, text, file);
}
public static @NotNull ContextMessage createDefaultAssistantMessage() {
return new ContextMessage(Speaker.ASSISTANT, "Ok.", null);
}
public static @NotNull ContextMessage createDefaultAssistantMessage() {
return new ContextMessage(Speaker.ASSISTANT, "Ok.", null);
}
}

View File

@ -4,43 +4,49 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class EmbeddingsSearchResult {
private final @Nullable String repoName;
private final @Nullable String revision;
private final @NotNull String fileName;
private final int startLine;
private final int endLine;
private final @NotNull String content;
private final @Nullable String repoName;
private final @Nullable String revision;
private final @NotNull String fileName;
private final int startLine;
private final int endLine;
private final @NotNull String content;
public EmbeddingsSearchResult(@Nullable String repoName, @Nullable String revision, @NotNull String fileName, int startLine, int endLine, @NotNull String content) {
this.repoName = repoName;
this.revision = revision;
this.fileName = fileName;
this.startLine = startLine;
this.endLine = endLine;
this.content = content;
}
public EmbeddingsSearchResult(
@Nullable String repoName,
@Nullable String revision,
@NotNull String fileName,
int startLine,
int endLine,
@NotNull String content) {
this.repoName = repoName;
this.revision = revision;
this.fileName = fileName;
this.startLine = startLine;
this.endLine = endLine;
this.content = content;
}
public @Nullable String getRepoName() {
return repoName;
}
public @Nullable String getRepoName() {
return repoName;
}
public @Nullable String getRevision() {
return revision;
}
public @Nullable String getRevision() {
return revision;
}
public @NotNull String getFileName() {
return fileName;
}
public @NotNull String getFileName() {
return fileName;
}
public int getStartLine() {
return startLine;
}
public int getStartLine() {
return startLine;
}
public int getEndLine() {
return endLine;
}
public int getEndLine() {
return endLine;
}
public @NotNull String getContent() {
return content;
}
public @NotNull String getContent() {
return content;
}
}

View File

@ -1,32 +1,33 @@
package com.sourcegraph.cody.context.embeddings;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class EmbeddingsSearchResults {
private @NotNull List<EmbeddingsSearchResult> codeResults;
private @NotNull List<EmbeddingsSearchResult> textResults;
private @NotNull List<EmbeddingsSearchResult> codeResults;
private @NotNull List<EmbeddingsSearchResult> textResults;
public EmbeddingsSearchResults(@NotNull List<EmbeddingsSearchResult> codeResults, @NotNull List<EmbeddingsSearchResult> textResults) {
this.codeResults = codeResults;
this.textResults = textResults;
}
public EmbeddingsSearchResults(
@NotNull List<EmbeddingsSearchResult> codeResults,
@NotNull List<EmbeddingsSearchResult> textResults) {
this.codeResults = codeResults;
this.textResults = textResults;
}
// Getters and setters
public @NotNull List<EmbeddingsSearchResult> getCodeResults() {
return codeResults;
}
// Getters and setters
public @NotNull List<EmbeddingsSearchResult> getCodeResults() {
return codeResults;
}
public void setCodeResults(@NotNull List<EmbeddingsSearchResult> codeResults) {
this.codeResults = codeResults;
}
public void setCodeResults(@NotNull List<EmbeddingsSearchResult> codeResults) {
this.codeResults = codeResults;
}
public @NotNull List<EmbeddingsSearchResult> getTextResults() {
return textResults;
}
public @NotNull List<EmbeddingsSearchResult> getTextResults() {
return textResults;
}
public void setTextResults(@NotNull List<EmbeddingsSearchResult> textResults) {
this.textResults = textResults;
}
public void setTextResults(@NotNull List<EmbeddingsSearchResult> textResults) {
this.textResults = textResults;
}
}

View File

@ -6,162 +6,179 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.sourcegraph.api.GraphQlClient;
import com.sourcegraph.api.GraphQlResponse;
import com.sourcegraph.cody.prompts.Prompter;
import com.sourcegraph.cody.context.ContextMessage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.sourcegraph.cody.prompts.Prompter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class EmbeddingsSearcher {
public static @NotNull List<ContextMessage> getContextMessages(@NotNull String codebase, @NotNull String query, int codeResultCount, int textResultCount) throws IOException {
// Get repo ID
String repoId;
repoId = EmbeddingsSearcher.getRepoIdIfEmbeddingExists(codebase);
public static @NotNull List<ContextMessage> getContextMessages(
@NotNull String codebase, @NotNull String query, int codeResultCount, int textResultCount)
throws IOException {
// Get repo ID
String repoId;
repoId = EmbeddingsSearcher.getRepoIdIfEmbeddingExists(codebase);
// Run embeddings search
EmbeddingsSearchResults results = EmbeddingsSearcher.search(repoId, query, codeResultCount, textResultCount);
// Run embeddings search
EmbeddingsSearchResults results =
EmbeddingsSearcher.search(repoId, query, codeResultCount, textResultCount);
// Concat results.getCodeResults() and results.getTextResults() into a single list
List<EmbeddingsSearchResult> allResults = new ArrayList<>();
allResults.addAll(results.getCodeResults());
allResults.addAll(results.getTextResults());
// Concat results.getCodeResults() and results.getTextResults() into a single list
List<EmbeddingsSearchResult> allResults = new ArrayList<>();
allResults.addAll(results.getCodeResults());
allResults.addAll(results.getTextResults());
// Group results by file
List<GroupedResults> groupedResults = ResultsGrouper.groupResultsByFile(allResults);
// Group results by file
List<GroupedResults> groupedResults = ResultsGrouper.groupResultsByFile(allResults);
// Reverse results so that they appear in ascending order of importance (least -> most)
Collections.reverse(groupedResults);
// Reverse results so that they appear in ascending order of importance (least -> most)
Collections.reverse(groupedResults);
// Get context messages
List<ContextMessage> messages = new ArrayList<>();
for (GroupedResults group : groupedResults) {
for (String snippet : group.getSnippets()) {
String contextPrompt = Prompter.getContextPrompt(group.getFile().getFileName(), snippet);
messages.add(ContextMessage.createHumanMessage(contextPrompt, group.getFile()));
messages.add(ContextMessage.createDefaultAssistantMessage());
}
}
return messages;
// Get context messages
List<ContextMessage> messages = new ArrayList<>();
for (GroupedResults group : groupedResults) {
for (String snippet : group.getSnippets()) {
String contextPrompt = Prompter.getContextPrompt(group.getFile().getFileName(), snippet);
messages.add(ContextMessage.createHumanMessage(contextPrompt, group.getFile()));
messages.add(ContextMessage.createDefaultAssistantMessage());
}
}
return messages;
}
private static EmbeddingsSearchResults search(@NotNull String repoId, @NotNull String query, int codeResultsCount, int textResultsCount) throws IOException {
// Prepare GraphQL query
String graphQlQuery = "query LegacyEmbeddingsSearch($repo: ID!, $query: String!, $codeResultsCount: Int!, $textResultsCount: Int!) {\n" +
" embeddingsSearch(repo: $repo, query: $query, codeResultsCount: $codeResultsCount, textResultsCount: $textResultsCount) {\n" +
" codeResults {\n" +
" fileName\n" +
" startLine\n" +
" endLine\n" +
" content\n" +
" }\n" +
" textResults {\n" +
" fileName\n" +
" startLine\n" +
" endLine\n" +
" content\n" +
" }\n" +
" }\n" +
"}";
JsonObject variables = new JsonObject();
variables.add("repo", new JsonPrimitive(repoId));
variables.add("query", new JsonPrimitive(query));
variables.add("codeResultsCount", new JsonPrimitive(codeResultsCount));
variables.add("textResultsCount", new JsonPrimitive(textResultsCount));
private static EmbeddingsSearchResults search(
@NotNull String repoId, @NotNull String query, int codeResultsCount, int textResultsCount)
throws IOException {
// Prepare GraphQL query
String graphQlQuery =
"query LegacyEmbeddingsSearch($repo: ID!, $query: String!, $codeResultsCount: Int!, $textResultsCount: Int!) {\n"
+ " embeddingsSearch(repo: $repo, query: $query, codeResultsCount: $codeResultsCount, textResultsCount: $textResultsCount) {\n"
+ " codeResults {\n"
+ " fileName\n"
+ " startLine\n"
+ " endLine\n"
+ " content\n"
+ " }\n"
+ " textResults {\n"
+ " fileName\n"
+ " startLine\n"
+ " endLine\n"
+ " content\n"
+ " }\n"
+ " }\n"
+ "}";
JsonObject variables = new JsonObject();
variables.add("repo", new JsonPrimitive(repoId));
variables.add("query", new JsonPrimitive(query));
variables.add("codeResultsCount", new JsonPrimitive(codeResultsCount));
variables.add("textResultsCount", new JsonPrimitive(textResultsCount));
// Call GraphQL service
GraphQlResponse response = GraphQlClient.callGraphQLService("TODO", "TODO", "TODO", graphQlQuery, variables); // TODO!
// Call GraphQL service
GraphQlResponse response =
GraphQlClient.callGraphQLService("TODO", "TODO", "TODO", graphQlQuery, variables); // TODO!
// Parse response
if (response.getStatusCode() != 200) {
throw new IOException("GraphQL request failed with status code " + response.getStatusCode());
// Parse response
if (response.getStatusCode() != 200) {
throw new IOException("GraphQL request failed with status code " + response.getStatusCode());
} else {
try {
JsonObject body = response.getBodyAsJson();
if (body.has("errors")) {
throw new IOException("GraphQL request failed with errors: " + body.get("errors"));
}
JsonObject data = body.getAsJsonObject("data");
if (data == null) {
throw new IOException("GraphQL response is missing data field");
}
JsonObject embeddingsSearch = data.getAsJsonObject("embeddingsSearch");
if (embeddingsSearch == null) {
throw new IOException("GraphQL response is missing data.embeddingsSearch field");
}
ArrayList<EmbeddingsSearchResult> codeResults =
convertRawResultsToSearchResults(embeddingsSearch.getAsJsonObject("codeResults"));
ArrayList<EmbeddingsSearchResult> textResults =
convertRawResultsToSearchResults(embeddingsSearch.getAsJsonObject("textResults"));
return new EmbeddingsSearchResults(codeResults, textResults);
} catch (JsonSyntaxException e) {
throw new IOException("GraphQL response is not valid JSON", e);
}
}
}
/**
* Converts raw results from a GraphQL response to a list of EmbeddingsSearchResult objects. This
* works for both code and text results.
*/
private static @NotNull ArrayList<EmbeddingsSearchResult> convertRawResultsToSearchResults(
@Nullable JsonObject rawResults) {
if (rawResults == null) {
return new ArrayList<>();
}
ArrayList<EmbeddingsSearchResult> results = new ArrayList<>();
for (JsonElement result : rawResults.getAsJsonArray()) {
JsonPrimitive repoName = ((JsonObject) result).getAsJsonPrimitive("repoName");
JsonPrimitive revision = ((JsonObject) result).getAsJsonPrimitive("revision");
String fileName = ((JsonObject) result).getAsJsonPrimitive("fileName").getAsString();
int startLine = ((JsonObject) result).getAsJsonPrimitive("startLine").getAsInt();
int endLine = ((JsonObject) result).getAsJsonPrimitive("endLine").getAsInt();
String content = ((JsonObject) result).getAsJsonPrimitive("content").getAsString();
results.add(
new EmbeddingsSearchResult(
repoName != null ? repoName.toString() : null,
revision != null ? revision.toString() : null,
fileName,
startLine,
endLine,
content));
}
return results;
}
/**
* Returns the repository ID if the repository exists and has an embedding, or null otherwise.
*
* @param repoName Like "github.com/sourcegraph/cody"
* @return base64-encoded repoID like "UmVwb3NpdG9yeTozNjgwOTI1MA=="
* @throws IOException Thrown if we can't reach the server.
*/
private static @NotNull String getRepoIdIfEmbeddingExists(String repoName) throws IOException {
String query =
"query Repository($name: String!) {\n"
+ " repository(name: $name) {\n"
+ " id\n"
+ " embeddingExists\n"
+ " }\n"
+ "}";
JsonObject variables = new JsonObject();
variables.add("name", new JsonPrimitive(repoName));
GraphQlResponse response =
GraphQlClient.callGraphQLService("TODO", "TODO", "TODO", query, variables);
if (response.getStatusCode() != 200) {
throw new IOException("GraphQL request failed with status code " + response.getStatusCode());
} else {
try {
JsonObject body = response.getBodyAsJson();
JsonObject data = body.getAsJsonObject("data");
JsonObject repository = data.getAsJsonObject("repository");
if (repository == null) {
throw new IOException("GraphQL response is missing data.repository field");
} else {
try {
JsonObject body = response.getBodyAsJson();
if (body.has("errors")) {
throw new IOException("GraphQL request failed with errors: " + body.get("errors"));
}
JsonObject data = body.getAsJsonObject("data");
if (data == null) {
throw new IOException("GraphQL response is missing data field");
}
JsonObject embeddingsSearch = data.getAsJsonObject("embeddingsSearch");
if (embeddingsSearch == null) {
throw new IOException("GraphQL response is missing data.embeddingsSearch field");
}
ArrayList<EmbeddingsSearchResult> codeResults = convertRawResultsToSearchResults(embeddingsSearch.getAsJsonObject("codeResults"));
ArrayList<EmbeddingsSearchResult> textResults = convertRawResultsToSearchResults(embeddingsSearch.getAsJsonObject("textResults"));
return new EmbeddingsSearchResults(codeResults, textResults);
} catch (JsonSyntaxException e) {
throw new IOException("GraphQL response is not valid JSON", e);
}
}
}
/**
* Converts raw results from a GraphQL response to a list of EmbeddingsSearchResult objects.
* This works for both code and text results.
*/
private static @NotNull ArrayList<EmbeddingsSearchResult> convertRawResultsToSearchResults(@Nullable JsonObject rawResults) {
if (rawResults == null) {
return new ArrayList<>();
}
ArrayList<EmbeddingsSearchResult> results = new ArrayList<>();
for (JsonElement result : rawResults.getAsJsonArray()) {
JsonPrimitive repoName = ((JsonObject) result).getAsJsonPrimitive("repoName");
JsonPrimitive revision = ((JsonObject) result).getAsJsonPrimitive("revision");
String fileName = ((JsonObject) result).getAsJsonPrimitive("fileName").getAsString();
int startLine = ((JsonObject) result).getAsJsonPrimitive("startLine").getAsInt();
int endLine = ((JsonObject) result).getAsJsonPrimitive("endLine").getAsInt();
String content = ((JsonObject) result).getAsJsonPrimitive("content").getAsString();
results.add(new EmbeddingsSearchResult(repoName != null ? repoName.toString() : null, revision != null ? revision.toString() : null,
fileName, startLine, endLine, content));
}
return results;
}
/**
* Returns the repository ID if the repository exists and has an embedding, or null otherwise.
*
* @param repoName Like "github.com/sourcegraph/cody"
* @return base64-encoded repoID like "UmVwb3NpdG9yeTozNjgwOTI1MA=="
* @throws IOException Thrown if we can't reach the server.
*/
private static @NotNull String getRepoIdIfEmbeddingExists(String repoName) throws IOException {
String query = "query Repository($name: String!) {\n" +
" repository(name: $name) {\n" +
" id\n" +
" embeddingExists\n" +
" }\n" +
"}";
JsonObject variables = new JsonObject();
variables.add("name", new JsonPrimitive(repoName));
GraphQlResponse response = GraphQlClient.callGraphQLService("TODO", "TODO", "TODO", query, variables);
if (response.getStatusCode() != 200) {
throw new IOException("GraphQL request failed with status code " + response.getStatusCode());
} else {
try {
JsonObject body = response.getBodyAsJson();
JsonObject data = body.getAsJsonObject("data");
JsonObject repository = data.getAsJsonObject("repository");
if (repository == null) {
throw new IOException("GraphQL response is missing data.repository field");
} else {
boolean embeddingExists = repository.getAsJsonPrimitive("embeddingExists").getAsBoolean();
if (embeddingExists) {
return repository.getAsJsonPrimitive("id").getAsString();
} else {
throw new IOException("Repository does not have an embedding");
}
}
} catch (JsonSyntaxException e) {
throw new IOException("GraphQL response is not valid JSON", e);
}
boolean embeddingExists = repository.getAsJsonPrimitive("embeddingExists").getAsBoolean();
if (embeddingExists) {
return repository.getAsJsonPrimitive("id").getAsString();
} else {
throw new IOException("Repository does not have an embedding");
}
}
} catch (JsonSyntaxException e) {
throw new IOException("GraphQL response is not valid JSON", e);
}
}
}
}

View File

@ -1,24 +1,23 @@
package com.sourcegraph.cody.context.embeddings;
import com.sourcegraph.cody.context.ContextFile;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class GroupedResults {
private final @NotNull ContextFile file;
private final @NotNull List<String> snippets;
private final @NotNull ContextFile file;
private final @NotNull List<String> snippets;
public GroupedResults(@NotNull ContextFile file, @NotNull List<String> snippets) {
this.file = file;
this.snippets = snippets;
}
public GroupedResults(@NotNull ContextFile file, @NotNull List<String> snippets) {
this.file = file;
this.snippets = snippets;
}
public @NotNull ContextFile getFile() {
return file;
}
public @NotNull ContextFile getFile() {
return file;
}
public @NotNull List<String> getSnippets() {
return snippets;
}
public @NotNull List<String> getSnippets() {
return snippets;
}
}

View File

@ -1,64 +1,71 @@
package com.sourcegraph.cody.context.embeddings;
import com.sourcegraph.cody.context.ContextFile;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
public class ResultsGrouper {
public static @NotNull List<GroupedResults> groupResultsByFile(@NotNull List<EmbeddingsSearchResult> results) {
List<ContextFile> originalFileOrder = new ArrayList<>();
for (EmbeddingsSearchResult result : results) {
boolean found = false;
for (ContextFile ogFile : originalFileOrder) {
if (ogFile.getFileName().equals(result.getFileName())) {
found = true;
break;
}
}
if (!found) {
originalFileOrder.add(new ContextFile(result.getFileName(), result.getRepoName(), result.getRevision()));
}
public static @NotNull List<GroupedResults> groupResultsByFile(
@NotNull List<EmbeddingsSearchResult> results) {
List<ContextFile> originalFileOrder = new ArrayList<>();
for (EmbeddingsSearchResult result : results) {
boolean found = false;
for (ContextFile ogFile : originalFileOrder) {
if (ogFile.getFileName().equals(result.getFileName())) {
found = true;
break;
}
Map<String, List<EmbeddingsSearchResult>> resultsGroupedByFile = new HashMap<>();
for (EmbeddingsSearchResult result : results) {
List<EmbeddingsSearchResult> groupedResults = resultsGroupedByFile.get(result.getFileName());
if (groupedResults == null) {
groupedResults = new ArrayList<>();
groupedResults.add(result);
resultsGroupedByFile.put(result.getFileName(), groupedResults);
} else {
groupedResults.add(result);
}
}
return originalFileOrder.stream()
.map(file -> new GroupedResults(file, mergeConsecutiveResults(resultsGroupedByFile.get(file.getFileName()))))
.collect(Collectors.toList());
}
if (!found) {
originalFileOrder.add(
new ContextFile(result.getFileName(), result.getRepoName(), result.getRevision()));
}
}
private static @NotNull List<String> mergeConsecutiveResults(@NotNull List<EmbeddingsSearchResult> results) {
List<EmbeddingsSearchResult> sortedResults = results.stream()
Map<String, List<EmbeddingsSearchResult>> resultsGroupedByFile = new HashMap<>();
for (EmbeddingsSearchResult result : results) {
List<EmbeddingsSearchResult> groupedResults = resultsGroupedByFile.get(result.getFileName());
if (groupedResults == null) {
groupedResults = new ArrayList<>();
groupedResults.add(result);
resultsGroupedByFile.put(result.getFileName(), groupedResults);
} else {
groupedResults.add(result);
}
}
return originalFileOrder.stream()
.map(
file ->
new GroupedResults(
file, mergeConsecutiveResults(resultsGroupedByFile.get(file.getFileName()))))
.collect(Collectors.toList());
}
private static @NotNull List<String> mergeConsecutiveResults(
@NotNull List<EmbeddingsSearchResult> results) {
List<EmbeddingsSearchResult> sortedResults =
results.stream()
.sorted(Comparator.comparingInt(EmbeddingsSearchResult::getStartLine))
.collect(Collectors.toList());
List<String> mergedResults = new ArrayList<>();
mergedResults.add(sortedResults.get(0).getContent());
List<String> mergedResults = new ArrayList<>();
mergedResults.add(sortedResults.get(0).getContent());
for (int i = 1; i < sortedResults.size(); i++) {
EmbeddingsSearchResult result = sortedResults.get(i);
EmbeddingsSearchResult previousResult = sortedResults.get(i - 1);
for (int i = 1; i < sortedResults.size(); i++) {
EmbeddingsSearchResult result = sortedResults.get(i);
EmbeddingsSearchResult previousResult = sortedResults.get(i - 1);
if (result.getStartLine() == previousResult.getEndLine()) {
mergedResults.set(mergedResults.size() - 1, mergedResults.get(mergedResults.size() - 1) + result.getContent());
} else {
mergedResults.add(result.getContent());
}
}
return mergedResults;
if (result.getStartLine() == previousResult.getEndLine()) {
mergedResults.set(
mergedResults.size() - 1,
mergedResults.get(mergedResults.size() - 1) + result.getContent());
} else {
mergedResults.add(result.getContent());
}
}
}
return mergedResults;
}
}

View File

@ -1,60 +1,63 @@
package com.sourcegraph.cody.editor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class EditorContext {
@Nullable
private final String currentFileName;
@Nullable
private final String currentFileContent;
@Nullable
private final String precedingText;
@Nullable
private final String selection;
@Nullable
private final String followingText;
@Nullable private final String currentFileName;
@Nullable private final String currentFileContent;
@Nullable private final String precedingText;
@Nullable private final String selection;
@Nullable private final String followingText;
public EditorContext() {
this(null, null, null, null,null);
}
public EditorContext() {
this(null, null, null, null, null);
}
public EditorContext(@Nullable String currentFileName, @Nullable String currentFileContent, @Nullable String precedingText, @Nullable String selection, @Nullable String followingText) {
this.currentFileName = currentFileName;
this.currentFileContent = currentFileContent;
this.precedingText = precedingText;
this.selection = selection;
this.followingText = followingText;
}
public EditorContext(
@Nullable String currentFileName,
@Nullable String currentFileContent,
@Nullable String precedingText,
@Nullable String selection,
@Nullable String followingText) {
this.currentFileName = currentFileName;
this.currentFileContent = currentFileContent;
this.precedingText = precedingText;
this.selection = selection;
this.followingText = followingText;
}
public @Nullable String getCurrentFileName() {
return currentFileName;
}
public @Nullable String getCurrentFileName() {
return currentFileName;
}
public @Nullable String getCurrentFileExtension() {
return currentFileName != null ? currentFileName.substring(currentFileName.lastIndexOf(".") + 1) : null;
}
public @Nullable String getCurrentFileExtension() {
return currentFileName != null
? currentFileName.substring(currentFileName.lastIndexOf(".") + 1)
: null;
}
public @Nullable String getCurrentFileContent() {
return currentFileContent;
}
public @Nullable String getCurrentFileContent() {
return currentFileContent;
}
public @Nullable String getPrecedingText() {
return precedingText;
}
public @Nullable String getPrecedingText() {
return precedingText;
}
public @Nullable String getSelection() {
return selection;
}
public @Nullable String getSelection() {
return selection;
}
public @Nullable String getFollowingText() {
return followingText;
}
public @Nullable String getFollowingText() {
return followingText;
}
public @NotNull ArrayList<String> getCurrentFileContentAsArrayList() {
return currentFileContent != null ? new ArrayList<>(Collections.singletonList(this.getCurrentFileContent())) : new ArrayList<>();
}
public @NotNull ArrayList<String> getCurrentFileContentAsArrayList() {
return currentFileContent != null
? new ArrayList<>(Collections.singletonList(this.getCurrentFileContent()))
: new ArrayList<>();
}
}

View File

@ -11,38 +11,48 @@ import com.sourcegraph.cody.TruncationUtils;
import org.jetbrains.annotations.NotNull;
public class EditorContextGetter {
@NotNull
public static EditorContext getEditorContext(@NotNull Project project) {
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (editor == null) {
return new EditorContext();
}
@NotNull Document currentDocument = editor.getDocument();
VirtualFile currentFile = FileDocumentManager.getInstance().getFile(currentDocument);
if (currentFile == null) {
return new EditorContext();
}
// Get preceding text
int startLine = Math.max(0, currentDocument.getLineNumber(editor.getSelectionModel().getSelectionStart()) - TruncationUtils.SURROUNDING_LINES);
int precedingTextStartOffset = currentDocument.getLineStartOffset(startLine);
int precedingTextEndOffset = editor.getSelectionModel().getSelectionStart();
String precedingText = currentDocument.getText(new TextRange(precedingTextStartOffset, precedingTextEndOffset));
// Get selection
String selection = editor.getSelectionModel().getSelectedText();
// Get following text
int endLine = Math.min(currentDocument.getLineCount() - 1, currentDocument.getLineNumber(editor.getSelectionModel().getSelectionEnd()) + TruncationUtils.SURROUNDING_LINES);
int followingTextStartOffset = editor.getSelectionModel().getSelectionEnd();
int followingTextEndOffset = currentDocument.getLineEndOffset(endLine);
String followingText = currentDocument.getText(new TextRange(followingTextStartOffset, followingTextEndOffset));
return new EditorContext(
currentFile.getName(),
currentDocument.getText(),
precedingText,
selection == null || selection.isEmpty() ? null : selection,
followingText);
@NotNull
public static EditorContext getEditorContext(@NotNull Project project) {
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (editor == null) {
return new EditorContext();
}
@NotNull Document currentDocument = editor.getDocument();
VirtualFile currentFile = FileDocumentManager.getInstance().getFile(currentDocument);
if (currentFile == null) {
return new EditorContext();
}
// Get preceding text
int startLine =
Math.max(
0,
currentDocument.getLineNumber(editor.getSelectionModel().getSelectionStart())
- TruncationUtils.SURROUNDING_LINES);
int precedingTextStartOffset = currentDocument.getLineStartOffset(startLine);
int precedingTextEndOffset = editor.getSelectionModel().getSelectionStart();
String precedingText =
currentDocument.getText(new TextRange(precedingTextStartOffset, precedingTextEndOffset));
// Get selection
String selection = editor.getSelectionModel().getSelectedText();
// Get following text
int endLine =
Math.min(
currentDocument.getLineCount() - 1,
currentDocument.getLineNumber(editor.getSelectionModel().getSelectionEnd())
+ TruncationUtils.SURROUNDING_LINES);
int followingTextStartOffset = editor.getSelectionModel().getSelectionEnd();
int followingTextEndOffset = currentDocument.getLineEndOffset(endLine);
String followingText =
currentDocument.getText(new TextRange(followingTextStartOffset, followingTextEndOffset));
return new EditorContext(
currentFile.getName(),
currentDocument.getText(),
precedingText,
selection == null || selection.isEmpty() ? null : selection,
followingText);
}
}

View File

@ -1,50 +1,49 @@
package com.sourcegraph.cody.prompts;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class LanguageUtils {
private static final @NotNull Map<String, String> EXTENSION_TO_LANGUAGE;
private static final @NotNull Map<String, String> EXTENSION_TO_LANGUAGE;
static {
EXTENSION_TO_LANGUAGE = new HashMap<>();
EXTENSION_TO_LANGUAGE.put("py", "Python");
EXTENSION_TO_LANGUAGE.put("rb", "Ruby");
EXTENSION_TO_LANGUAGE.put("md", "Markdown");
EXTENSION_TO_LANGUAGE.put("php", "PHP");
EXTENSION_TO_LANGUAGE.put("js", "Javascript");
EXTENSION_TO_LANGUAGE.put("ts", "Typescript");
EXTENSION_TO_LANGUAGE.put("jsx", "JSX");
EXTENSION_TO_LANGUAGE.put("tsx", "TSX");
static {
EXTENSION_TO_LANGUAGE = new HashMap<>();
EXTENSION_TO_LANGUAGE.put("py", "Python");
EXTENSION_TO_LANGUAGE.put("rb", "Ruby");
EXTENSION_TO_LANGUAGE.put("md", "Markdown");
EXTENSION_TO_LANGUAGE.put("php", "PHP");
EXTENSION_TO_LANGUAGE.put("js", "Javascript");
EXTENSION_TO_LANGUAGE.put("ts", "Typescript");
EXTENSION_TO_LANGUAGE.put("jsx", "JSX");
EXTENSION_TO_LANGUAGE.put("tsx", "TSX");
}
public static @NotNull String getNormalizedLanguageName(@Nullable String extension) {
if (extension == null || extension.isEmpty()) {
return "";
}
public static @NotNull String getNormalizedLanguageName(@Nullable String extension) {
if (extension == null || extension.isEmpty()) {
return "";
}
String language = EXTENSION_TO_LANGUAGE.get(extension);
if (language == null) {
return extension.substring(0, 1).toUpperCase() + extension.substring(1);
}
return language;
String language = EXTENSION_TO_LANGUAGE.get(extension);
if (language == null) {
return extension.substring(0, 1).toUpperCase() + extension.substring(1);
}
public static boolean isMarkdownFile(@NotNull String filePath) {
String extension = getExtension(filePath);
return extension.equals("md") || extension.equals("markdown");
}
return language;
}
public static @NotNull String getExtension(@NotNull String filePath) {
int lastDotIndex = filePath.lastIndexOf('.');
if (lastDotIndex == -1) {
return "";
}
return filePath.substring(lastDotIndex + 1);
public static boolean isMarkdownFile(@NotNull String filePath) {
String extension = getExtension(filePath);
return extension.equals("md") || extension.equals("markdown");
}
public static @NotNull String getExtension(@NotNull String filePath) {
int lastDotIndex = filePath.lastIndexOf('.');
if (lastDotIndex == -1) {
return "";
}
return filePath.substring(lastDotIndex + 1);
}
}

View File

@ -2,52 +2,64 @@ package com.sourcegraph.cody.prompts;
import com.sourcegraph.cody.completions.Message;
import com.sourcegraph.cody.completions.Speaker;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
public class Preamble {
private static final String actions = "You are Cody, an AI-powered coding assistant created by Sourcegraph. You work inside a text editor. You have access to my currently open files. You perform the following actions:\n" +
"- Answer general programming questions.\n" +
"- Answer questions about the code that I have provided to you.\n" +
"- Generate code that matches a written description.\n" +
"- Explain what a section of code does.";
private static final String actions =
"You are Cody, an AI-powered coding assistant created by Sourcegraph. You work inside a text editor. You have access to my currently open files. You perform the following actions:\n"
+ "- Answer general programming questions.\n"
+ "- Answer questions about the code that I have provided to you.\n"
+ "- Generate code that matches a written description.\n"
+ "- Explain what a section of code does.";
private static final String rules = "In your responses, obey the following rules:\n" +
"- Be as brief and concise as possible without losing clarity.\n" +
"- All code snippets have to be markdown-formatted, and placed in-between triple backticks like this ```.\n" +
"- Answer questions only if you know the answer or can make a well-informed guess. Otherwise, tell me you don't know and what context I need to provide you for you to answer the question.\n" +
"- Only reference file names or URLs if you are sure they exist.";
private static final String rules =
"In your responses, obey the following rules:\n"
+ "- Be as brief and concise as possible without losing clarity.\n"
+ "- All code snippets have to be markdown-formatted, and placed in-between triple backticks like this ```.\n"
+ "- Answer questions only if you know the answer or can make a well-informed guess. Otherwise, tell me you don't know and what context I need to provide you for you to answer the question.\n"
+ "- Only reference file names or URLs if you are sure they exist.";
private static final String answer = "Understood. I am Cody, an AI assistant made by Sourcegraph to help with programming tasks.\n" +
"I work inside a text editor. I have access to your currently open files in the editor.\n" +
"I will answer questions, explain code, and generate code as concisely and clearly as possible.\n" +
"My responses will be formatted using Markdown syntax for code blocks.\n" +
"I will acknowledge when I don't know an answer or need more context.";
private static final String answer =
"Understood. I am Cody, an AI assistant made by Sourcegraph to help with programming tasks.\n"
+ "I work inside a text editor. I have access to your currently open files in the editor.\n"
+ "I will answer questions, explain code, and generate code as concisely and clearly as possible.\n"
+ "My responses will be formatted using Markdown syntax for code blocks.\n"
+ "I will acknowledge when I don't know an answer or need more context.";
public static List<Message> getPreamble(@Nullable String codebase) {
List<String> preamble = new ArrayList<>();
preamble.add(actions);
preamble.add(rules);
public static List<Message> getPreamble(@Nullable String codebase) {
List<String> preamble = new ArrayList<>();
preamble.add(actions);
preamble.add(rules);
List<String> preambleResponse = new ArrayList<>();
preambleResponse.add(answer);
List<String> preambleResponse = new ArrayList<>();
preambleResponse.add(answer);
// If we have a codebase, add a preamble about it
if (codebase != null) {
String codebasePreamble = "You have access to the `" + codebase + "` repository. You are able to answer questions about the `" + codebase + "` repository. " +
"I will provide the relevant code snippets from the `" + codebase + "` repository when necessary to answer my questions.";
// If we have a codebase, add a preamble about it
if (codebase != null) {
String codebasePreamble =
"You have access to the `"
+ codebase
+ "` repository. You are able to answer questions about the `"
+ codebase
+ "` repository. "
+ "I will provide the relevant code snippets from the `"
+ codebase
+ "` repository when necessary to answer my questions.";
preamble.add(codebasePreamble);
preambleResponse.add("I have access to the `" + codebase + "` repository and can answer questions about its files.");
}
// Return this as a list of two items
List<Message> messages = new ArrayList<>();
messages.add(new Message(Speaker.HUMAN, String.join("\n\n", preamble)));
messages.add(new Message(Speaker.ASSISTANT, String.join("\n", preambleResponse)));
return messages;
preamble.add(codebasePreamble);
preambleResponse.add(
"I have access to the `"
+ codebase
+ "` repository and can answer questions about its files.");
}
// Return this as a list of two items
List<Message> messages = new ArrayList<>();
messages.add(new Message(Speaker.HUMAN, String.join("\n\n", preamble)));
messages.add(new Message(Speaker.ASSISTANT, String.join("\n", preambleResponse)));
return messages;
}
}

View File

@ -4,32 +4,49 @@ import org.jetbrains.annotations.NotNull;
public class Prompter {
private static final @NotNull String CURRENT_EDITOR_CODE_TEMPLATE = "I have the `{filePath}` file opened in my editor. ";
private static final @NotNull String CURRENT_EDITOR_SELECTED_CODE_TEMPLATE = "I am currently looking at this part of the code from `{filePath}`. ";
private static final @NotNull String CODE_CONTEXT_TEMPLATE = "Use following code snippet from file `{filePath}`:\n```{language}\n{text}\n```";
private static final @NotNull String TEXT_CONTEXT_TEMPLATE = "Use the following text from file `{filePath}`:\n{text}";
private static final @NotNull String CURRENT_EDITOR_CODE_TEMPLATE =
"I have the `{filePath}` file opened in my editor. ";
private static final @NotNull String CURRENT_EDITOR_SELECTED_CODE_TEMPLATE =
"I am currently looking at this part of the code from `{filePath}`. ";
private static final @NotNull String CODE_CONTEXT_TEMPLATE =
"Use following code snippet from file `{filePath}`:\n```{language}\n{text}\n```";
private static final @NotNull String TEXT_CONTEXT_TEMPLATE =
"Use the following text from file `{filePath}`:\n{text}";
public static @NotNull String getCurrentEditorCodePrompt(@NotNull String filePath, @NotNull String code) {
String context = LanguageUtils.isMarkdownFile(filePath) ? getTextContextPrompt(filePath, code) : getCodeContextPrompt(filePath, code);
return CURRENT_EDITOR_CODE_TEMPLATE.replace("{filePath}", filePath) + context;
}
public static @NotNull String getCurrentEditorCodePrompt(
@NotNull String filePath, @NotNull String code) {
String context =
LanguageUtils.isMarkdownFile(filePath)
? getTextContextPrompt(filePath, code)
: getCodeContextPrompt(filePath, code);
return CURRENT_EDITOR_CODE_TEMPLATE.replace("{filePath}", filePath) + context;
}
public static @NotNull String getCurrentEditorSelectedCode(@NotNull String filePath, @NotNull String code) {
String context = LanguageUtils.isMarkdownFile(filePath) ? getTextContextPrompt(filePath, code) : getCodeContextPrompt(filePath, code);
return CURRENT_EDITOR_SELECTED_CODE_TEMPLATE.replace("{filePath}", filePath) + context;
}
public static @NotNull String getCurrentEditorSelectedCode(
@NotNull String filePath, @NotNull String code) {
String context =
LanguageUtils.isMarkdownFile(filePath)
? getTextContextPrompt(filePath, code)
: getCodeContextPrompt(filePath, code);
return CURRENT_EDITOR_SELECTED_CODE_TEMPLATE.replace("{filePath}", filePath) + context;
}
public static @NotNull String getContextPrompt(@NotNull String filePath, @NotNull String code) {
return LanguageUtils.isMarkdownFile(filePath) ? getTextContextPrompt(filePath, code) : getCodeContextPrompt(filePath, code);
}
public static @NotNull String getContextPrompt(@NotNull String filePath, @NotNull String code) {
return LanguageUtils.isMarkdownFile(filePath)
? getTextContextPrompt(filePath, code)
: getCodeContextPrompt(filePath, code);
}
public static @NotNull String getCodeContextPrompt(@NotNull String filePath, @NotNull String code) {
return CODE_CONTEXT_TEMPLATE.replace("{filePath}", filePath)
.replace("{language}", LanguageUtils.getNormalizedLanguageName(filePath))
.replace("{text}", code);
}
public static @NotNull String getCodeContextPrompt(
@NotNull String filePath, @NotNull String code) {
return CODE_CONTEXT_TEMPLATE
.replace("{filePath}", filePath)
.replace("{language}", LanguageUtils.getNormalizedLanguageName(filePath))
.replace("{text}", code);
}
public static @NotNull String getTextContextPrompt(@NotNull String filePath, @NotNull String text) {
return TEXT_CONTEXT_TEMPLATE.replace("{filePath}", filePath).replace("{text}", text);
}
public static @NotNull String getTextContextPrompt(
@NotNull String filePath, @NotNull String text) {
return TEXT_CONTEXT_TEMPLATE.replace("{filePath}", filePath).replace("{text}", text);
}
}

View File

@ -7,106 +7,93 @@ import com.sourcegraph.cody.chat.ChatMessage;
import com.sourcegraph.cody.editor.EditorContext;
import com.sourcegraph.cody.editor.EditorContextGetter;
import com.sourcegraph.cody.prompts.LanguageUtils;
import java.util.ArrayList;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public class RecipeRunner {
private final @NotNull Project project;
private final @NotNull UpdatableChat chat;
private final @NotNull Project project;
private final @NotNull UpdatableChat chat;
public RecipeRunner(@NotNull Project project, @NotNull UpdatableChat chat) {
public RecipeRunner(@NotNull Project project, @NotNull UpdatableChat chat) {
this.project = project;
this.chat = chat;
this.project = project;
this.chat = chat;
}
private String getMarkdownFormatPrompt() {
return "Enclose code snippets with three backticks like so: ```.";
}
public void runExplainCodeDetailed() {
EditorContext editorContext = EditorContextGetter.getEditorContext(project);
if (editorContext.getSelection() == null) {
chat.addMessage(
ChatMessage.createAssistantMessage(
"No code selected. Please select some code and try again."));
return;
}
String languageName =
LanguageUtils.getNormalizedLanguageName(editorContext.getCurrentFileExtension());
private String getMarkdownFormatPrompt() {
return "Enclose code snippets with three backticks like so: ```.";
}
String truncatedSelectedText =
TruncationUtils.truncateText(
editorContext.getSelection(), TruncationUtils.MAX_RECIPE_INPUT_TOKENS);
String truncatedPrecedingText =
editorContext.getPrecedingText() != null
? TruncationUtils.truncateTextStart(
editorContext.getPrecedingText(), TruncationUtils.MAX_RECIPE_SURROUNDING_TOKENS)
: "";
String truncatedFollowingText =
editorContext.getFollowingText() != null
? TruncationUtils.truncateText(
editorContext.getFollowingText(), TruncationUtils.MAX_RECIPE_SURROUNDING_TOKENS)
: "";
public void runExplainCodeDetailed() {
EditorContext editorContext = EditorContextGetter.getEditorContext(project);
if (editorContext.getSelection() == null) {
chat.addMessage(ChatMessage.createAssistantMessage("No code selected. Please select some code and try again."));
return;
}
String languageName = LanguageUtils.getNormalizedLanguageName(editorContext.getCurrentFileExtension());
String truncatedSelectedText = TruncationUtils.truncateText(editorContext.getSelection(), TruncationUtils.MAX_RECIPE_INPUT_TOKENS);
String truncatedPrecedingText = editorContext.getPrecedingText() != null ? TruncationUtils.truncateTextStart(editorContext.getPrecedingText(), TruncationUtils.MAX_RECIPE_SURROUNDING_TOKENS) : "";
String truncatedFollowingText = editorContext.getFollowingText() != null ? TruncationUtils.truncateText(editorContext.getFollowingText(), TruncationUtils.MAX_RECIPE_SURROUNDING_TOKENS) : "";
String promptMessage = String.format(
String promptMessage =
String.format(
"Please explain the following %s code. Be very detailed and specific, and indicate when it is not clear to you what is going on. Format your response as an ordered list.\n```\n%s\n```\n%s",
languageName,
truncatedSelectedText,
getMarkdownFormatPrompt()
);
languageName, truncatedSelectedText, getMarkdownFormatPrompt());
String displayText = String.format(
"Explain the following code:\n```\n%s\n```",
editorContext.getSelection()
);
String displayText =
String.format("Explain the following code:\n```\n%s\n```", editorContext.getSelection());
// return new Interaction(
// { speaker: 'human', text: promptMessage, displayText },
// { speaker: 'assistant' },
// getContextMessagesFromSelection(
// truncatedSelectedText,
// truncatedPrecedingText,
// truncatedFollowingText,
// selection.fileName,
// context.codebaseContext
// )
ChatMessage humanMessage = ChatMessage.createHumanMessage(promptMessage, new ArrayList<>());
chat.addMessage(humanMessage);
}
// return new Interaction(
// { speaker: 'human', text: promptMessage, displayText },
// { speaker: 'assistant' },
// getContextMessagesFromSelection(
// truncatedSelectedText,
// truncatedPrecedingText,
// truncatedFollowingText,
// selection.fileName,
// context.codebaseContext
// )
ChatMessage humanMessage = ChatMessage.createHumanMessage(promptMessage, new ArrayList<>());
// private ArrayList<ChatMessage> getContextMessagesFromSelection(EditorContext editorContext)
// {
// return ChatMessage.createHumanMessage(editorContext, new ArrayList<String>());
// }
chat.addMessage(humanMessage);
}
public void runExplainCodeHighLevel() {}
// private ArrayList<ChatMessage> getContextMessagesFromSelection(EditorContext editorContext) {
// return ChatMessage.createHumanMessage(editorContext, new ArrayList<String>());
// }
public void runGenerateUnitTest() {}
public void runExplainCodeHighLevel() {
public void runGenerateDocstring() {}
}
public void runImproveVariableNames() {}
public void runGenerateUnitTest() {
public void runTranslateToLanguage() {}
}
public void runGitHistory() {}
public void runGenerateDocstring() {
public void runFindCodeSmells() {}
}
public void runFixup() {}
public void runImproveVariableNames() {
public void runContextSearch() {}
}
public void runTranslateToLanguage() {
}
public void runGitHistory() {
}
public void runFindCodeSmells() {
}
public void runFixup() {
}
public void runContextSearch() {
}
public void runReleaseNotes() {
}
public void runReleaseNotes() {}
}

View File

@ -5,41 +5,44 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class Event {
final String eventName;
final String anonymousUserId;
final String url;
final JsonObject eventProperties;
/**
* PRIVACY: Do NOT include any potentially private information, such as search queries or repository names.
*/
final JsonObject publicArgument;
final String eventName;
final String anonymousUserId;
final String url;
final JsonObject eventProperties;
public Event(@NotNull String eventName,
@NotNull String anonymousUserId,
@NotNull String url,
@Nullable JsonObject eventProperties,
@Nullable JsonObject publicArgument) {
this.eventName = eventName;
this.anonymousUserId = anonymousUserId;
this.url = url;
this.eventProperties = eventProperties;
this.publicArgument = publicArgument;
}
/**
* PRIVACY: Do NOT include any potentially private information, such as search queries or
* repository names.
*/
final JsonObject publicArgument;
public JsonObject toJson() {
JsonObject returnValue = new JsonObject();
returnValue.addProperty("event", this.eventName);
returnValue.addProperty("userCookieID", this.anonymousUserId);
returnValue.addProperty("url", this.url);
returnValue.addProperty("source", "IDEEXTENSION");
returnValue.addProperty("referrer", "CODY-JETBRAINS");
if (eventProperties != null) {
returnValue.add("argument", eventProperties);
}
if (publicArgument != null) {
returnValue.add("publicArgument", publicArgument);
}
returnValue.addProperty("deviceID", this.anonymousUserId);
return returnValue;
public Event(
@NotNull String eventName,
@NotNull String anonymousUserId,
@NotNull String url,
@Nullable JsonObject eventProperties,
@Nullable JsonObject publicArgument) {
this.eventName = eventName;
this.anonymousUserId = anonymousUserId;
this.url = url;
this.eventProperties = eventProperties;
this.publicArgument = publicArgument;
}
public JsonObject toJson() {
JsonObject returnValue = new JsonObject();
returnValue.addProperty("event", this.eventName);
returnValue.addProperty("userCookieID", this.anonymousUserId);
returnValue.addProperty("url", this.url);
returnValue.addProperty("source", "IDEEXTENSION");
returnValue.addProperty("referrer", "CODY-JETBRAINS");
if (eventProperties != null) {
returnValue.add("argument", eventProperties);
}
if (publicArgument != null) {
returnValue.add("publicArgument", publicArgument);
}
returnValue.addProperty("deviceID", this.anonymousUserId);
return returnValue;
}
}

View File

@ -7,69 +7,91 @@ import com.intellij.openapi.project.Project;
import com.sourcegraph.api.GraphQlClient;
import com.sourcegraph.cody.config.ConfigUtil;
import com.sourcegraph.cody.config.SettingsComponent;
import java.io.IOException;
import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.function.Consumer;
public class GraphQlLogger {
private static final Logger logger = Logger.getInstance(GraphQlLogger.class);
private static final Logger logger = Logger.getInstance(GraphQlLogger.class);
public static void logInstallEvent(@NotNull Project project, @NotNull Consumer<Boolean> callback) {
String anonymousUserId = ConfigUtil.getAnonymousUserId();
if (anonymousUserId != null) {
Event event = new Event("CodyInstalled", anonymousUserId, ConfigUtil.getSourcegraphUrl(project), null, null);
logEvent(project, event, (responseStatusCode) -> callback.accept(responseStatusCode == 200));
}
public static void logInstallEvent(
@NotNull Project project, @NotNull Consumer<Boolean> callback) {
String anonymousUserId = ConfigUtil.getAnonymousUserId();
if (anonymousUserId != null) {
Event event =
new Event(
"CodyInstalled", anonymousUserId, ConfigUtil.getSourcegraphUrl(project), null, null);
logEvent(project, event, (responseStatusCode) -> callback.accept(responseStatusCode == 200));
}
}
public static void logUninstallEvent(@NotNull Project project) {
String anonymousUserId = ConfigUtil.getAnonymousUserId();
if (anonymousUserId != null) {
Event event = new Event("CodyUninstalled", anonymousUserId, ConfigUtil.getSourcegraphUrl(project), null, null);
logEvent(project, event, null);
}
public static void logUninstallEvent(@NotNull Project project) {
String anonymousUserId = ConfigUtil.getAnonymousUserId();
if (anonymousUserId != null) {
Event event =
new Event(
"CodyUninstalled",
anonymousUserId,
ConfigUtil.getSourcegraphUrl(project),
null,
null);
logEvent(project, event, null);
}
}
// TODO: Use this
public static void logSearchDuration(@NotNull Project project, long duration) {
String anonymousUserId = ConfigUtil.getAnonymousUserId();
if (anonymousUserId != null) {
JsonObject durationObject = new JsonObject();
durationObject.addProperty("duration", duration);
Event event = new Event("CodyJetBrainsExtension:keywordContext:searchDuration",
anonymousUserId, ConfigUtil.getSourcegraphUrl(project), durationObject, durationObject);
logEvent(project, event, null);
}
// TODO: Use this
public static void logSearchDuration(@NotNull Project project, long duration) {
String anonymousUserId = ConfigUtil.getAnonymousUserId();
if (anonymousUserId != null) {
JsonObject durationObject = new JsonObject();
durationObject.addProperty("duration", duration);
Event event =
new Event(
"CodyJetBrainsExtension:keywordContext:searchDuration",
anonymousUserId,
ConfigUtil.getSourcegraphUrl(project),
durationObject,
durationObject);
logEvent(project, event, null);
}
}
// This could be exposed later (as public), but currently, we don't use it externally.
private static void logEvent(@NotNull Project project, @NotNull Event event, @Nullable Consumer<Integer> callback) {
String instanceUrl = ConfigUtil.getSourcegraphUrl(project);
String accessToken = ConfigUtil.getInstanceType(project) == SettingsComponent.InstanceType.ENTERPRISE
? ConfigUtil.getEnterpriseAccessToken(project) : ConfigUtil.getDotcomAccessToken(project);
String customRequestHeaders = ConfigUtil.getCustomRequestHeaders(project);
new Thread(() -> {
String query = "mutation LogEvents($events: [Event!]) {" +
" logEvents(events: $events) { " +
" alwaysNil" +
" }" +
"}";
// This could be exposed later (as public), but currently, we don't use it externally.
private static void logEvent(
@NotNull Project project, @NotNull Event event, @Nullable Consumer<Integer> callback) {
String instanceUrl = ConfigUtil.getSourcegraphUrl(project);
String accessToken =
ConfigUtil.getInstanceType(project) == SettingsComponent.InstanceType.ENTERPRISE
? ConfigUtil.getEnterpriseAccessToken(project)
: ConfigUtil.getDotcomAccessToken(project);
String customRequestHeaders = ConfigUtil.getCustomRequestHeaders(project);
new Thread(
() -> {
String query =
"mutation LogEvents($events: [Event!]) {"
+ " logEvents(events: $events) { "
+ " alwaysNil"
+ " }"
+ "}";
JsonArray events = new JsonArray();
events.add(event.toJson());
JsonObject variables = new JsonObject();
variables.add("events", events);
JsonArray events = new JsonArray();
events.add(event.toJson());
JsonObject variables = new JsonObject();
variables.add("events", events);
try {
int responseStatusCode = GraphQlClient.callGraphQLService(instanceUrl, accessToken, customRequestHeaders, query, variables).getStatusCode();
try {
int responseStatusCode =
GraphQlClient.callGraphQLService(
instanceUrl, accessToken, customRequestHeaders, query, variables)
.getStatusCode();
if (callback != null) {
callback.accept(responseStatusCode);
callback.accept(responseStatusCode);
}
} catch (IOException e) {
} catch (IOException e) {
logger.info(e);
}
}).start();
}
}
})
.start();
}
}

View File

@ -7,46 +7,50 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupActivity;
import com.sourcegraph.cody.config.ConfigUtil;
import com.sourcegraph.cody.config.SettingsChangeListener;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class PostStartupActivity implements StartupActivity.DumbAware {
private static String generateAnonymousUserId() {
return UUID.randomUUID().toString();
private static String generateAnonymousUserId() {
return UUID.randomUUID().toString();
}
@Override
public void runActivity(@NotNull Project project) {
// Make sure that SettingsChangeListener is loaded
project.getService(SettingsChangeListener.class);
// When no anonymous user ID is set yet, we create a new one and treat this as an installation
// event.
// This likely means that the user has never started IntelliJ with our extension before
if (ConfigUtil.getAnonymousUserId() == null) {
ConfigUtil.setAnonymousUserId(generateAnonymousUserId());
}
@Override
public void runActivity(@NotNull Project project) {
// Make sure that SettingsChangeListener is loaded
project.getService(SettingsChangeListener.class);
// When no anonymous user ID is set yet, we create a new one and treat this as an installation event.
// This likely means that the user has never started IntelliJ with our extension before
if (ConfigUtil.getAnonymousUserId() == null) {
ConfigUtil.setAnonymousUserId(generateAnonymousUserId());
}
PluginInstaller.addStateListener(new PluginStateListener() {
public void install(@NotNull IdeaPluginDescriptor ideaPluginDescriptor) {
GraphQlLogger.logInstallEvent(project, (wasSuccessful) -> {
if (wasSuccessful) {
ConfigUtil.setInstallEventLogged(true);
}
PluginInstaller.addStateListener(
new PluginStateListener() {
public void install(@NotNull IdeaPluginDescriptor ideaPluginDescriptor) {
GraphQlLogger.logInstallEvent(
project,
(wasSuccessful) -> {
if (wasSuccessful) {
ConfigUtil.setInstallEventLogged(true);
}
});
}
@Override
public void uninstall(@NotNull IdeaPluginDescriptor ideaPluginDescriptor) {
if (ideaPluginDescriptor.getPluginId().getIdString().equals("com.sourcegraph.cody")) {
GraphQlLogger.logUninstallEvent(project);
// Clearing this so that we can detect a new installation if the user re-enables the
// extension.
ConfigUtil.setAnonymousUserId(null);
ConfigUtil.setInstallEventLogged(false);
}
@Override
public void uninstall(@NotNull IdeaPluginDescriptor ideaPluginDescriptor) {
if (ideaPluginDescriptor.getPluginId().getIdString().equals("com.sourcegraph.cody")) {
GraphQlLogger.logUninstallEvent(project);
// Clearing this so that we can detect a new installation if the user re-enables the extension.
ConfigUtil.setAnonymousUserId(null);
ConfigUtil.setInstallEventLogged(false);
}
}
}
});
}
}
}