Skip to content

Commit 8950ade

Browse files
committed
fix(amazonq): fix identation for edits multi-line suggestion
1 parent 1c2686f commit 8950ade

File tree

3 files changed

+84
-4
lines changed

3 files changed

+84
-4
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/**
7+
* Strips common indentation from each line of code that may contain HTML tags
8+
* @param lines Array of code lines (may contain HTML tags)
9+
* @returns Array of code lines with common indentation removed
10+
*/
11+
export function stripCommonIndentation(lines: string[]): string[] {
12+
if (lines.length === 0) {
13+
return lines
14+
}
15+
const removeFirstTag = (line: string) => line.replace(/^<[^>]*>/, '')
16+
const getLeadingWhitespace = (text: string) => text.match(/^\s*/)?.[0] || ''
17+
18+
// Find minimum indentation across all lines
19+
const minIndentLength = Math.min(...lines.map((line) => getLeadingWhitespace(removeFirstTag(line)).length))
20+
21+
// Remove common indentation from each line
22+
return lines.map((line) => {
23+
const firstTagRemovedLine = removeFirstTag(line)
24+
const leadingWhitespace = getLeadingWhitespace(firstTagRemovedLine)
25+
const reducedWhitespace = leadingWhitespace.substring(minIndentLength)
26+
return line.replace(leadingWhitespace, reducedWhitespace)
27+
})
28+
}

packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { diffWordsWithSpace, diffLines } from 'diff'
77
import * as vscode from 'vscode'
88
import { ToolkitError, getLogger } from 'aws-core-vscode/shared'
99
import { diffUtilities } from 'aws-core-vscode/shared'
10+
import { stripCommonIndentation } from './stringUtils'
1011
type Range = { line: number; start: number; end: number }
1112

1213
const logger = getLogger('nextEditPrediction')
@@ -78,6 +79,7 @@ export class SvgGenerationService {
7879

7980
const highlightRanges = this.generateHighlightRanges(removedLines, addedLines, modifiedLines)
8081
const diffAddedWithHighlight = this.getHighlightEdit(addedLines, highlightRanges.addedRanges)
82+
const normalizedDiffLines = stripCommonIndentation(diffAddedWithHighlight)
8183

8284
// Create SVG window, document, and container
8385
const window = createSVGWindow()
@@ -90,7 +92,7 @@ export class SvgGenerationService {
9092

9193
// Generate CSS for syntax highlighting HTML content based on theme
9294
const styles = this.generateStyles(currentTheme)
93-
const htmlContent = this.generateHtmlContent(diffAddedWithHighlight, styles, offset)
95+
const htmlContent = this.generateHtmlContent(normalizedDiffLines, styles, offset)
9496

9597
// Create foreignObject to embed HTML
9698
const foreignObject = draw.foreignObject(width + offset, height)
@@ -162,6 +164,9 @@ export class SvgGenerationService {
162164
white-space: pre-wrap; /* Preserve whitespace */
163165
background-color: ${diffAdded};
164166
}
167+
.diff-unchanged {
168+
white-space: pre-wrap; /* Preserve indentation for unchanged lines */
169+
}
165170
`
166171
}
167172

@@ -229,7 +234,7 @@ export class SvgGenerationService {
229234

230235
// If no ranges for this line, leave it as-is with HTML escaping
231236
if (lineRanges.length === 0) {
232-
result.push(this.escapeHtml(line))
237+
result.push(`<span class="diff-unchanged">${this.escapeHtml(line)}</span>`)
233238
continue
234239
}
235240

@@ -244,7 +249,7 @@ export class SvgGenerationService {
244249
// Add text before the current range (with HTML escaping)
245250
if (range.start > currentPos) {
246251
const beforeText = line.substring(currentPos, range.start)
247-
highlightedLine += this.escapeHtml(beforeText)
252+
highlightedLine += `<span class="diff-unchanged">${this.escapeHtml(beforeText)}</span>`
248253
}
249254

250255
// Add the highlighted part (with HTML escaping)
@@ -258,7 +263,7 @@ export class SvgGenerationService {
258263
// Add any remaining text after the last range (with HTML escaping)
259264
if (currentPos < line.length) {
260265
const afterText = line.substring(currentPos)
261-
highlightedLine += this.escapeHtml(afterText)
266+
highlightedLine += `<span class="diff-unchanged">${this.escapeHtml(afterText)}</span>`
262267
}
263268

264269
result.push(highlightedLine)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import assert from 'assert'
6+
import { stripCommonIndentation } from '../../../../../../src/app/inline/EditRendering/stringUtils'
7+
8+
describe('stripCommonIndentation', () => {
9+
it('should strip common leading whitespace', () => {
10+
const input = [' line1 ', ' line2 ', ' line3 ']
11+
const expected = ['line1 ', 'line2 ', ' line3 ']
12+
assert.deepStrictEqual(stripCommonIndentation(input), expected)
13+
})
14+
15+
it('should handle HTML tags', () => {
16+
const input = [
17+
'<span class="diff-unchanged> line1 </span>',
18+
'<span class="diff-changed> line2 </span>',
19+
]
20+
const expected = ['<span class="diff-unchanged> line1 </span>', '<span class="diff-changed>line2 </span>']
21+
assert.deepStrictEqual(stripCommonIndentation(input), expected)
22+
})
23+
24+
it('should handle mixed indentation', () => {
25+
const input = [' line1', ' line2', ' line3']
26+
const expected = ['line1', ' line2', ' line3']
27+
assert.deepStrictEqual(stripCommonIndentation(input), expected)
28+
})
29+
30+
it('should handle empty lines', () => {
31+
const input = [' line1', '', ' line2']
32+
const expected = [' line1', '', ' line2']
33+
assert.deepStrictEqual(stripCommonIndentation(input), expected)
34+
})
35+
36+
it('should handle no indentation', () => {
37+
const input = ['line1', 'line2']
38+
const expected = ['line1', 'line2']
39+
assert.deepStrictEqual(stripCommonIndentation(input), expected)
40+
})
41+
42+
it('should handle single line', () => {
43+
const input = [' single line']
44+
const expected = ['single line']
45+
assert.deepStrictEqual(stripCommonIndentation(input), expected)
46+
})
47+
})

0 commit comments

Comments
 (0)