-
Notifications
You must be signed in to change notification settings - Fork 358
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
feat(auto-edit): Support unified diff and refactor diff format #7000
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
75d538e
init
umpox 1afbb74
more
umpox dc56e79
support unified diffs
umpox d22a26e
add heuristic to group deletions/additions
umpox 79443d3
show line and character diff colors for unified diff
umpox f531051
blockify
umpox fcfeb7f
fix ranges
umpox 3a5b96c
fix wrong line color on unchanged lines
umpox 66d5645
make background line color match available space
umpox 855a6dd
update colors
umpox 84951e4
refactor
umpox 0d555b5
Merge branch 'main' into tr/image-unified-diff
umpox 87f42a2
refactor
umpox 0a128d6
clean
umpox f8b93c6
ci
umpox 22a5269
Merge branch 'main' into tr/image-unified-diff
umpox d640aee
better diff handling
umpox 247f27b
clean
umpox 23d8ffd
clean up
umpox 77f1d5b
Merge branch 'main' into tr/image-unified-diff
umpox f2aa777
address feedback
umpox File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
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
Binary file removed
BIN
-21.6 KB
...utoedits/renderer/image-gen/__image_snapshots__/highlighted-suggestion-dark.png
Binary file not shown.
Binary file removed
BIN
-21.3 KB
...toedits/renderer/image-gen/__image_snapshots__/highlighted-suggestion-light.png
Binary file not shown.
Binary file removed
BIN
-22.5 KB
...oedits/renderer/image-gen/__image_snapshots__/unhighlighted-suggestion-dark.png
Binary file not shown.
Binary file removed
BIN
-22.7 KB
...edits/renderer/image-gen/__image_snapshots__/unhighlighted-suggestion-light.png
Binary file not shown.
177 changes: 177 additions & 0 deletions
177
vscode/src/autoedits/renderer/image-gen/canvas/draw-decorations.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,177 @@ | ||
import type { EmulatedCanvas2D } from 'canvaskit-wasm' | ||
import { canvasKit, fontCache } from '.' | ||
import type { DiffMode } from '..' | ||
import type { VisualDiff, VisualDiffLine } from '../decorated-diff/types' | ||
import { DEFAULT_HIGHLIGHT_COLORS } from '../highlight/constants' | ||
import type { SYNTAX_HIGHLIGHT_THEME } from '../highlight/types' | ||
import { type RenderConfig, type UserProvidedRenderConfig, getRenderConfig } from './render-config' | ||
import type { RenderContext } from './types' | ||
import { getRangesToHighlight } from './utils' | ||
|
||
function createCanvas( | ||
options: { | ||
width: number | ||
height: number | ||
fontSize: number | ||
scale?: number | ||
backgroundColor?: string | ||
}, | ||
context: RenderContext | ||
): { | ||
canvas: EmulatedCanvas2D | ||
ctx: CanvasRenderingContext2D | ||
} { | ||
const { width, height, fontSize, scale, backgroundColor } = options | ||
const canvas = context.CanvasKit.MakeCanvas(width, height) | ||
canvas.loadFont(context.font, { family: 'DejaVuSansMono' }) | ||
const ctx = canvas.getContext('2d') | ||
if (!ctx) { | ||
throw new Error('Failed to get 2D context') | ||
} | ||
ctx.font = `${fontSize}px DejaVuSansMono` | ||
if (scale) { | ||
ctx.scale(scale, scale) | ||
} | ||
if (backgroundColor) { | ||
ctx.fillStyle = backgroundColor | ||
ctx.fillRect(0, 0, width, height) | ||
} | ||
return { canvas, ctx } | ||
} | ||
|
||
function drawText( | ||
ctx: CanvasRenderingContext2D, | ||
line: VisualDiffLine, | ||
position: { x: number; y: number }, | ||
mode: SYNTAX_HIGHLIGHT_THEME, | ||
config: RenderConfig | ||
): number { | ||
const highlights = line.syntaxHighlights[mode] | ||
|
||
// Handle case with no syntax highlighting | ||
if (highlights.length === 0) { | ||
// No syntax highlighting, we probably don't support this language via Shiki | ||
// Default to white or black depending on the theme | ||
ctx.fillStyle = DEFAULT_HIGHLIGHT_COLORS[mode] | ||
ctx.fillText(line.text, position.x, position.y + config.fontSize) | ||
return ctx.measureText(line.text).width | ||
} | ||
|
||
// Draw highlighted text segments | ||
let xPos = position.x | ||
for (const { range, color } of highlights) { | ||
const [start, end] = range | ||
const content = line.text.substring(start, end) | ||
ctx.fillStyle = color | ||
ctx.fillText(content, xPos, position.y + config.fontSize) | ||
xPos += ctx.measureText(content).width | ||
} | ||
|
||
return xPos | ||
} | ||
|
||
function drawDiffColors( | ||
ctx: CanvasRenderingContext2D, | ||
line: VisualDiffLine, | ||
position: { x: number; y: number }, | ||
mode: DiffMode, | ||
config: RenderConfig | ||
): void { | ||
const isRemoval = line.type === 'removed' || line.type === 'modified-removed' | ||
const diffColors = isRemoval ? config.diffColors.removed : config.diffColors.inserted | ||
|
||
// For unified diffs, we want to ensure that changed lines also have a background color | ||
if (mode === 'unified' && line.type !== 'unchanged') { | ||
const endOfLine = ctx.measureText(line.text).width | ||
ctx.fillStyle = diffColors.line | ||
ctx.fillRect(position.x, position.y, endOfLine, config.lineHeight) | ||
} | ||
|
||
// Get ranges to highlight based on line type | ||
const ranges = getRangesToHighlight(line) | ||
if (ranges.length === 0) { | ||
return | ||
} | ||
|
||
// Draw highlights for each range | ||
ctx.fillStyle = diffColors.text | ||
for (const [start, end] of ranges) { | ||
const preHighlightWidth = ctx.measureText(line.text.slice(0, start)).width | ||
const highlightWidth = ctx.measureText(line.text.slice(start, end)).width | ||
ctx.fillRect(position.x + preHighlightWidth, position.y, highlightWidth, config.lineHeight) | ||
} | ||
} | ||
|
||
export function drawDecorationsToCanvas( | ||
diff: VisualDiff, | ||
theme: SYNTAX_HIGHLIGHT_THEME, | ||
mode: DiffMode, | ||
userConfig: UserProvidedRenderConfig | ||
): EmulatedCanvas2D { | ||
if (!canvasKit || !fontCache) { | ||
// TODO: Log these errors, useful to see if we run into issues where we're not correctly | ||
// initializing the canvas | ||
throw new Error('Canvas not initialized') | ||
} | ||
|
||
const context: RenderContext = { | ||
CanvasKit: canvasKit, | ||
font: fontCache, | ||
} | ||
const config = getRenderConfig(userConfig) | ||
|
||
// In order for us to draw to the canvas, we must first determine the correct | ||
// dimensions for the canvas. We can do this with a temporary Canvas that uses the same font | ||
const { ctx: tempCtx } = createCanvas({ height: 10, width: 10, fontSize: config.fontSize }, context) | ||
|
||
// Iterate through each token line, and determine the required width of the canvas (maximum line length) | ||
// and the required height of the canvas (number of lines determined by their line height) | ||
let tempYPos = config.padding.y | ||
let requiredWidth = 0 | ||
for (const line of diff.lines) { | ||
const measure = tempCtx.measureText(line.text) | ||
requiredWidth = Math.max(requiredWidth, config.padding.x + measure.width) | ||
tempYPos += config.lineHeight | ||
} | ||
|
||
// Note: We limit the canvas width to avoid the image getting excessively large. | ||
// We should consider possible strategies here, such as tweaking this value or refusing | ||
// to show image decorations for such large images. This could possibly be an area where we would | ||
// prefer an inline decorator. | ||
const canvasWidth = Math.min(requiredWidth + config.padding.x, config.maxWidth) | ||
const canvasHeight = tempYPos + config.padding.y | ||
|
||
// Round to the nearest pixel, using sub-pixels will cause CanvasKit to crash | ||
const height = Math.round(canvasHeight * config.pixelRatio) | ||
const width = Math.round(canvasWidth * config.pixelRatio) | ||
|
||
// Now we create the actual canvas, ensuring we scale it accordingly to improve the output resolution. | ||
const { canvas, ctx } = createCanvas( | ||
{ | ||
height, | ||
width, | ||
fontSize: config.fontSize, | ||
// We upscale the canvas to improve resolution, this will be brought back to the intended size | ||
// using the `scale` CSS property when the decoration is rendered. | ||
scale: config.pixelRatio, | ||
backgroundColor: config.backgroundColor?.[theme], | ||
}, | ||
context | ||
) | ||
|
||
// Paint text and colors onto the canvas | ||
let yPos = config.padding.y | ||
for (const line of diff.lines) { | ||
const position = { x: config.padding.x, y: yPos } | ||
|
||
// Paint any background diff colors first, we will render the text over the top | ||
drawDiffColors(ctx, line, position, mode, config) | ||
|
||
// Draw the text, this may or may not be syntax highlighted depending on language support | ||
drawText(ctx, line, position, theme, config) | ||
|
||
yPos += config.lineHeight | ||
} | ||
|
||
return canvas | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should figure out why Sentry error reporting isn't working right now. It'd be helpful to check if this issue occurs on users' machines.