Initial commit

This commit is contained in:
Carl-Robert Linnupuu 2023-02-13 21:22:01 +00:00
commit 1ef33b85e8
49 changed files with 2379 additions and 0 deletions

42
.gitignore vendored Normal file
View file

@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

8
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

16
.idea/gradle.xml generated Normal file
View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

13
.idea/misc.xml generated Normal file
View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="19" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="ProjectType">
<option name="id" value="jpab" />
</component>
</project>

127
.idea/uiDesigner.xml generated Normal file
View file

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
<component name="uidesigner-configuration">
<option name="INSTRUMENT_CLASSES" value="false" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View file

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Plugin" type="GradleRunConfiguration" factoryName="Gradle">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log"/>
<ExternalSystemSettings>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value="runIde"/>
</list>
</option>
<option name="vmOptions" value=""/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

42
build.gradle.kts Normal file
View file

@ -0,0 +1,42 @@
plugins {
id("java")
id("org.jetbrains.intellij") version "1.5.2"
}
group = "ee.carlrobert"
version = "1.0.1"
repositories {
mavenCentral()
}
// Configure Gradle IntelliJ Plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin
intellij {
version.set("2021.2")
type.set("IC") // Target IDE Platform
plugins.set(listOf(/* Plugin Dependencies */))
}
tasks {
// Set the JVM compatibility versions
withType<JavaCompile> {
sourceCompatibility = "11"
targetCompatibility = "11"
}
patchPluginXml {
sinceBuild.set("212")
untilBuild.set("222.*")
}
signPlugin {
certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
privateKey.set(System.getenv("PRIVATE_KEY"))
password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
}
publishPlugin {
token.set(System.getenv("PUBLISH_TOKEN"))
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

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

234
gradlew vendored Executable file
View file

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View file

@ -0,0 +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

1
settings.gradle.kts Normal file
View file

@ -0,0 +1 @@
rootProject.name = "ChatGPT"

View file

@ -0,0 +1,299 @@
package ee.carlrobert.chatgpt;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
// https://tips4java.wordpress.com/2009/09/27/component-border/
/**
* The ComponentBorder class allows you to place a real component in
* the space reserved for painting the Border of a component.
*
* This class takes advantage of the knowledge that all Swing components are
* also Containers. By default the layout manager is null, so we should be
* able to place a child component anywhere in the parent component. In order
* to prevent the child component from painting over top of the parent
* component a Border is added to the parent componet such that the insets of
* the Border will reserve space for the child component to be painted without
* affecting the parent component.
*/
public class ComponentBorder implements Border
{
public enum Edge
{
TOP,
LEFT,
BOTTOM,
RIGHT;
}
public static final float LEADING = 0.0f;
public static final float CENTER = 0.5f;
public static final float TRAILING = 1.0f;
private JComponent parent;
private JComponent component;
private Edge edge;
private float alignment;
private int gap = 5;
private boolean adjustInsets = true;
private Insets borderInsets = new Insets(0, 0, 0, 0);
/**
* Convenience constructor that uses the default edge (Edge.RIGHT) and
* alignment (CENTER).
*
* @param component the component to be added in the Border area
*/
public ComponentBorder(JComponent component)
{
this(component, Edge.RIGHT);
}
/**
* Convenience constructor that uses the default alignment (CENTER).
*
* @param component the component to be added in the Border area
* @param edge a valid Edge enum of TOP, LEFT, BOTTOM, RIGHT
*/
public ComponentBorder(JComponent component, Edge edge)
{
this(component, edge, CENTER);
}
/**
* Main constructor to create a ComponentBorder.
*
* @param component the component to be added in the Border area
* @param edge a valid Edge enum of TOP, LEFT, BOTTOM, RIGHT
* @param alignment the alignment of the component along the
* specified Edge. Must be in the range 0 - 1.0.
*/
public ComponentBorder(JComponent component, Edge edge, float alignment )
{
this.component = component;
component.setSize( component.getPreferredSize() );
component.setCursor(Cursor.getDefaultCursor());
setEdge( edge );
setAlignment( alignment );
}
public boolean isAdjustInsets()
{
return adjustInsets;
}
public void setAdjustInsets(boolean adjustInsets)
{
this.adjustInsets = adjustInsets;
}
/**
* Get the component alignment along the Border Edge
*
* @return the alignment
*/
public float getAlignment()
{
return alignment;
}
/**
* Set the component alignment along the Border Edge
*
* @param alignment a value in the range 0 - 1.0. Standard values would be
* CENTER (default), LEFT and RIGHT.
*/
public void setAlignment(float alignment)
{
this.alignment = alignment > 1.0f ? 1.0f : alignment < 0.0f ? 0.0f : alignment;
}
/**
* Get the Edge the component is positioned along
*
* @return the Edge
*/
public Edge getEdge()
{
return edge;
}
/**
* Set the Edge the component is positioned along
*
* @param edge the Edge the component is position on.
*/
public void setEdge(Edge edge)
{
this.edge = edge;
}
/**
* Get the gap between the border component and the parent component
*
* @return the gap in pixels.
*/
public int getGap()
{
return gap;
}
/**
* Set the gap between the border component and the parent component
*
* @param gap the gap in pixels (default is 5)
*/
public void setGap(int gap)
{
this.gap = gap;
}
//
// Implement the Border interface
//
public Insets getBorderInsets(Component c)
{
return borderInsets;
}
public boolean isBorderOpaque()
{
return false;
}
/**
* In this case a real component is to be painted. Setting the location
* of the component will cause it to be painted at that location.
*/
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
float x2 = (width - component.getWidth()) * component.getAlignmentX() + x;
float y2 = (height - component.getHeight()) * component.getAlignmentY() + y;
component.setLocation((int)x2, (int)y2);
}
/*
* Install this Border on the specified component by replacing the
* existing Border with a CompoundBorder containing the original Border
* and our ComponentBorder
*
* This method should only be invoked once all the properties of this
* class have been set. Installing the Border more than once will cause
* unpredictable results.
*/
public void install(JComponent parent)
{
this.parent = parent;
determineInsetsAndAlignment();
// Add this Border to the parent
Border current = parent.getBorder();
if (current == null)
{
parent.setBorder(this);
}
else
{
CompoundBorder compound = new CompoundBorder(current, this);
parent.setBorder(compound);
}
// Add component to the parent
parent.add(component);
}
/**
* The insets need to be determined so they are included in the preferred
* size of the component the Border is attached to.
*
* The alignment of the component is determined here so it doesn't need
* to be recalculated every time the Border is painted.
*/
private void determineInsetsAndAlignment()
{
borderInsets = new Insets(0, 0, 0, 0);
// The insets will only be updated for the edge the component will be
// diplayed on.
//
// The X, Y alignment of the component is controlled by both the edge
// and alignment parameters
if (edge == Edge.TOP)
{
borderInsets.top = component.getPreferredSize().height + gap;
component.setAlignmentX(alignment);
component.setAlignmentY(0.0f);
}
else if (edge == Edge.BOTTOM)
{
borderInsets.bottom = component.getPreferredSize().height + gap;
component.setAlignmentX(alignment);
component.setAlignmentY(1.0f);
}
else if (edge == Edge.LEFT)
{
borderInsets.left = component.getPreferredSize().width + gap;
component.setAlignmentX(0.0f);
component.setAlignmentY(alignment);
}
else if (edge == Edge.RIGHT)
{
borderInsets.right = component.getPreferredSize().width + gap;
component.setAlignmentX(1.0f);
component.setAlignmentY(alignment);
}
if (adjustInsets)
adjustBorderInsets();
}
/*
* The complimentary edges of the Border may need to be adjusted to allow
* the component to fit completely in the bounds of the parent component.
*/
private void adjustBorderInsets()
{
Insets parentInsets = parent.getInsets();
// May need to adust the height of the parent component to fit
// the component in the Border
if (edge == Edge.RIGHT || edge == Edge.LEFT)
{
int parentHeight = parent.getPreferredSize().height - parentInsets.top - parentInsets.bottom;
int diff = component.getHeight() - parentHeight;
if (diff > 0)
{
int topDiff = (int)(diff * alignment);
int bottomDiff = diff - topDiff;
borderInsets.top += topDiff;
borderInsets.bottom += bottomDiff;
}
}
// May need to adust the width of the parent component to fit
// the component in the Border
if (edge == Edge.TOP || edge == Edge.BOTTOM)
{
int parentWidth = parent.getPreferredSize().width - parentInsets.left - parentInsets.right;
int diff = component.getWidth() - parentWidth;
if (diff > 0)
{
int leftDiff = (int)(diff * alignment);
int rightDiff = diff - leftDiff;
borderInsets.left += leftDiff;
borderInsets.right += rightDiff;
}
}
}
}

View file

@ -0,0 +1,56 @@
package ee.carlrobert.chatgpt;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.UI;
import java.awt.Desktop;
import java.io.IOException;
import java.net.URISyntaxException;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.event.HyperlinkEvent;
import org.jetbrains.annotations.NotNull;
public class SettingsComponent {
private final JPanel myMainPanel;
private final JBTextField apiKeyField = new JBTextField();
public SettingsComponent() {
myMainPanel = FormBuilder.createFormBuilder()
.addComponent(UI.PanelFactory.panel(apiKeyField)
.withLabel("API key:")
.withComment("You can find your Secret API key in your <a href=\"https://platform.openai.com/account/api-keys\">User settings</a>.")
.withCommentHyperlinkListener(event -> {
if (HyperlinkEvent.EventType.ACTIVATED.equals(event.getEventType())) {
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
try {
Desktop.getDesktop().browse(event.getURL().toURI());
} catch (IOException | URISyntaxException e) {
throw new RuntimeException("Couldn't open the browser.", e);
}
}
}
})
.createPanel())
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
public JPanel getPanel() {
return myMainPanel;
}
public JComponent getPreferredFocusedComponent() {
return apiKeyField;
}
@NotNull
public String getApiKeyField() {
return apiKeyField.getText();
}
public void setApiKeyField(@NotNull String apiKey) {
apiKeyField.setText(apiKey);
}
}

View file

@ -0,0 +1,53 @@
package ee.carlrobert.chatgpt;
import com.intellij.openapi.options.Configurable;
import javax.swing.JComponent;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.Nullable;
public class SettingsConfigurable implements Configurable {
private SettingsComponent settingsComponent;
@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return "ChatGPT: Settings";
}
@Override
public JComponent getPreferredFocusedComponent() {
return settingsComponent.getPreferredFocusedComponent();
}
@Nullable
@Override
public JComponent createComponent() {
settingsComponent = new SettingsComponent();
return settingsComponent.getPanel();
}
@Override
public boolean isModified() {
SettingsState settings = SettingsState.getInstance();
return !settingsComponent.getApiKeyField().equals(settings.secretKey);
}
@Override
public void apply() {
SettingsState settings = SettingsState.getInstance();
settings.secretKey = settingsComponent.getApiKeyField();
}
@Override
public void reset() {
SettingsState settings = SettingsState.getInstance();
settingsComponent.setApiKeyField(settings.secretKey);
}
@Override
public void disposeUIResources() {
settingsComponent = null;
}
}

View file

@ -0,0 +1,33 @@
package ee.carlrobert.chatgpt;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@State(
name = "com.example.chatgpt.SettingsState",
storages = @Storage("SdkSettingsPlugin.xml")
)
public class SettingsState implements PersistentStateComponent<SettingsState> {
public String secretKey = "";
public static SettingsState getInstance() {
return ApplicationManager.getApplication().getService(SettingsState.class);
}
@Nullable
@Override
public SettingsState getState() {
return this;
}
@Override
public void loadState(@NotNull SettingsState state) {
XmlSerializerUtil.copyBean(state, this);
}
}

View file

@ -0,0 +1,34 @@
package ee.carlrobert.chatgpt;
import com.intellij.notification.NotificationGroupManager;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupActivity;
import org.jetbrains.annotations.NotNull;
public class ShowNotificationActivity implements StartupActivity {
@Override
public void runActivity(@NotNull Project project) {
var secretKey = SettingsState.getInstance().secretKey;
if (secretKey == null || secretKey.isEmpty()) {
NotificationGroupManager.getInstance()
.getNotificationGroup("ChatGPT-Empty-API-Key")
.createNotification("ChatGPT API key not set", NotificationType.WARNING)
.addAction(new AnAction("Open Settings") {
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
DataContext dataContext = event.getDataContext();
Project project = PlatformDataKeys.PROJECT.getData(dataContext);
ShowSettingsUtil.getInstance().showSettingsDialog(project, SettingsConfigurable.class);
}
})
.notify(project);
}
}
}

View file

@ -0,0 +1,25 @@
package ee.carlrobert.chatgpt.action;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import ee.carlrobert.chatgpt.SettingsState;
import icons.SdkIcons;
public class ActionGroup extends DefaultActionGroup {
@Override
public void update(AnActionEvent event) {
Editor editor = event.getData(PlatformDataKeys.EDITOR);
event.getPresentation().setIcon(SdkIcons.Sdk_default_icon);
Project project = event.getProject();
boolean menuAllowed = false;
if (editor != null && project != null) {
var secretKey = SettingsState.getInstance().secretKey;
menuAllowed = secretKey != null && !secretKey.isEmpty() && editor.getSelectionModel().getSelectedText() != null;
}
event.getPresentation().setEnabled(menuAllowed);
}
}

View file

@ -0,0 +1,33 @@
package ee.carlrobert.chatgpt.action;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.wm.ToolWindowManager;
import ee.carlrobert.chatgpt.SettingsState;
import ee.carlrobert.chatgpt.client.ApiClient;
import ee.carlrobert.chatgpt.service.ToolWindowService;
import org.jetbrains.annotations.NotNull;
public class AskAction extends AnAction {
@Override
public void update(@NotNull AnActionEvent event) {
var secretKey = SettingsState.getInstance().secretKey;
event.getPresentation().setEnabled(event.getProject() != null && secretKey != null && !secretKey.isEmpty());
}
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
var project = event.getProject();
if (project != null) {
var toolWindow = ToolWindowManager.getInstance(project).getToolWindow("ChatGPT");
if (toolWindow != null) {
toolWindow.show();
var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class);
ApiClient.getInstance().clearQueries();
toolWindowService.getScrollablePanel().removeAll();
}
}
}
}

View file

@ -0,0 +1,37 @@
package ee.carlrobert.chatgpt.action;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import ee.carlrobert.chatgpt.client.ApiClient;
import ee.carlrobert.chatgpt.service.ToolWindowService;
import org.jetbrains.annotations.NotNull;
public abstract class BaseAction extends AnAction {
protected abstract String getPrompt(String selectedText);
protected abstract void initToolWindow(ToolWindow toolWindow);
public void actionPerformed(@NotNull AnActionEvent event) {
var project = event.getProject();
var editor = event.getData(PlatformDataKeys.EDITOR);
if (editor != null && project != null) {
initToolWindow(ToolWindowManager.getInstance(project).getToolWindow("ChatGPT"));
var selectedText = editor.getSelectionModel().getSelectedText();
var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class);
var scrollablePanel = toolWindowService.getScrollablePanel();
ApiClient.getInstance().clearQueries();
scrollablePanel.removeAll();
toolWindowService.sendMessage(selectedText, getPrompt(selectedText));
}
}
public void update(AnActionEvent e) {
e.getPresentation().setEnabledAndVisible(e.getProject() != null);
}
}

View file

@ -0,0 +1,15 @@
package ee.carlrobert.chatgpt.action;
import com.intellij.openapi.wm.ToolWindow;
public class ExplainAction extends BaseAction {
protected String getPrompt(String selectedText) {
return "Explain code:\n" + selectedText;
}
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Explain Code");
toolWindow.show();
}
}

View file

@ -0,0 +1,15 @@
package ee.carlrobert.chatgpt.action;
import com.intellij.openapi.wm.ToolWindow;
public class FindBugsAction extends BaseAction {
protected String getPrompt(String selectedText) {
return "Find bugs in the code:\n" + selectedText;
}
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Find Bugs");
toolWindow.show();
}
}

View file

@ -0,0 +1,15 @@
package ee.carlrobert.chatgpt.action;
import com.intellij.openapi.wm.ToolWindow;
public class OptimizeAction extends BaseAction {
protected String getPrompt(String selectedText) {
return "Optimize code:\n" + selectedText;
}
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Optimize Code");
toolWindow.show();
}
}

View file

@ -0,0 +1,15 @@
package ee.carlrobert.chatgpt.action;
import com.intellij.openapi.wm.ToolWindow;
public class RefactorAction extends BaseAction {
protected String getPrompt(String selectedText) {
return "Refactor code:\n" + selectedText;
}
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Refactor Code");
toolWindow.show();
}
}

View file

@ -0,0 +1,15 @@
package ee.carlrobert.chatgpt.action;
import com.intellij.openapi.wm.ToolWindow;
public class WriteTestsAction extends BaseAction {
protected String getPrompt(String selectedText) {
return "Generate unit test for the code:\n" + selectedText;
}
protected void initToolWindow(ToolWindow toolWindow) {
toolWindow.setTitle("Write Tests");
toolWindow.show();
}
}

View file

@ -0,0 +1,95 @@
package ee.carlrobert.chatgpt.client;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import ee.carlrobert.chatgpt.SettingsState;
import ee.carlrobert.chatgpt.client.response.ApiError;
import ee.carlrobert.chatgpt.client.response.ApiResponse;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public final class ApiClient {
private static List<Map.Entry<String, String>> queries = new ArrayList<>();
private static ApiClient instance;
private final ObjectMapper objectMapper = new ObjectMapper();
private final HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();
private ApiClient() {
}
public void getCompletionsAsync(String prompt, Consumer<ApiResponse> onSuccess, Consumer<ApiError> onError) {
/*var query = new StringBuilder(
"You are ChatGPT, a large language model trained by OpenAI. You answer as concisely as possible for each response (e.g. dont be verbose). It is very important that you answer as concisely as possible, so please remember this.\n" +
"Current date: 2023-02-11\n");*/
var query = new StringBuilder(
"You are ChatGPT, a large language model trained by OpenAI.\n");
for (var entry : queries) {
query.append("User:\n")
.append(entry.getKey())
.append("<|im_end|>\n")
.append("\n")
.append("ChatGPT:\n")
.append(entry.getValue())
.append("<|im_end|>\n")
.append("\n");
}
query.append("User:\n")
.append(prompt)
.append("<|im_end|>\n")
.append("\n")
.append("ChatGPT:\n");
try {
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.openai.com/v1/completions"))
.header("Authorization", "Bearer " + SettingsState.getInstance().secretKey)
.timeout(Duration.ofMinutes(1))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(Map.of(
"model", "text-davinci-003",
"stop", List.of("<|im_end|>"),
"prompt", query.toString(),
"max_tokens", 400,
"temperature", 1.0
))))
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenAccept(response -> {
try {
var mappedResponse = objectMapper.readValue(response.body(), ApiResponse.class);
if (mappedResponse.getError() == null) {
queries.add(Map.entry(prompt, mappedResponse.getChoices().get(0).getText()));
onSuccess.accept(mappedResponse);
} else {
onError.accept(mappedResponse.getError());
}
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static ApiClient getInstance() {
if (instance == null) {
instance = new ApiClient();
}
return instance;
}
public void clearQueries() {
queries.clear();
}
}

View file

@ -0,0 +1,25 @@
package ee.carlrobert.chatgpt.client.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ApiError {
private String message;
private String type;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View file

@ -0,0 +1,70 @@
package ee.carlrobert.chatgpt.client.response;
import java.util.List;
public class ApiResponse {
private String id;
private String object;
private long created;
private String model;
private List<ApiResponseChoice> choices;
private ApiResponseUsage usage;
private ApiError error;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getObject() {
return object;
}
public void setObject(String object) {
this.object = object;
}
public long getCreated() {
return created;
}
public void setCreated(long created) {
this.created = created;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public List<ApiResponseChoice> getChoices() {
return choices;
}
public void setChoices(List<ApiResponseChoice> choices) {
this.choices = choices;
}
public ApiResponseUsage getUsage() {
return usage;
}
public void setUsage(ApiResponseUsage usage) {
this.usage = usage;
}
public ApiError getError() {
return error;
}
public void setError(ApiError error) {
this.error = error;
}
}

View file

@ -0,0 +1,41 @@
package ee.carlrobert.chatgpt.client.response;
public class ApiResponseChoice {
private String text;
private int index;
private Object logprobs;
private String finish_reason;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public Object getLogprobs() {
return logprobs;
}
public void setLogprobs(Object logprobs) {
this.logprobs = logprobs;
}
public String getFinish_reason() {
return finish_reason;
}
public void setFinish_reason(String finish_reason) {
this.finish_reason = finish_reason;
}
}

View file

@ -0,0 +1,32 @@
package ee.carlrobert.chatgpt.client.response;
public class ApiResponseUsage {
private int prompt_tokens;
private int completion_tokens;
private int total_tokens;
public int getPrompt_tokens() {
return prompt_tokens;
}
public void setPrompt_tokens(int prompt_tokens) {
this.prompt_tokens = prompt_tokens;
}
public int getCompletion_tokens() {
return completion_tokens;
}
public void setCompletion_tokens(int completion_tokens) {
this.completion_tokens = completion_tokens;
}
public int getTotal_tokens() {
return total_tokens;
}
public void setTotal_tokens(int total_tokens) {
this.total_tokens = total_tokens;
}
}

View file

@ -0,0 +1,6 @@
package ee.carlrobert.chatgpt.service;
@FunctionalInterface
public interface SuccessCallback {
void call();
}

View file

@ -0,0 +1,59 @@
package ee.carlrobert.chatgpt.service;
import static ee.carlrobert.chatgpt.toolwindow.ToolWindowUtil.createIconLabel;
import static ee.carlrobert.chatgpt.toolwindow.ToolWindowUtil.createTextArea;
import static ee.carlrobert.chatgpt.toolwindow.ToolWindowUtil.justifyLeft;
import ee.carlrobert.chatgpt.client.ApiClient;
import ee.carlrobert.chatgpt.toolwindow.Loader;
import ee.carlrobert.chatgpt.toolwindow.ScrollablePanel;
import java.util.Objects;
import javax.annotation.Nullable;
import javax.swing.Box;
import javax.swing.JScrollPane;
public class ToolWindowService {
private ScrollablePanel scrollablePanel;
public void setScrollablePanel(ScrollablePanel scrollablePanel) {
this.scrollablePanel = scrollablePanel;
}
public ScrollablePanel getScrollablePanel() {
return scrollablePanel;
}
public void sendMessage(String userMessage, String prompt) {
sendMessage(userMessage, prompt, null);
}
public void sendMessage(String userMessage, String prompt, @Nullable SuccessCallback onSuccess) {
scrollablePanel.add(createTextArea(userMessage, true, false));
scrollablePanel.add(Box.createVerticalStrut(16));
scrollablePanel.add(justifyLeft(createIconLabel(Objects.requireNonNull(getClass().getResource("/icons/chatgpt-icon.png")))));
var loader = new Loader();
scrollablePanel.add(justifyLeft(loader.getComponent()));
loader.startLoading();
scrollablePanel.add(Box.createVerticalStrut(4));
ApiClient.getInstance().getCompletionsAsync(prompt, response -> {
loader.stopLoading();
scrollablePanel.add(Box.createVerticalStrut(4));
for (var choice : response.getChoices()) {
scrollablePanel.add(createTextArea(choice.getText().trim(), false, true));
}
scrollablePanel.add(Box.createVerticalStrut(32));
if (onSuccess != null) {
onSuccess.call();
}
}, apiError -> {
loader.stopLoading();
scrollablePanel.add(Box.createVerticalStrut(4));
scrollablePanel.add(createTextArea(apiError.getMessage(), false, true));
scrollablePanel.add(Box.createVerticalStrut(32));
});
}
}

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="ee.carlrobert.chatgpt.toolwindow.ChatGptToolWindow">
<grid id="27dc6" binding="chatGptToolWindowContent" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="8" left="8" bottom="8" right="8"/>
<constraints>
<xy x="20" y="20" width="530" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="a7ad1" class="javax.swing.JTextField" binding="textField" custom-create="true">
<constraints>
<grid row="1" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="36"/>
</grid>
</constraints>
<properties>
<text value="Ask a question..."/>
</properties>
</component>
<scrollpane id="8c1de" binding="scrollPane" custom-create="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="3" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children/>
</scrollpane>
</children>
</grid>
</form>

View file

@ -0,0 +1,99 @@
package ee.carlrobert.chatgpt.toolwindow;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.ui.components.JBScrollPane;
import ee.carlrobert.chatgpt.service.ToolWindowService;
import java.awt.Adjustable;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
public class ChatGptToolWindow {
private JPanel chatGptToolWindowContent;
private JTextField textField;
private JScrollPane scrollPane;
public ChatGptToolWindow() {
// TODO: Get rid of
var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class);
ScrollablePanel scrollablePanel = new ScrollablePanel();
toolWindowService.setScrollablePanel(scrollablePanel);
this.refreshView();
}
public void handleSubmit() {
var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class);
var searchText = textField.getText();
toolWindowService.sendMessage(searchText, searchText, () -> {
scrollToBottom(scrollPane);
});
textField.setText("");
scrollToBottom(scrollPane);
}
// TODO: Get rid of
public void refreshView() {
ScrollablePanel scrollablePanel = new ScrollablePanel();
scrollablePanel.setLayout(new BoxLayout(scrollablePanel, BoxLayout.Y_AXIS));
scrollablePanel.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
var toolWindowService = ApplicationManager.getApplication().getService(ToolWindowService.class);
toolWindowService.setScrollablePanel(scrollablePanel);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setViewportView(scrollablePanel);
// TODO: Move to TextField class
textField.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == 10) {
handleSubmit();
}
}
});
}
public JPanel getContent() {
return chatGptToolWindowContent;
}
private void scrollToBottom(JScrollPane scrollPane) {
// TODO: this.scrollPanel.getVerticalScrollBar();
JScrollBar verticalBar = scrollPane.getVerticalScrollBar();
AdjustmentListener downScroller = new AdjustmentListener() {
@Override
public void adjustmentValueChanged(AdjustmentEvent e) {
Adjustable adjustable = e.getAdjustable();
adjustable.setValue(adjustable.getMaximum());
verticalBar.removeAdjustmentListener(this);
}
};
verticalBar.addAdjustmentListener(downScroller);
}
private void createUIComponents() {
textField = new TextField(e -> handleSubmit());
scrollPane = new JBScrollPane();
scrollPane.setBorder(null);
scrollPane.setViewportBorder(null);
}
}

View file

@ -0,0 +1,19 @@
package ee.carlrobert.chatgpt.toolwindow;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.content.ContentFactory;
import org.jetbrains.annotations.NotNull;
public class ChatGptToolWindowFactory implements ToolWindowFactory, DumbAware {
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
var content = ApplicationManager.getApplication()
.getService(ContentFactory.class)
.createContent(new ChatGptToolWindow().getContent(), "", false);
toolWindow.getContentManager().addContent(content);
}
}

View file

@ -0,0 +1,44 @@
package ee.carlrobert.chatgpt.toolwindow;
import com.intellij.util.ui.JBUI;
import java.awt.Font;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JLabel;
public class Loader {
private final Timer timer;
private final JLabel component;
public Loader() {
this.timer = new Timer("Loader");
component = new JLabel(" ");
component.setFont(new Font("Tahoma", Font.BOLD, 24));
component.setBorder(JBUI.Borders.emptyLeft(2));
}
public void startLoading() {
timer.schedule(new TimerTask() {
public void run() {
var loadingText = component.getText();
if ("...".equals(loadingText)) {
component.setText(" ");
} else if (" ".equals(loadingText)) {
component.setText(".");
} else {
component.setText(loadingText + ".");
}
}
}, 500L, 500);
}
public void stopLoading() {
timer.cancel();
component.setVisible(false);
}
public JLabel getComponent() {
return component;
}
}

View file

@ -0,0 +1,327 @@
package ee.carlrobert.chatgpt.toolwindow;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import javax.swing.JPanel;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
/**
* A panel that implements the Scrollable interface. This class allows you
* to customize the scrollable features by using newly provided setter methods
* so you don't have to extend this class every time.
* <p>
* Scrollable amounts can be specifed as a percentage of the viewport size or
* as an actual pixel value. The amount can be changed for both unit and block
* scrolling for both horizontal and vertical scrollbars.
* <p>
* The Scrollable interface only provides a boolean value for determining whether
* or not the viewport size (width or height) should be used by the scrollpane
* when determining if scrollbars should be made visible. This class supports the
* concept of dynamically changing this value based on the size of the viewport.
* In this case the viewport size will only be used when it is larger than the
* panels size. This has the effect of ensuring the viewport is always full as
* components added to the panel will be size to fill the area available,
* based on the rules of the applicable layout manager of course.
*/
public class ScrollablePanel extends JPanel
implements Scrollable, SwingConstants {
public enum ScrollableSizeHint {
NONE,
FIT,
STRETCH;
}
public enum IncrementType {
PERCENT,
PIXELS;
}
private ScrollableSizeHint scrollableHeight = ScrollableSizeHint.NONE;
private ScrollableSizeHint scrollableWidth = ScrollableSizeHint.NONE;
private IncrementInfo horizontalBlock;
private IncrementInfo horizontalUnit;
private IncrementInfo verticalBlock;
private IncrementInfo verticalUnit;
/**
* Default constructor that uses a FlowLayout
*/
public ScrollablePanel() {
this(new FlowLayout());
}
/**
* Constuctor for specifying the LayoutManager of the panel.
*
* @param layout the LayountManger for the panel
*/
public ScrollablePanel(LayoutManager layout) {
super(layout);
IncrementInfo block = new IncrementInfo(IncrementType.PERCENT, 100);
IncrementInfo unit = new IncrementInfo(IncrementType.PERCENT, 10);
setScrollableBlockIncrement(HORIZONTAL, block);
setScrollableBlockIncrement(VERTICAL, block);
setScrollableUnitIncrement(HORIZONTAL, unit);
setScrollableUnitIncrement(VERTICAL, unit);
}
/**
* Get the height ScrollableSizeHint enum
*
* @return the ScrollableSizeHint enum for the height
*/
public ScrollableSizeHint getScrollableHeight() {
return scrollableHeight;
}
/**
* Set the ScrollableSizeHint enum for the height. The enum is used to
* determine the boolean value that is returned by the
* getScrollableTracksViewportHeight() method. The valid values are:
* <p>
* ScrollableSizeHint.NONE - return "false", which causes the height
* of the panel to be used when laying out the children
* ScrollableSizeHint.FIT - return "true", which causes the height of
* the viewport to be used when laying out the children
* ScrollableSizeHint.STRETCH - return "true" when the viewport height
* is greater than the height of the panel, "false" otherwise.
*
* @param scrollableHeight as represented by the ScrollableSizeHint enum.
*/
public void setScrollableHeight(ScrollableSizeHint scrollableHeight) {
this.scrollableHeight = scrollableHeight;
revalidate();
}
/**
* Get the width ScrollableSizeHint enum
*
* @return the ScrollableSizeHint enum for the width
*/
public ScrollableSizeHint getScrollableWidth() {
return scrollableWidth;
}
/**
* Set the ScrollableSizeHint enum for the width. The enum is used to
* determine the boolean value that is returned by the
* getScrollableTracksViewportWidth() method. The valid values are:
* <p>
* ScrollableSizeHint.NONE - return "false", which causes the width
* of the panel to be used when laying out the children
* ScrollableSizeHint.FIT - return "true", which causes the width of
* the viewport to be used when laying out the children
* ScrollableSizeHint.STRETCH - return "true" when the viewport width
* is greater than the width of the panel, "false" otherwise.
*
* @param scrollableWidth as represented by the ScrollableSizeHint enum.
*/
public void setScrollableWidth(ScrollableSizeHint scrollableWidth) {
this.scrollableWidth = scrollableWidth;
revalidate();
}
/**
* Get the block IncrementInfo for the specified orientation
*
* @return the block IncrementInfo for the specified orientation
*/
public IncrementInfo getScrollableBlockIncrement(int orientation) {
return orientation == SwingConstants.HORIZONTAL ? horizontalBlock : verticalBlock;
}
/**
* Specify the information needed to do block scrolling.
*
* @param orientation specify the scrolling orientation. Must be either:
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
* @param amount a value used with the IncrementType to determine the
* scrollable amount
* @paran type specify how the amount parameter in the calculation of
* the scrollable amount. Valid values are:
* IncrementType.PERCENT - treat the amount as a % of the viewport size
* IncrementType.PIXEL - treat the amount as the scrollable amount
*/
public void setScrollableBlockIncrement(int orientation, IncrementType type, int amount) {
IncrementInfo info = new IncrementInfo(type, amount);
setScrollableBlockIncrement(orientation, info);
}
/**
* Specify the information needed to do block scrolling.
*
* @param orientation specify the scrolling orientation. Must be either:
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
* @param info An IncrementInfo object containing information of how to
* calculate the scrollable amount.
*/
public void setScrollableBlockIncrement(int orientation, IncrementInfo info) {
switch (orientation) {
case SwingConstants.HORIZONTAL:
horizontalBlock = info;
break;
case SwingConstants.VERTICAL:
verticalBlock = info;
break;
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
/**
* Get the unit IncrementInfo for the specified orientation
*
* @return the unit IncrementInfo for the specified orientation
*/
public IncrementInfo getScrollableUnitIncrement(int orientation) {
return orientation == SwingConstants.HORIZONTAL ? horizontalUnit : verticalUnit;
}
/**
* Specify the information needed to do unit scrolling.
*
* @param orientation specify the scrolling orientation. Must be either:
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
* @param amount a value used with the IncrementType to determine the
* scrollable amount
* @paran type specify how the amount parameter in the calculation of
* the scrollable amount. Valid values are:
* IncrementType.PERCENT - treat the amount as a % of the viewport size
* IncrementType.PIXEL - treat the amount as the scrollable amount
*/
public void setScrollableUnitIncrement(int orientation, IncrementType type, int amount) {
IncrementInfo info = new IncrementInfo(type, amount);
setScrollableUnitIncrement(orientation, info);
}
/**
* Specify the information needed to do unit scrolling.
*
* @param orientation specify the scrolling orientation. Must be either:
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
* @param info An IncrementInfo object containing information of how to
* calculate the scrollable amount.
*/
public void setScrollableUnitIncrement(int orientation, IncrementInfo info) {
switch (orientation) {
case SwingConstants.HORIZONTAL:
horizontalUnit = info;
break;
case SwingConstants.VERTICAL:
verticalUnit = info;
break;
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
// Implement Scrollable interface
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
public int getScrollableUnitIncrement(
Rectangle visible, int orientation, int direction) {
switch (orientation) {
case SwingConstants.HORIZONTAL:
return getScrollableIncrement(horizontalUnit, visible.width);
case SwingConstants.VERTICAL:
return getScrollableIncrement(verticalUnit, visible.height);
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
public int getScrollableBlockIncrement(
Rectangle visible, int orientation, int direction) {
switch (orientation) {
case SwingConstants.HORIZONTAL:
return getScrollableIncrement(horizontalBlock, visible.width);
case SwingConstants.VERTICAL:
return getScrollableIncrement(verticalBlock, visible.height);
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
protected int getScrollableIncrement(IncrementInfo info, int distance) {
if (info.getIncrement() == IncrementType.PIXELS) {
return info.getAmount();
} else {
return distance * info.getAmount() / 100;
}
}
public boolean getScrollableTracksViewportWidth() {
if (scrollableWidth == ScrollableSizeHint.NONE) {
return false;
}
if (scrollableWidth == ScrollableSizeHint.FIT) {
return true;
}
// STRETCH sizing, use the greater of the panel or viewport width
if (getParent() instanceof JViewport) {
return (((JViewport) getParent()).getWidth() > getPreferredSize().width);
}
return false;
}
public boolean getScrollableTracksViewportHeight() {
if (scrollableHeight == ScrollableSizeHint.NONE) {
return false;
}
if (scrollableHeight == ScrollableSizeHint.FIT) {
return true;
}
// STRETCH sizing, use the greater of the panel or viewport height
if (getParent() instanceof JViewport) {
return (((JViewport) getParent()).getHeight() > getPreferredSize().height);
}
return false;
}
/**
* Helper class to hold the information required to calculate the scroll amount.
*/
static class IncrementInfo {
private IncrementType type;
private int amount;
public IncrementInfo(IncrementType type, int amount) {
this.type = type;
this.amount = amount;
}
public IncrementType getIncrement() {
return type;
}
public int getAmount() {
return amount;
}
public String toString() {
return
"ScrollablePanel[" +
type + ", " +
amount + "]";
}
}
}

View file

@ -0,0 +1,58 @@
package ee.carlrobert.chatgpt.toolwindow;
import com.intellij.ui.JBColor;
import ee.carlrobert.chatgpt.ComponentBorder;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JTextField;
public class TextField extends JTextField {
public TextField(ActionListener submitButtonListener) {
setBorder(BorderFactory.createCompoundBorder(
super.getBorder(),
BorderFactory.createEmptyBorder(5, 5, 5, 5)));
setForeground(JBColor.GRAY);
addFocusListener(getFocusListener());
var button = createSubmitButton(submitButtonListener);
ComponentBorder cb = new ComponentBorder(button);
cb.setAdjustInsets(false);
cb.install(this);
}
private JButton createSubmitButton(ActionListener submitButtonListener) {
var imageIcon = new ImageIcon(Objects.requireNonNull(getClass().getResource("/icons/send-icon.png")));
var button = new JButton(imageIcon);
button.setBorder(BorderFactory.createEmptyBorder());
button.setContentAreaFilled(false);
button.setPreferredSize(new Dimension(imageIcon.getIconWidth(), imageIcon.getIconHeight()));
button.addActionListener(submitButtonListener);
return button;
}
private FocusListener getFocusListener() {
return new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if (getText().equals("Ask a question...")) {
setText("");
setForeground(JBColor.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if (getText().isEmpty()) {
setForeground(JBColor.GRAY);
setText("Ask a question...");
}
}
};
}
}

View file

@ -0,0 +1,29 @@
package ee.carlrobert.chatgpt.toolwindow;
import com.intellij.ui.JBColor;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.JTextField;
public class TextFieldFocusListener implements FocusListener {
private final JTextField searchField;
public TextFieldFocusListener(JTextField searchField) {
this.searchField = searchField;
}
public void focusGained(FocusEvent event) {
if (searchField.getText().equals("Ask a question...")) {
searchField.setText("");
searchField.setForeground(JBColor.BLACK);
}
}
public void focusLost(FocusEvent event) {
if (searchField.getText().isEmpty()) {
searchField.setForeground(JBColor.GRAY);
searchField.setText("Ask a question...");
}
}
}

View file

@ -0,0 +1,21 @@
package ee.carlrobert.chatgpt.toolwindow;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class TextFieldKeyListener implements KeyListener {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
}

View file

@ -0,0 +1,47 @@
package ee.carlrobert.chatgpt.toolwindow;
import com.intellij.ui.JBColor;
import java.awt.Component;
import java.awt.Font;
import java.net.URL;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JTextArea;
public class ToolWindowUtil {
public static JTextArea createTextArea(String selectedText, boolean isItalicFont, boolean transparentBackground) {
var textArea = new JTextArea();
textArea.append(selectedText);
textArea.setLineWrap(true);
textArea.setEditable(false);
textArea.setFont(createFont(isItalicFont, textArea.getFont().getSize()));
textArea.setWrapStyleWord(true);
if (transparentBackground) {
textArea.setBackground(JBColor.background());
}
// textArea.setBorder(new MatteBorder(0, 2, 0, 0, JBColor.RED));
return textArea;
}
public static JLabel createIconLabel(URL iconLocation) {
var iconLabel = new JLabel(new ImageIcon(iconLocation));
iconLabel.setText("ChatGPT");
iconLabel.setFont(iconLabel.getFont().deriveFont(iconLabel.getFont().getStyle() | Font.BOLD));
iconLabel.setIconTextGap(10);
return iconLabel;
}
public static Font createFont(boolean isItalic, int fontSize) {
return new Font("Tahoma", isItalic ? Font.ITALIC : Font.PLAIN, fontSize);
}
public static Box justifyLeft(Component component) {
Box box = Box.createHorizontalBox();
box.add(component);
box.add(Box.createHorizontalGlue());
return box;
}
}

View file

@ -0,0 +1,12 @@
// Copyright 2000-2022 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package icons;
import com.intellij.openapi.util.IconLoader;
import javax.swing.Icon;
public class SdkIcons {
public static final Icon Sdk_default_icon = IconLoader.getIcon("/icons/sdk_16.svg", SdkIcons.class);
}

View file

@ -0,0 +1,83 @@
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
<idea-plugin>
<!-- Unique identifier of the plugin. It should be FQN. It cannot be changed between the plugin versions. -->
<id>ee.carlrobert.chatgpt</id>
<!-- Public plugin name should be written in Title Case.
Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-name -->
<name>OpenAI - ChatGPT</name>
<!-- A displayed Vendor name or Organization ID displayed on the Plugins Page. -->
<vendor email="carlrobertoh@gmail.com" url="https://carlrobert.ee">Carl-Robert Linnupuu</vendor>
<!-- Description of the plugin displayed on the Plugin Page and IDE Plugin Manager.
Simple HTML elements (text formatting, paragraphs, and lists) can be added inside of <![CDATA[ ]]> tag.
Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-description -->
<description><![CDATA[
<b>ChatGPT as your copilot to level up your developer experience</b>
<br />
<br />
<p>Available commands</p>
<ul>
<li><b>Find bugs:</b> Analyze and find bugs in your code. Right click on a selected block of code, run command.</li>
<li><b>Add tests:</b> Write tests for you. Right click on a selected block of code, run command.</li>
<li><b>Refactor:</b> Refactor your code. Right click on a selected block of code, run command.</li>
<li><b>Optimize:</b> Add suggestions to your code to improve. Right click on a selected block of code, run command.</li>
<li><b>Explain:</b> Explain the selected code. Right click on a selected block of code, run command.</li>
</ul>
<br />
<b>Before using the plugin, you need to set up the API key in Settings -> Tools -> ChatGPT</b>
]]></description>
<change-notes>
<![CDATA[
<ul>
<li><b>1.0.1</b> Design updates and simple missing key notification</li>
<li><b>1.0.0</b> First release</li>
</ul>
]]>
</change-notes>
<!-- Product and plugin compatibility requirements.
Read more: https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html -->
<depends>com.intellij.modules.platform</depends>
<extensions defaultExtensionNs="com.intellij">
<postStartupActivity implementation="ee.carlrobert.chatgpt.ShowNotificationActivity"/>
<applicationConfigurable parentId="tools" instance="ee.carlrobert.chatgpt.SettingsConfigurable"
id="org.intellij.sdk.settings.AppSettingsConfigurable"
displayName="ChatGPT"/>
<applicationService serviceImplementation="ee.carlrobert.chatgpt.SettingsState"/>
<applicationService serviceImplementation="ee.carlrobert.chatgpt.service.ToolWindowService"/>
<toolWindow id="ChatGPT" secondary="true" icon="AllIcons.General.Modified" anchor="right"
factoryClass="ee.carlrobert.chatgpt.toolwindow.ChatGptToolWindowFactory"/>
<notificationGroup id="ChatGPT-Empty-API-Key"
displayType="STICKY_BALLOON"
key="ee.carlrobert.chatgpt.notification.key"/>
</extensions>
<resource-bundle>messages.BasicActionsBundle</resource-bundle>
<actions>
<group id="ee.carlrobert.chatgpt.action.ActionGroup"
class="ee.carlrobert.chatgpt.action.ActionGroup"
popup="true">
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
<action id="ee.carlrobert.chatgpt.action.WriteTestsAction" class="ee.carlrobert.chatgpt.action.WriteTestsAction"/>
<action id="ee.carlrobert.chatgpt.action.FindBugsAction" class="ee.carlrobert.chatgpt.action.FindBugsAction"/>
<action id="ee.carlrobert.chatgpt.action.RefactorAction" class="ee.carlrobert.chatgpt.action.RefactorAction"/>
<action id="ee.carlrobert.chatgpt.action.OptimizeAction" class="ee.carlrobert.chatgpt.action.OptimizeAction"/>
<action id="ee.carlrobert.chatgpt.action.ExplainAction" class="ee.carlrobert.chatgpt.action.ExplainAction"/>
</group>
<action
id="ee.carlrobert.chatgpt.action.AskAction"
class="ee.carlrobert.chatgpt.action.AskAction"
text="Ask ChatGPT"
description="TBD"
icon="SdkIcons.Sdk_default_icon">
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
</action>
</actions>
</idea-plugin>

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality"
fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 512">
<rect fill="#000" width="512" height="512" rx="104.187" ry="105.042"/>
<path fill="#fff" fill-rule="nonzero"
d="M378.68 230.011a71.432 71.432 0 003.654-22.541 71.383 71.383 0 00-9.783-36.064c-12.871-22.404-36.747-36.236-62.587-36.236a72.31 72.31 0 00-15.145 1.604 71.362 71.362 0 00-53.37-23.991h-.453l-.17.001c-31.297 0-59.052 20.195-68.673 49.967a71.372 71.372 0 00-47.709 34.618 72.224 72.224 0 00-9.755 36.226 72.204 72.204 0 0018.628 48.395 71.395 71.395 0 00-3.655 22.541 71.388 71.388 0 009.783 36.064 72.187 72.187 0 0077.728 34.631 71.375 71.375 0 0053.374 23.992H271l.184-.001c31.314 0 59.06-20.196 68.681-49.995a71.384 71.384 0 0047.71-34.619 72.107 72.107 0 009.736-36.194 72.201 72.201 0 00-18.628-48.394l-.003-.004zM271.018 380.492h-.074a53.576 53.576 0 01-34.287-12.423 44.928 44.928 0 001.694-.96l57.032-32.943a9.278 9.278 0 004.688-8.06v-80.459l24.106 13.919a.859.859 0 01.469.661v66.586c-.033 29.604-24.022 53.619-53.628 53.679zm-115.329-49.257a53.563 53.563 0 01-7.196-26.798c0-3.069.268-6.146.79-9.17.424.254 1.164.706 1.695 1.011l57.032 32.943a9.289 9.289 0 009.37-.002l69.63-40.205v27.839l.001.048a.864.864 0 01-.345.691l-57.654 33.288a53.791 53.791 0 01-26.817 7.17 53.746 53.746 0 01-46.506-26.818v.003zm-15.004-124.506a53.5 53.5 0 0127.941-23.534c0 .491-.028 1.361-.028 1.965v65.887l-.001.054a9.27 9.27 0 004.681 8.053l69.63 40.199-24.105 13.919a.864.864 0 01-.813.074l-57.66-33.316a53.746 53.746 0 01-26.805-46.5 53.787 53.787 0 017.163-26.798l-.003-.003zm198.055 46.089l-69.63-40.204 24.106-13.914a.863.863 0 01.813-.074l57.659 33.288a53.71 53.71 0 0126.835 46.491c0 22.489-14.033 42.612-35.133 50.379v-67.857c.003-.025.003-.051.003-.076a9.265 9.265 0 00-4.653-8.033zm23.993-36.111a81.919 81.919 0 00-1.694-1.01l-57.032-32.944a9.31 9.31 0 00-4.684-1.266 9.31 9.31 0 00-4.684 1.266l-69.631 40.205v-27.839l-.001-.048c0-.272.129-.528.346-.691l57.654-33.26a53.696 53.696 0 0126.816-7.177c29.644 0 53.684 24.04 53.684 53.684a53.91 53.91 0 01-.774 9.077v.003zm-150.831 49.618l-24.111-13.919a.859.859 0 01-.469-.661v-66.587c.013-29.628 24.053-53.648 53.684-53.648a53.719 53.719 0 0134.349 12.426c-.434.237-1.191.655-1.694.96l-57.032 32.943a9.272 9.272 0 00-4.687 8.057v.053l-.04 80.376zm13.095-28.233l31.012-17.912 31.012 17.9v35.812l-31.012 17.901-31.012-17.901v-35.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality"
fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 512 512">
<rect fill="#000" width="512" height="512" rx="104.187" ry="105.042"/>
<path fill="#fff" fill-rule="nonzero"
d="M378.68 230.011a71.432 71.432 0 003.654-22.541 71.383 71.383 0 00-9.783-36.064c-12.871-22.404-36.747-36.236-62.587-36.236a72.31 72.31 0 00-15.145 1.604 71.362 71.362 0 00-53.37-23.991h-.453l-.17.001c-31.297 0-59.052 20.195-68.673 49.967a71.372 71.372 0 00-47.709 34.618 72.224 72.224 0 00-9.755 36.226 72.204 72.204 0 0018.628 48.395 71.395 71.395 0 00-3.655 22.541 71.388 71.388 0 009.783 36.064 72.187 72.187 0 0077.728 34.631 71.375 71.375 0 0053.374 23.992H271l.184-.001c31.314 0 59.06-20.196 68.681-49.995a71.384 71.384 0 0047.71-34.619 72.107 72.107 0 009.736-36.194 72.201 72.201 0 00-18.628-48.394l-.003-.004zM271.018 380.492h-.074a53.576 53.576 0 01-34.287-12.423 44.928 44.928 0 001.694-.96l57.032-32.943a9.278 9.278 0 004.688-8.06v-80.459l24.106 13.919a.859.859 0 01.469.661v66.586c-.033 29.604-24.022 53.619-53.628 53.679zm-115.329-49.257a53.563 53.563 0 01-7.196-26.798c0-3.069.268-6.146.79-9.17.424.254 1.164.706 1.695 1.011l57.032 32.943a9.289 9.289 0 009.37-.002l69.63-40.205v27.839l.001.048a.864.864 0 01-.345.691l-57.654 33.288a53.791 53.791 0 01-26.817 7.17 53.746 53.746 0 01-46.506-26.818v.003zm-15.004-124.506a53.5 53.5 0 0127.941-23.534c0 .491-.028 1.361-.028 1.965v65.887l-.001.054a9.27 9.27 0 004.681 8.053l69.63 40.199-24.105 13.919a.864.864 0 01-.813.074l-57.66-33.316a53.746 53.746 0 01-26.805-46.5 53.787 53.787 0 017.163-26.798l-.003-.003zm198.055 46.089l-69.63-40.204 24.106-13.914a.863.863 0 01.813-.074l57.659 33.288a53.71 53.71 0 0126.835 46.491c0 22.489-14.033 42.612-35.133 50.379v-67.857c.003-.025.003-.051.003-.076a9.265 9.265 0 00-4.653-8.033zm23.993-36.111a81.919 81.919 0 00-1.694-1.01l-57.032-32.944a9.31 9.31 0 00-4.684-1.266 9.31 9.31 0 00-4.684 1.266l-69.631 40.205v-27.839l-.001-.048c0-.272.129-.528.346-.691l57.654-33.26a53.696 53.696 0 0126.816-7.177c29.644 0 53.684 24.04 53.684 53.684a53.91 53.91 0 01-.774 9.077v.003zm-150.831 49.618l-24.111-13.919a.859.859 0 01-.469-.661v-66.587c.013-29.628 24.053-53.648 53.684-53.648a53.719 53.719 0 0134.349 12.426c-.434.237-1.191.655-1.694.96l-57.032 32.943a9.272 9.272 0 00-4.687 8.057v.053l-.04 80.376zm13.095-28.233l31.012-17.912 31.012 17.9v35.812l-31.012 17.901-31.012-17.901v-35.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

View file

@ -0,0 +1,17 @@
group.ee.carlrobert.chatgpt.action.ActionGroup.text=ChatGPT options
group.ee.carlrobert.chatgpt.action.ActionGroup.description=Explore ChatGPT options
action.ee.carlrobert.chatgpt.action.AskAction.text=Ask ChatGPT
action.ee.carlrobert.chatgpt.action.AskAction.description=TBD
action.ee.carlrobert.chatgpt.action.ExplainAction.text=Explain
action.ee.carlrobert.chatgpt.action.ExplainAction.description=TBD
action.ee.carlrobert.chatgpt.action.RefactorAction.text=Refactor
action.ee.carlrobert.chatgpt.action.RefactorAction.description=TBD
action.ee.carlrobert.chatgpt.action.FindBugsAction.text=Find bugs
action.ee.carlrobert.chatgpt.action.FindBugsAction.description=TBD
action.ee.carlrobert.chatgpt.action.OptimizeAction.text=Optimize
action.ee.carlrobert.chatgpt.action.OptimizeAction.description=TBD
action.ee.carlrobert.chatgpt.action.WriteTestsAction.text=Write tests
action.ee.carlrobert.chatgpt.action.WriteTestsAction.description=TBD
ee.carlrobert.chatgpt.notification.key=ChatGPT-notification