Skip to content

Commit

Permalink
DEV2-1707: add a snooze button for tabnine completions (#1341)
Browse files Browse the repository at this point in the history
* DEV2-1707: add a pause button

* handle self-hosted

* add setting

* fix lint

* improve naming

* track snooze toggled

* CompletionState refactor (#1344)

* remove unneccessary check

---------

Co-authored-by: Dima Abramovich <[email protected]>
  • Loading branch information
yairco1990 and dimacodota authored Oct 26, 2023
1 parent c998175 commit c0655e1
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 26 deletions.
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,13 @@
"default": 0,
"description": "debounce milliseconds before rendering tabnine suggestion"
},
"tabnine.snoozeDuration": {
"type": "number",
"default": 1,
"description": "Hours to disable inline completions when clicking the snooze button",
"minimum": 1,
"maximum": 24
},
"tabnine.useProxySupport": {
"type": "boolean",
"default": true,
Expand Down
13 changes: 13 additions & 0 deletions src/autocompleteInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from "./globals/versions";
import enableProposed from "./globals/proposedAPI";
import { registerInlineProvider } from "./inlineSuggestions/registerInlineProvider";
import { completionsState } from "./state/completionsState";

let subscriptions: Disposable[] = [];

Expand All @@ -40,6 +41,14 @@ export default async function installAutocomplete(
}
})
);

completionsState.on("changed", (enabled) => {
if (enabled) {
void reinstallAutocomplete(InstallOptions.get());
} else {
uninstallAutocomplete();
}
});
}

async function reinstallAutocomplete({
Expand All @@ -49,6 +58,10 @@ async function reinstallAutocomplete({
}: InstallOptions) {
uninstallAutocomplete();

if (!completionsState.value) {
return;
}

if (
(inlineEnabled || snippetsEnabled) &&
(isInlineSuggestionReleasedApiSupported() || (await isDefaultAPIEnabled()))
Expand Down
28 changes: 17 additions & 11 deletions src/commandsHandler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { commands, ExtensionContext } from "vscode";
import { StateType, STATUS_BAR_FIRST_TIME_CLICKED } from "./globals/consts";
import { Capability, isCapabilityEnabled } from "./capabilities/capabilities";
import { StateType, STATUS_BAR_FIRST_TIME_CLICKED } from "./globals/consts";
import openHub, { openHubExternal } from "./hub/openHub";
import { showStatusBarNotificationOptions } from "./statusBar/statusBarNotificationOptions";

const CONFIG_COMMAND = "TabNine::config";
const CONFIG_EXTERNAL_COMMAND = "TabNine::configExternal";
Expand All @@ -23,15 +24,20 @@ export function registerCommands(context: ExtensionContext): void {
}

function handleStatusBar(context: ExtensionContext) {
const openHubWithStatus = openHub(StateType.STATUS);

return async (args: string[] | null = null): Promise<void> => {
await openHubWithStatus(args);

if (
isCapabilityEnabled(Capability.SHOW_AGRESSIVE_STATUS_BAR_UNTIL_CLICKED)
) {
await context.globalState.update(STATUS_BAR_FIRST_TIME_CLICKED, true);
}
return (args: string[] | null = null) => {
showStatusBarNotificationOptions(
"Open Hub",
() => void openHubHandler(context, args)
);
};
}

async function openHubHandler(
context: ExtensionContext,
args: string[] | null = null
) {
await openHub(StateType.STATUS)(args);
if (isCapabilityEnabled(Capability.SHOW_AGRESSIVE_STATUS_BAR_UNTIL_CLICKED)) {
await context.globalState.update(STATUS_BAR_FIRST_TIME_CLICKED, true);
}
}
9 changes: 9 additions & 0 deletions src/enterprise/statusBar/StatusBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "../../globals/consts";
import getUserInfo, { UserInfo } from "../requests/UserInfo";
import { Logger } from "../../utils/logger";
import { completionsState } from "../../state/completionsState";

export class StatusBar implements Disposable {
private item: StatusItem;
Expand All @@ -37,6 +38,14 @@ export class StatusBar implements Disposable {

// eslint-disable-next-line @typescript-eslint/unbound-method
this.setServerRequired().catch(Logger.error);

completionsState.on("changed", (enabled) => {
if (enabled) {
this.item.setDefault();
} else {
this.item.setCompletionsDisabled();
}
});
}

private async setServerRequired() {
Expand Down
11 changes: 9 additions & 2 deletions src/enterprise/statusBar/StatusItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class StatusItem implements Disposable {
private comand: Disposable;

constructor() {
this.item = window.createStatusBarItem(StatusBarAlignment.Left, -1);
this.item = window.createStatusBarItem(StatusBarAlignment.Right, -1);
this.comand = commands.registerCommand(commandId, action);
this.item.show();
}
Expand All @@ -30,7 +30,7 @@ export class StatusItem implements Disposable {

public setDefault() {
this.item.backgroundColor = undefined;
this.item.tooltip = `${FULL_BRAND_REPRESENTATION} (Click to open settings)`;
this.item.tooltip = `${FULL_BRAND_REPRESENTATION} (Show options)`;
this.item.text = STATUS_NAME;
}

Expand All @@ -54,6 +54,13 @@ export class StatusItem implements Disposable {
this.item.text = STATUS_NAME;
}

public setCompletionsDisabled() {
this.item.backgroundColor = new ThemeColor(
"statusBarItem.warningBackground"
);
this.item.text = STATUS_NAME;
}

public setCommand(state: StatusState) {
this.item.command = {
title: "Status action",
Expand Down
13 changes: 8 additions & 5 deletions src/enterprise/statusBar/statusAction.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Uri, commands, env, window } from "vscode";
import { callForLogin } from "../../authentication/authentication.api";
import { showStatusBarNotificationOptions } from "../../statusBar/statusBarNotificationOptions";
import { Logger } from "../../utils/logger";
import {
EXTENSION_ID,
OPEN_SETTINGS_COMMAND,
TABNINE_HOST_CONFIGURATION,
} from "../consts";
import { Logger } from "../../utils/logger";

export enum StatusState {
SetServer,
Expand Down Expand Up @@ -91,10 +92,12 @@ export function action(state: StatusState): void {
break;

default:
void commands.executeCommand(
OPEN_SETTINGS_COMMAND,
`@ext:tabnine.${EXTENSION_ID}`
);
showStatusBarNotificationOptions("Open Settings", () => {
void commands.executeCommand(
OPEN_SETTINGS_COMMAND,
`@ext:tabnine.${EXTENSION_ID}`
);
});
break;
}
}
Expand Down
6 changes: 2 additions & 4 deletions src/provideInlineCompletionItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
} from "./lookAheadSuggestion";
import debounceCompletions from "./debounceCompletions";
import reportSuggestionShown from "./reportSuggestionShown";
import { shouldBlockCompletions } from "./registration/forceRegistration";
import { Logger } from "./utils/logger";

const END_OF_LINE_VALID_REGEX = new RegExp("^\\s*[)}\\]\"'`]*\\s*[:{;,]?\\s*$");
Expand All @@ -24,10 +23,9 @@ export default async function provideInlineCompletionItems(
try {
clearCurrentLookAheadSuggestion();
if (
!completionIsAllowed(document, position) ||
!isValidMidlinePosition(document, position) ||
!getShouldComplete() ||
shouldBlockCompletions()
!completionIsAllowed(document, position) ||
!isValidMidlinePosition(document, position)
) {
return undefined;
}
Expand Down
34 changes: 34 additions & 0 deletions src/state/completionsState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { EventEmitter } from "events";
import { workspace } from "vscode";

class CompletionState extends EventEmitter {
private state: boolean = true;

private enableTimeout: NodeJS.Timeout | null = null;

get value(): boolean {
return this.state;
}

set value(enabled: boolean) {
this.state = enabled;
this.emit("changed", enabled);

if (this.enableTimeout) {
clearTimeout(this.enableTimeout);
this.enableTimeout = null;
}

if (!enabled) {
const snoozeDuration = workspace
.getConfiguration("tabnine")
.get<number>("snoozeDuration", 1);

this.enableTimeout = setTimeout(() => {
this.state = true;
}, snoozeDuration * 60 * 1000);
}
}
}

export const completionsState = new CompletionState();
9 changes: 7 additions & 2 deletions src/statusBar/StatusBarData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from "../globals/consts";
import { getPersistedAlphaVersion } from "../preRelease/versions";
import { shouldStatusBarBeProminent } from "../registration/forceRegistration";
import { completionsState } from "../state/completionsState";

export default class StatusBarData implements Disposable {
private _serviceLevel?: ServiceLevel;
Expand Down Expand Up @@ -77,7 +78,7 @@ export default class StatusBarData implements Disposable {
return this._text;
}

private updateStatusBar() {
public updateStatusBar() {
const issueText = this._text ? `: ${this._text}` : "";
const serviceLevel = this.getDisplayServiceLevel();
const limited = this._limited ? ` ${LIMITATION_SYMBOL}` : "";
Expand All @@ -87,6 +88,10 @@ export default class StatusBarData implements Disposable {
this._statusBarItem.backgroundColor = new ThemeColor(
"statusBarItem.warningBackground"
);
} else if (!completionsState.value) {
this._statusBarItem.backgroundColor = new ThemeColor(
"statusBarItem.warningBackground"
);
} else {
this._statusBarItem.backgroundColor = undefined;
}
Expand All @@ -102,7 +107,7 @@ export default class StatusBarData implements Disposable {
Capability.SHOW_AGRESSIVE_STATUS_BAR_UNTIL_CLICKED
) && !this._context.globalState.get(STATUS_BAR_FIRST_TIME_CLICKED)
? "Click 'tabnine' for settings and more information"
: `${FULL_BRAND_REPRESENTATION} (Click to open settings)${
: `${FULL_BRAND_REPRESENTATION} (Show options)${
getPersistedAlphaVersion(this._context) ?? ""
}`;
}
Expand Down
7 changes: 5 additions & 2 deletions src/statusBar/statusBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import StatusBarData from "./StatusBarData";
import StatusBarPromotionItem from "./StatusBarPromotionItem";
import { ServiceLevel } from "../binary/state";
import { Logger } from "../utils/logger";
import { completionsState } from "../state/completionsState";

const SPINNER = "$(sync~spin)";

Expand All @@ -22,9 +23,9 @@ export function registerStatusBar(context: ExtensionContext): Disposable {
return statusBarData;
}

const statusBar = window.createStatusBarItem(StatusBarAlignment.Left, -1);
const statusBar = window.createStatusBarItem(StatusBarAlignment.Right, -1);
promotion = new StatusBarPromotionItem(
window.createStatusBarItem(StatusBarAlignment.Left, -1)
window.createStatusBarItem(StatusBarAlignment.Right, -1)
);
statusBarData = new StatusBarData(statusBar, context);
statusBar.command = STATUS_BAR_COMMAND;
Expand All @@ -37,6 +38,8 @@ export function registerStatusBar(context: ExtensionContext): Disposable {
Logger.error("failed to rename status bar");
}

completionsState.on("changed", () => statusBarData?.updateStatusBar());

setLoadingStatus("Starting...");
return Disposable.from(statusBarData, promotion);
}
Expand Down
49 changes: 49 additions & 0 deletions src/statusBar/statusBarNotificationOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { window, workspace } from "vscode";
import { completionsState } from "../state/completionsState";
import { sendEvent } from "../binary/requests/sendEvent";

const RESUME_TABNINE = "Resume Tabnine";

export function showStatusBarNotificationOptions(
settingsButton: string,
onSettingsClicked: () => void
) {
const snoozeDuration = workspace
.getConfiguration("tabnine")
.get<number>("snoozeDuration", 1);

const snoozeTabnine = `Snooze Tabnine (${snoozeDuration}h)`;

const currentAction = completionsState.value ? snoozeTabnine : RESUME_TABNINE;

void window
.showInformationMessage("Tabnine options", settingsButton, currentAction)
.then((selection) => {
switch (selection) {
case settingsButton:
onSettingsClicked();
break;
case snoozeTabnine:
trackSnoozeToggled(false, snoozeDuration);
completionsState.value = false;
break;
case RESUME_TABNINE:
trackSnoozeToggled(true, snoozeDuration);
completionsState.value = true;
break;
default:
console.warn("Unexpected selection");
break;
}
});
}

function trackSnoozeToggled(showCompletions: boolean, duration: number) {
void sendEvent({
name: "snooze-toggled",
properties: {
show_completions: showCompletions.toString(),
duration: duration.toString(),
},
});
}

0 comments on commit c0655e1

Please sign in to comment.