Skip to content

Commit

Permalink
feat: proposed api enable (#729)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimacodota authored Mar 10, 2022
1 parent 9d28e4d commit d0a3408
Show file tree
Hide file tree
Showing 24 changed files with 2,417 additions and 79 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = {
env: {
node: true,
es2021: true,
jest: true,
},
extends: [
"airbnb-typescript/base",
Expand All @@ -20,6 +21,7 @@ module.exports = {
tsconfigRootDir: __dirname,
},
plugins: ["@typescript-eslint", "import"],
ignorePatterns: ["vscode.proposed.inlineCompletions.d.ts"],
rules: {
"no-void": "off",
"no-console": "off",
Expand Down
8 changes: 8 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
transform: {
"^.+\\.ts?$": "ts-jest",
},
testRegex: "(/__tests__/.*|\\.(test|spec))\\.[tj]sx?$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
testEnvironment: "node",
};
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
"eslint-plugin-import": "^2.22.1",
"glob": "^7.1.6",
"husky": "^5.1.2",
"jest": "^27.4.7",
"lint-staged": "^10.5.4",
"mocha": "^8.3.2",
"mocha-teamcity-reporter": "^3.0.0",
Expand All @@ -152,6 +153,7 @@
"rimraf": "^3.0.2",
"sinon": "^10.0.0",
"terser-webpack-plugin": "^5.1.1",
"ts-jest": "^27.1.3",
"ts-loader": "^9.0.0",
"ts-mockito": "^2.6.1",
"typescript": "^4.2.2",
Expand Down Expand Up @@ -554,5 +556,8 @@
},
"lint-staged": {
"*.{ts,js,css,md}": "prettier --write src/"
}
},
"enabledApiProposals": [
"inlineCompletions"
]
}
18 changes: 5 additions & 13 deletions src/binary/requests/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { State } from "../state";
import { StateType } from "../../globals/consts";
import { SaveSnippetRequest, SaveSnippetResponse } from "./saveSnippet";

const DEFAULT_SNIPPET_TIMEOUT = 5000;

export const tabNineProcess = new Binary();

export type MarkdownStringSpec = {
Expand All @@ -31,6 +29,7 @@ export type ResultEntry = {
documentation?: string | MarkdownStringSpec;
deprecated?: boolean;
completion_kind?: CompletionKind;
is_cached?: boolean;
};

export type AutocompleteResult = {
Expand Down Expand Up @@ -70,21 +69,14 @@ export type SnippetAutocompleteParams = AutocompleteParams & {
};

export function autocomplete(
requestData: AutocompleteParams
): Promise<AutocompleteResult | undefined | null> {
return tabNineProcess.request<AutocompleteResult | undefined | null>({
Autocomplete: requestData,
});
}

export function autocompleteSnippet(
requestData: AutocompleteParams
requestData: AutocompleteParams,
timeout?: number
): Promise<AutocompleteResult | undefined | null> {
return tabNineProcess.request<AutocompleteResult | undefined | null>(
{
AutocompleteSnippet: requestData,
Autocomplete: requestData,
},
DEFAULT_SNIPPET_TIMEOUT
timeout
);
}

Expand Down
7 changes: 6 additions & 1 deletion src/binary/requests/setState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export type HintShownRequest = {
};
};

export type SnippetShownRequest = {
SnippetShown: Record<string, never>;
};

export type SelectionStateRequest = {
Selection: {
// the file extension: rs | js etc.
Expand Down Expand Up @@ -124,7 +128,8 @@ export type StateRequest =
| NotificationShownRequest
| StatusShownRequest
| HoverShownRequest
| HintShownRequest;
| HintShownRequest
| SnippetShownRequest;

export default function setState(state: StateRequest): Promise<unknown> {
return tabNineProcess.request({ SetState: { state_type: state } });
Expand Down
1 change: 1 addition & 0 deletions src/capabilities/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum Capability {
NOTIFICATIONS_WIDGET = "vscode.notifications-widget",
TABNINE_TODAY_WIDGET = "vscode.tabnine-today-widget",
SAVE_SNIPPETS = "save_snippets",
BETA_CAPABILITY = "beta",
}

const enabledCapabilities: Record<string, boolean> = {};
Expand Down
1 change: 1 addition & 0 deletions src/globals/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export enum StatePayload {
STATUS_SHOWN = "StatusShown",
HOVER_SHOWN = "HoverShown",
HINT_SHOWN = "HintShown",
SNIPPET_SHOWN = "SnippetShown",
}
export enum MessageActions {
NONE = "None",
Expand Down
72 changes: 72 additions & 0 deletions src/globals/proposedAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { promises as fs } from "fs";
import * as vscode from "vscode";
import * as path from "path";
import * as os from "os";
import showMessage from "../preRelease/messages";

const EXTENSION_ID = "TabNine.tabnine-vscode";
const ARGV_FILE_NAME = "argv.json";
const PRODUCT_FILE_NAME = "product.json";
const PRODUCT_FILE_PATH = path.join(vscode.env.appRoot, PRODUCT_FILE_NAME);
const ENABLE_PROPOSED_API = [
"",
` "enable-proposed-api": ["${EXTENSION_ID}"]`,
"}",
];

export default async function enableProposed(): Promise<boolean> {
return handleProposed().catch((error) => {
console.error("failed to enable proposedAPI", error);
return false;
});
}

async function getDataFolderName(): Promise<string | undefined> {
const data = await fs.readFile(PRODUCT_FILE_PATH);
const file = JSON.parse(data.toString("utf8")) as {
dataFolderName?: string;
};
return file?.dataFolderName;
}

function getArgvResource(dataFolderName: string): string {
const vscodePortable = process.env.VSCODE_PORTABLE;
if (vscodePortable) {
return path.join(vscodePortable, ARGV_FILE_NAME);
}

return path.join(os.homedir(), dataFolderName, ARGV_FILE_NAME);
}
async function handleProposed(): Promise<boolean> {
const dataFolderName = await getDataFolderName();

if (dataFolderName) {
const argvResource = getArgvResource(dataFolderName);
const argvString = (await fs.readFile(argvResource)).toString();

if (argvString.includes(`${EXTENSION_ID}`)) {
return true;
}

const modifiedArgvString = modifyArgvFileContent(argvString);
await fs.writeFile(argvResource, Buffer.from(modifiedArgvString));
askForReload();
}
return false;
}

function askForReload() {
void showMessage({
messageId: "inline-update",
messageText: `Please reload the window for the Tabnine inline completions to take effect.`,
buttonText: "Reload",
action: () =>
void vscode.commands.executeCommand("workbench.action.reloadWindow"),
});
}

function modifyArgvFileContent(argvString: string) {
return argvString
.substring(0, argvString.length - 2)
.concat(",\n", ENABLE_PROPOSED_API.join("\n"));
}
5 changes: 5 additions & 0 deletions src/globals/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import * as semver from "semver";
import tabnineExtensionProperties from "./tabnineExtensionProperties";

const AUTHENTICATION_API_VERSION = "1.54.0";
const INLINE_API = "1.58.0";

export default function isAuthenticationApiSupported(): boolean {
return semver.gte(
tabnineExtensionProperties.vscodeVersion,
AUTHENTICATION_API_VERSION
);
}

export function isInlineSuggestionApiSupported(): boolean {
return semver.gte(tabnineExtensionProperties.vscodeVersion, INLINE_API);
}
43 changes: 43 additions & 0 deletions src/inlineSuggestions/registerHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import {
Disposable,
ExtensionContext,
ExtensionMode,
languages,
TextEditor,
TextEditorSelectionChangeEvent,
TextEditorSelectionChangeKind,
window,
workspace,
} from "vscode";
import { CompletionKind } from "../binary/requests/requests";
import setState from "../binary/requests/setState";
import { Capability, isCapabilityEnabled } from "../capabilities/capabilities";
import getSuggestionMode, {
SuggestionsMode,
Expand All @@ -19,7 +22,11 @@ import {
NEXT_INLINE_COMMAND,
PREV_INLINE_COMMAND,
SNIPPET_COMMAND,
StatePayload,
} from "../globals/consts";
import enableProposed from "../globals/proposedAPI";
import provideInlineCompletionItems from "../provideInlineCompletionItems";
import { initTracker } from "./stateTracker";
import acceptInlineSuggestion from "./acceptInlineSuggestion";
import clearInlineSuggestionsState from "./clearDecoration";
import { getNextSuggestion, getPrevSuggestion } from "./inlineSuggestionState";
Expand All @@ -30,6 +37,7 @@ import snippetAutoTriggerHandler from "./snippets/autoTriggerHandler";
import { isInSnippetInsertion } from "./snippets/blankSnippet";
import requestSnippet from "./snippets/snippetProvider";
import textListener from "./textListener";
import { isInlineSuggestionApiSupported } from "../globals/versions";

export const decorationType = window.createTextEditorDecorationType({});

Expand All @@ -51,6 +59,13 @@ function isSnippetAutoTriggerEnabled() {
return isCapabilityEnabled(Capability.SNIPPET_AUTO_TRIGGER);
}

async function isDefaultAPIEnabled(): Promise<boolean> {
return (
isCapabilityEnabled(Capability.BETA_CAPABILITY) &&
isInlineSuggestionApiSupported() &&
(await enableProposed())
);
}
export default async function registerInlineHandlers(
context: ExtensionContext
): Promise<void> {
Expand All @@ -59,6 +74,34 @@ export default async function registerInlineHandlers(

if (!inlineEnabled && !snippetsEnabled) return;

if (await isDefaultAPIEnabled()) {
const inlineCompletionsProvider = {
provideInlineCompletionItems,
};
context.subscriptions.push(
languages.registerInlineCompletionItemProvider(
{ pattern: "**" },
inlineCompletionsProvider
),
...initTracker()
);
window
.getInlineCompletionItemController(inlineCompletionsProvider)
.onDidShowCompletionItem((e) => {
// binary is not supporting api version ^4.0.57
if (e.completionItem.isCached === undefined) return;

const shouldSendSnippetShown =
e.completionItem.completionKind === CompletionKind.Snippet &&
!e.completionItem.isCached;

if (shouldSendSnippetShown) {
void setState({ [StatePayload.SNIPPET_SHOWN]: {} });
}
});
return;
}

if (inlineEnabled) {
await enableInlineSuggestionsContext();
registerTextChangeHandler();
Expand Down
6 changes: 4 additions & 2 deletions src/inlineSuggestions/snippets/autoTriggerHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { EOL } from "os";
import { TextDocumentChangeEvent } from "vscode";
import { SnippetRequestTrigger } from "../../binary/requests/requests";
import getCurrentPosition from "../positionExtracter";
import { isInSnippetInsertion } from "./blankSnippet";
import requestSnippet from "./snippetProvider";
Expand All @@ -10,11 +9,14 @@ export default async function snippetAutoTriggerHandler({
contentChanges,
}: TextDocumentChangeEvent): Promise<void> {
const [change] = contentChanges;
if (!change) {
return;
}
const position = getCurrentPosition(change);
const hasNewlines = change.text.includes(EOL);
const currentLineIsEmpty = document.lineAt(position.line).text.trim() === "";

if (!isInSnippetInsertion() && hasNewlines && currentLineIsEmpty) {
await requestSnippet(document, position, SnippetRequestTrigger.Auto);
await requestSnippet(document, position);
}
}
11 changes: 2 additions & 9 deletions src/inlineSuggestions/snippets/snippetProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,12 @@ import {
} from "../inlineSuggestionState";
import runCompletion from "../../runCompletion";
import setInlineSuggestion from "../setInlineSuggestion";
import { SnippetRequestTrigger } from "../../binary/requests/requests";

export default async function requestSnippet(
document: TextDocument,
position: Position,
trigger: SnippetRequestTrigger = SnippetRequestTrigger.User
position: Position
): Promise<void> {
const autocompleteResult = await runCompletion(
document,
position,
"snippet",
trigger
);
const autocompleteResult = await runCompletion(document, position);

const currentUri = window.activeTextEditor?.document.uri;
if (currentUri !== document.uri) {
Expand Down
36 changes: 36 additions & 0 deletions src/inlineSuggestions/stateTracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Disposable, TextDocumentChangeEvent, window, workspace } from "vscode";

let shouldComplete = false;
let change = false;

function onChange(): void {
change = true;
}

function onTextSelectionChange(): void {
if (change) {
shouldComplete = true;
change = false;
} else {
shouldComplete = false;
}
}
export function getShouldComplete(): boolean {
return shouldComplete;
}

export function initTracker(): Disposable[] {
return [
workspace.onDidChangeTextDocument(
({ contentChanges }: TextDocumentChangeEvent) => {
const contentChange = contentChanges[0];
const changeHappened =
contentChange?.rangeLength >= 0 && contentChange?.text !== "";
if (changeHappened) {
onChange();
}
}
),
window.onDidChangeTextEditorSelection(onTextSelectionChange),
];
}
Loading

0 comments on commit d0a3408

Please sign in to comment.