Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;

public class Code4MeBundle extends DynamicBundle {

private static final String PATH_TO_BUNDLE = "messages.Code4MeBundle";
private static final Code4MeBundle instance = new Code4MeBundle();

private static final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

public static @Nls String message(
@NotNull @PropertyKey(resourceBundle = PATH_TO_BUNDLE) String key,
Object... params
Expand All @@ -31,10 +27,6 @@ public static Supplier<String> messagePointer(
return instance.getLazyMessage(key, params);
}

public static ScheduledExecutorService getExecutorService() {
return executorService;
}

private Code4MeBundle() {
super(PATH_TO_BUNDLE);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package me.code4me.plugin;

import org.jetbrains.annotations.NotNull;

import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Code4MeScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {

private static final Code4MeScheduledThreadPoolExecutor instance = new Code4MeScheduledThreadPoolExecutor();

public static Code4MeScheduledThreadPoolExecutor getInstance() {
return instance;
}

private Code4MeScheduledThreadPoolExecutor() {
super(1);
}

@Override
public ScheduledFuture<?> schedule(@NotNull Runnable command, long delay, @NotNull TimeUnit unit) {
return super.schedule(() -> {
try {
command.run();
} catch (Throwable th) {
th.printStackTrace();
throw th;
}
}, delay, unit);
}

@Override
public <V> ScheduledFuture<V> schedule(@NotNull Callable<V> callable, long delay, @NotNull TimeUnit unit) {
return super.schedule(() -> {
try {
return callable.call();
} catch (Throwable th) {
th.printStackTrace();
throw th;
}
}, delay, unit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ public class PredictionAutocompleteResponse extends Code4MeResponse {

private final String[] predictions;
private final String verifyToken;
private final boolean survey;

public PredictionAutocompleteResponse(String[] predictions, String verifyToken) {
public PredictionAutocompleteResponse(String[] predictions, String verifyToken, boolean survey) {
super(200);
this.predictions = predictions;
this.verifyToken = verifyToken;
this.survey = survey;
}

public String[] getPredictions() {
Expand All @@ -18,4 +20,8 @@ public String[] getPredictions() {
public String getVerifyToken() {
return verifyToken;
}

public boolean isSurvey() {
return survey;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.notification.NotificationAction;
import com.intellij.notification.NotificationGroupManager;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.ApplicationManager;
Expand All @@ -23,17 +24,21 @@
import com.intellij.patterns.PlatformPatterns;
import me.code4me.plugin.Code4MeIcons;
import me.code4me.plugin.Code4MeBundle;
import me.code4me.plugin.Code4MeScheduledThreadPoolExecutor;
import me.code4me.plugin.api.PredictionAutocompleteRequest;
import me.code4me.plugin.api.PredictionVerifyRequest;
import me.code4me.plugin.api.Code4MeErrorResponse;
import me.code4me.plugin.exceptions.ApiServerException;
import me.code4me.plugin.services.Code4MeApiService;
import me.code4me.plugin.services.Code4MeSettingsService;
import me.code4me.plugin.util.Code4MeUtil;
import org.jetbrains.annotations.NotNull;
import java.awt.EventQueue;
import java.util.Arrays;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

public class Code4MeCompletionContributor extends CompletionContributor {

Expand Down Expand Up @@ -61,13 +66,37 @@ public static void suggestCompletion(

project.getService(Code4MeApiService.class).fetchAutoCompletion(project, request).thenAccept(res -> {
String[] predictions = res.getPredictions();

Code4MeSettingsService settingsService = project.getService(Code4MeSettingsService.class);
Code4MeSettingsService.Settings settings = settingsService.getSettings();
if (!settings.isIgnoringSurvey() && res.isSurvey()) {
NotificationGroupManager.getInstance()
.getNotificationGroup("Code4Me Notifications")
.createNotification(
Code4MeBundle.message("survey-title"),
Code4MeBundle.message("survey-content"),
NotificationType.INFORMATION
).addAction(NotificationAction.createSimple(
Code4MeBundle.message("survey-redirect-action"),
() -> project.getService(Code4MeApiService.class).redirectToCode4MeSurvey(project))
).addAction(NotificationAction.createSimple(
Code4MeBundle.message("survey-cancel-action"),
() -> {
settings.setIgnoringSurvey(true);
settingsService.save();
})
).notify(project);
}

EventQueue.invokeLater(() -> {
if (predictions == null || Arrays.stream(predictions).allMatch(String::isBlank)) {
HintManager.getInstance().showInformationHint(editor, "No Code4Me Suggestions available");
} else {
String verifyToken = res.getVerifyToken();
completionCache.setPredictions(predictions);
completionCache.setOffset(offset);
completionCache.setVerifyToken(res.getVerifyToken());
completionCache.setVerifyToken(verifyToken);
completionCache.setTimeoutSupplier(() -> checkCodeChanges(project, verifyToken, null, offset, doc));
completionCache.setEmpty(false);

ApplicationManager.getApplication().invokeLater(() -> {
Expand All @@ -87,7 +116,7 @@ public static void suggestCompletion(
});
}

private static void checkCodeChanges(
private static ScheduledFuture<?> checkCodeChanges(
Project project,
String verifyToken,
String chosenPrediction,
Expand All @@ -105,9 +134,10 @@ public void documentChanged(@NotNull DocumentEvent event) {
};
doc.addDocumentListener(listener);

Code4MeBundle.getExecutorService().schedule(() -> {
return Code4MeScheduledThreadPoolExecutor.getInstance().schedule(() -> {
doc.removeDocumentListener(listener);
String groundTruth = doc.getText().substring(atomicOffset.get()).split("\n")[0];
String[] lines = doc.getText().substring(atomicOffset.get()).split("\n");
String groundTruth = lines.length == 0 ? "" : lines[0];
project.getService(Code4MeApiService.class).sendCompletionData(
project,
new PredictionVerifyRequest(verifyToken, chosenPrediction, groundTruth)
Expand Down Expand Up @@ -144,16 +174,21 @@ protected void addCompletions(
return;
}

ScheduledFuture<?> dataRequest = completionCache.getTimeoutSupplier().get();

for (String prediction : completionCache.getPredictions()) {
result.addElement(prioritize(LookupElementBuilder.create(prediction)
.withIcon(Code4MeIcons.PLUGIN_ICON)
.withInsertHandler((cxt, item) -> checkCodeChanges(
cxt.getProject(),
completionCache.getVerifyToken(),
prediction,
completionCache.getOffset(),
cxt.getDocument()
))
.withInsertHandler((cxt, item) -> {
if (!dataRequest.cancel(true)) return;
checkCodeChanges(
cxt.getProject(),
completionCache.getVerifyToken(),
prediction,
completionCache.getOffset(),
cxt.getDocument()
);
})
.withTypeText("Code4Me")
));
}
Expand All @@ -180,12 +215,14 @@ private static class CompletionCache {
private int offset;
private String verifyToken;
private boolean empty;
private Supplier<ScheduledFuture<?>> timeoutSupplier;

public CompletionCache() {
this.predictions = new String[0];
this.offset = -1;
this.verifyToken = "";
this.empty = true;
this.timeoutSupplier = null;
}

public void setPredictions(String[] predictions) {
Expand Down Expand Up @@ -219,5 +256,13 @@ public void setEmpty(boolean empty) {
public boolean isEmpty() {
return empty;
}

public void setTimeoutSupplier(Supplier<ScheduledFuture<?>> timeoutSupplier) {
this.timeoutSupplier = timeoutSupplier;
}

public Supplier<ScheduledFuture<?>> getTimeoutSupplier() {
return timeoutSupplier;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.code4me.plugin.services;

import com.google.gson.Gson;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
Expand Down Expand Up @@ -37,6 +38,7 @@ public class Code4MeApiService {
private static final String BASE_URL = "https://code4me.me/api/v1";
private static final String PREDICTION_AUTOCOMPLETE_ENDPOINT = "/prediction/autocomplete";
private static final String PREDICTION_VERIFY_ENDPOINT = "/prediction/verify";
private static final String SURVEY_ENDPOINT = "/survey?user_id=%s";

private final AtomicBoolean lock = new AtomicBoolean(false);

Expand Down Expand Up @@ -157,4 +159,9 @@ private <T extends Code4MeResponse> Code4MeResponse parseResponseBody(
}
}
}

public void redirectToCode4MeSurvey(Project project) {
String userToken = project.getService(Code4MeSettingsService.class).getSettings().getUserToken();
BrowserUtil.browse(BASE_URL + String.format(SURVEY_ENDPOINT, userToken));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public Code4MeSettingsService() {

String settingsJson = PasswordSafe.getInstance().getPassword(credentialAttributes);
if (settingsJson == null) {
this.settings = new Settings(generateToken(), true);
this.settings = new Settings(generateToken(), true, false);
this.save();
} else {
this.settings = gson.fromJson(settingsJson, Settings.class);
Expand All @@ -51,10 +51,12 @@ public static class Settings {

private String userToken;
private boolean triggerPoints;
private Boolean ignoringSurvey;

public Settings(String userToken, boolean triggerPoints) {
public Settings(String userToken, boolean triggerPoints, boolean ignoringSurvey) {
this.userToken = userToken;
this.triggerPoints = triggerPoints;
this.ignoringSurvey = ignoringSurvey;
}

public void setUserToken(String userToken) {
Expand All @@ -72,5 +74,13 @@ public boolean isTriggerPoints() {
public void setTriggerPoints(boolean triggerPoints) {
this.triggerPoints = triggerPoints;
}

public boolean isIgnoringSurvey() {
return Boolean.TRUE.equals(ignoringSurvey);
}

public void setIgnoringSurvey(boolean ignoringSurvey) {
this.ignoringSurvey = ignoringSurvey;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ project-opened-setup-content=Thanks for using the Code4Me Plugin.<br><br>You can
project-opened-title=Code4Me plugin
project-opened-content=Thanks for using the Code4Me Plugin. You can use the <strong>alt + shift + K</strong> keybind to query an autocompletion.
project-opened-settings-action=Open settings
survey-title=Code4me survey
survey-content=Thanks for using the Code4Me plugin. We would greatly appreciate it if you could help our study by filling in the survey.
survey-redirect-action=Fill in survey
survey-cancel-action=Do not show again
2 changes: 1 addition & 1 deletion code4me-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Content-Type: application/json
```
{
"verifyToken": string,
"chosenPrediction": string,
"chosenPrediction": string | null,
"groundTruth": string
}
```
Expand Down
2 changes: 1 addition & 1 deletion code4me-server/src/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def verify():
request.get_json(),
[
("verifyToken", str, False),
("chosenPrediction", str, False),
("chosenPrediction", str, True),
("groundTruth", str, False),
],
)
Expand Down