-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(agent): normalize indentation in code completion postprocess (#3361)
* refactor: normalize indentation in code completion postprocess * refactor: normalize indentation in code completion postprocess * refactor: fix indentation in code completion postprocess
- Loading branch information
Showing
3 changed files
with
189 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
139 changes: 139 additions & 0 deletions
139
clients/tabby-agent/src/codeCompletion/postprocess/normalizeIndentation.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); | ||
}); |
48 changes: 48 additions & 0 deletions
48
clients/tabby-agent/src/codeCompletion/postprocess/normalizeIndentation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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("")); | ||
}; | ||
} |