Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(agent): normalize indentation in code completion postprocess #3361

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions clients/tabby-agent/src/codeCompletion/postprocess/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { trimMultiLineInSingleLineMode } from "./trimMultiLineInSingleLineMode";
import { dropDuplicated } from "./dropDuplicated";
import { dropMinimum } from "./dropMinimum";
import { calculateReplaceRange } from "./calculateReplaceRange";
import { normalizeIndentation } from "./normalizeIndentation";

type ItemListFilter = (items: CompletionItem[]) => Promise<CompletionItem[]>;

Expand Down Expand Up @@ -51,6 +52,7 @@ export async function postCacheProcess(
.then(applyFilter(limitScope))
.then(applyFilter(removeDuplicatedBlockClosingLine))
.then(applyFilter(formatIndentation))
.then(applyFilter(normalizeIndentation))
.then(applyFilter(dropDuplicated))
.then(applyFilter(trimSpace))
.then(applyFilter(dropMinimum))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { documentContext, inline, assertFilterResult } from "./testUtils";
import { normalizeIndentation } from "./normalizeIndentation";

describe("postprocess", () => {
describe("normalizeIndentation", () => {
const filter = normalizeIndentation();

it("should fix first line extra indentation", async () => {
const context = documentContext`
function test() {
}
`;
const completion = inline`
├ const x = 1;
const y = 2;┤
`;
const expected = inline`
├const x = 1;
const y = 2;┤
`;
await assertFilterResult(filter, context, completion, expected);
});
it("should remove extra indent", async () => {
const context = documentContext`
if (true) {
if (condition) {
}
}
`;
const completion = inline`
├doSomething();
doAnother();┤
`;
const expected = inline`
├doSomething();
doAnother();┤
`;
await assertFilterResult(filter, context, completion, expected);
});

it("should handle both inappropriate first line and extra indent case 01", async () => {
const context = documentContext`
if (true) {
if (condition) {
}
}
`;
const completion = inline`
├ doSomething();
doAnother();┤
`;
const expected = inline`
├doSomething();
doAnother();┤
`;
await assertFilterResult(filter, context, completion, expected);
});

it("should handle both inappropriate extra indent case 02", async () => {
const context = documentContext`
{
"command": "test",
}
`;
const completion = inline`
├"title": "Run Test",
"category": "Tabby"┤
`;
const expected = inline`
├"title": "Run Test",
"category": "Tabby"┤
`;
await assertFilterResult(filter, context, completion, expected);
});

it("should do nothing", async () => {
const context = documentContext`
function foo() {
}
`;
const completion = inline`
├ bar();┤
`;
const expected = inline`
├ bar();┤
`;
await assertFilterResult(filter, context, completion, expected);
});
it("should fix extra indent", async () => {
const context = documentContext`
{
}
`;
const completion = inline`
├"command": "tabby.inlineCompletion.trigger.automatic",
"title": "Trigger Code Completion Automatically",
"category": "Tabby"┤
`;
const expected = inline`
├"command": "tabby.inlineCompletion.trigger.automatic",
"title": "Trigger Code Completion Automatically",
"category": "Tabby"┤
`;
await assertFilterResult(filter, context, completion, expected);
});

it("shouldn't change indent", async () => {
const context = documentContext`
{
"command": "tabby.toggleInlineCompletionTriggerMode",
"title": "Toggle Code Completion Trigger Mode (Automatic/Manual)",
"category": "Tabby"
},
`;
const completion = inline`
├{
"command": "tabby.inlineCompletion.trigger",
"title": "Trigger Code Completion Manually",
"category": "Tabby"
},┤
`;
const expected = inline`
├{
"command": "tabby.inlineCompletion.trigger",
"title": "Trigger Code Completion Manually",
"category": "Tabby"
},┤
`;
await assertFilterResult(filter, context, completion, expected);
});
});
});
Sma1lboy marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { PostprocessFilter } from "./base";
import { CompletionItem } from "../solution";

function isOnlySpaces(str: string | null | undefined): boolean {
if (!str) return true;
return /^\s*$/.test(str);
}

function getLeadingSpaces(str: string): number {
const match = str.match(/^(\s*)/);
return match ? match[0].length : 0;
}

export function normalizeIndentation(): PostprocessFilter {
return (item: CompletionItem): CompletionItem => {
const { context, lines } = item;
if (!Array.isArray(lines) || lines.length === 0) return item;

// skip if current line has content
if (!isOnlySpaces(context.currentLinePrefix)) {
return item;
}

const normalizedLines = [...lines];
const firstLine = normalizedLines[0];
const cursorLineSpaces = getLeadingSpaces(context.currentLinePrefix);
if (firstLine) {
const firstLineSpaces = getLeadingSpaces(firstLine);
// deal with current cursor odd indentation
if ((firstLineSpaces + cursorLineSpaces) % 2 !== 0 && firstLineSpaces % 2 !== 0) {
normalizedLines[0] = firstLine.substring(firstLineSpaces);
}
}

//deal with extra space in the line indent
for (let i = 0; i < normalizedLines.length; i++) {
const line = normalizedLines[i];
if (!line || !line.trim()) continue;
const lineSpaces = getLeadingSpaces(line);
if (lineSpaces > 0 && lineSpaces % 2 !== 0) {
// move current line to recently close indent
normalizedLines[i] = " ".repeat(lineSpaces - 1) + line.trimStart();
}
}

return item.withText(normalizedLines.join(""));
};
}
Loading