Path: blob/main/src/vs/editor/contrib/inlineCompletions/test/browser/longDistanceWidgetPlacement.test.ts
4798 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';7import { Size2D } from '../../../../common/core/2d/size.js';8import { LineRange } from '../../../../common/core/ranges/lineRange.js';9import { WidgetLayoutConstants, WidgetPlacementContext, ContinuousLineSizes } from '../../browser/view/inlineEdits/inlineEditsViews/longDistanceHint/longDistnaceWidgetPlacement.js';1011suite('WidgetPlacementContext', () => {12ensureNoDisposablesAreLeakedInTestSuite();1314function createLineRangeInfo(startLine: number, sizes: Size2D[], top: number = 0): ContinuousLineSizes {15return {16lineRange: LineRange.ofLength(startLine, sizes.length),17top,18sizes,19};20}2122const defaultLayoutConstants: WidgetLayoutConstants = {23previewEditorMargin: 5,24widgetPadding: 2,25widgetBorder: 1,26lowerBarHeight: 10,27minWidgetWidth: 50,28};2930suite('constructor - availableSpaceSizes computation', () => {31test('computes available space sizes correctly with no padding', () => {32const sizes = [new Size2D(100, 20), new Size2D(150, 20), new Size2D(80, 20)];33const lineRangeInfo = createLineRangeInfo(1, sizes);34const editorTrueContentWidth = 500;35const endOfLinePadding = () => 0;3637const context = new WidgetPlacementContext(lineRangeInfo, editorTrueContentWidth, endOfLinePadding);3839assert.strictEqual(context.availableSpaceSizes.length, 3);40assert.strictEqual(context.availableSpaceSizes[0].width, 400); // 500 - 10041assert.strictEqual(context.availableSpaceSizes[1].width, 350); // 500 - 15042assert.strictEqual(context.availableSpaceSizes[2].width, 420); // 500 - 8043});4445test('computes available space sizes with end of line padding', () => {46const sizes = [new Size2D(100, 20), new Size2D(150, 20)];47const lineRangeInfo = createLineRangeInfo(1, sizes);48const editorTrueContentWidth = 500;49const endOfLinePadding = (lineNumber: number) => lineNumber * 10;5051const context = new WidgetPlacementContext(lineRangeInfo, editorTrueContentWidth, endOfLinePadding);5253assert.strictEqual(context.availableSpaceSizes[0].width, 390); // 500 - 100 - 1054assert.strictEqual(context.availableSpaceSizes[1].width, 330); // 500 - 150 - 2055});5657test('available space width is never negative', () => {58const sizes = [new Size2D(600, 20)];59const lineRangeInfo = createLineRangeInfo(1, sizes);60const editorTrueContentWidth = 500;61const endOfLinePadding = () => 0;6263const context = new WidgetPlacementContext(lineRangeInfo, editorTrueContentWidth, endOfLinePadding);6465assert.strictEqual(context.availableSpaceSizes[0].width, 0);66});6768test('preserves heights in available space sizes', () => {69const sizes = [new Size2D(100, 25), new Size2D(100, 30), new Size2D(100, 20)];70const lineRangeInfo = createLineRangeInfo(1, sizes);71const editorTrueContentWidth = 500;72const endOfLinePadding = () => 0;7374const context = new WidgetPlacementContext(lineRangeInfo, editorTrueContentWidth, endOfLinePadding);7576assert.strictEqual(context.availableSpaceSizes[0].height, 25);77assert.strictEqual(context.availableSpaceSizes[1].height, 30);78assert.strictEqual(context.availableSpaceSizes[2].height, 20);79});80});8182suite('constructor - prefix sums computation', () => {83test('computes height prefix sums correctly', () => {84const sizes = [new Size2D(100, 20), new Size2D(100, 30), new Size2D(100, 25)];85const lineRangeInfo = createLineRangeInfo(1, sizes);8687const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);8889assert.deepStrictEqual(context.availableSpaceHeightPrefixSums, [0, 20, 50, 75]);90});9192test('prefix sums start with 0 and have length = sizes.length + 1', () => {93const sizes = [new Size2D(100, 10), new Size2D(100, 20)];94const lineRangeInfo = createLineRangeInfo(1, sizes);9596const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);9798assert.strictEqual(context.availableSpaceHeightPrefixSums[0], 0);99assert.strictEqual(context.availableSpaceHeightPrefixSums.length, 3);100});101});102103suite('constructor - transposed sizes', () => {104test('transposes width and height correctly', () => {105const sizes = [new Size2D(100, 20), new Size2D(150, 30)];106const lineRangeInfo = createLineRangeInfo(1, sizes);107108const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);109110// Transposed: width becomes height and vice versa111// Available widths are 400 and 350, heights are 20 and 30112assert.strictEqual(context.availableSpaceSizesTransposed[0].width, 20);113assert.strictEqual(context.availableSpaceSizesTransposed[0].height, 400);114assert.strictEqual(context.availableSpaceSizesTransposed[1].width, 30);115assert.strictEqual(context.availableSpaceSizesTransposed[1].height, 350);116});117});118119suite('getWidgetVerticalOutline', () => {120test('computes vertical outline for first line', () => {121const sizes = [new Size2D(100, 20), new Size2D(100, 20)];122const lineRangeInfo = createLineRangeInfo(1, sizes, 100);123124const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);125const outline = context.getWidgetVerticalOutline(1, 50, defaultLayoutConstants);126127// previewEditorMargin + widgetPadding + widgetBorder = 5 + 2 + 1 = 8128// editorRange = [100, 150)129// verticalWidgetRange = [100 - 8, 150 + 8 + 10) = [92, 168)130assert.strictEqual(outline.start, 92);131assert.strictEqual(outline.endExclusive, 168);132});133134test('computes vertical outline for second line', () => {135const sizes = [new Size2D(100, 20), new Size2D(100, 25)];136const lineRangeInfo = createLineRangeInfo(1, sizes, 100);137138const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);139const outline = context.getWidgetVerticalOutline(2, 50, defaultLayoutConstants);140141// Line 2 is at index 1, prefixSum[1] = 20142// top = 100 + 20 = 120143// editorRange = [120, 170)144// margin = 8, lowerBarHeight = 10145// verticalWidgetRange = [120 - 8, 170 + 8 + 10) = [112, 188)146assert.strictEqual(outline.start, 112);147assert.strictEqual(outline.endExclusive, 188);148});149150test('works with zero margins', () => {151const sizes = [new Size2D(100, 20)];152const lineRangeInfo = createLineRangeInfo(1, sizes, 0);153const zeroConstants: WidgetLayoutConstants = {154previewEditorMargin: 0,155widgetPadding: 0,156widgetBorder: 0,157lowerBarHeight: 0,158minWidgetWidth: 50,159};160161const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);162const outline = context.getWidgetVerticalOutline(1, 50, zeroConstants);163164assert.strictEqual(outline.start, 0);165assert.strictEqual(outline.endExclusive, 50);166});167});168169suite('tryFindWidgetOutline', () => {170test('returns undefined when no line has enough width', () => {171// All lines have content that leaves less than minWidgetWidth172const sizes = [new Size2D(460, 20), new Size2D(470, 20), new Size2D(480, 20)];173const lineRangeInfo = createLineRangeInfo(1, sizes);174175const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);176const result = context.tryFindWidgetOutline(2, 15, 500, defaultLayoutConstants);177178assert.strictEqual(result, undefined);179});180181test('finds widget outline on target line when it has enough space', () => {182const sizes = [new Size2D(100, 20), new Size2D(100, 20), new Size2D(100, 20)];183const lineRangeInfo = createLineRangeInfo(1, sizes, 0);184185const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);186const result = context.tryFindWidgetOutline(2, 15, 500, defaultLayoutConstants);187188assert.ok(result !== undefined);189assert.ok(result.horizontalWidgetRange.length >= defaultLayoutConstants.minWidgetWidth);190});191192test('searches outward from target line', () => {193// First and last lines are excluded from placement194// Lines 2, 3 have no space, line 4 has space195const sizes = [196new Size2D(100, 20), // line 1 - excluded (first)197new Size2D(460, 20), // line 2 - no space198new Size2D(460, 20), // line 3 - no space (target)199new Size2D(100, 20), // line 4 - has space200new Size2D(100, 20), // line 5 - has space201new Size2D(100, 20), // line 6 - has space202new Size2D(100, 20), // line 7 - excluded (last)203];204const lineRangeInfo = createLineRangeInfo(1, sizes, 0);205206const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);207// Target is line 3, but it should find line 4 (searching outward)208const result = context.tryFindWidgetOutline(3, 15, 500, defaultLayoutConstants);209210assert.ok(result !== undefined);211});212213test('prefers closer lines to target', () => {214const sizes = [215new Size2D(100, 20), // line 0 - excluded (first)216new Size2D(100, 20), // line 1 - has space217new Size2D(100, 20), // line 2 - has space218new Size2D(100, 20), // line 3 - has space219new Size2D(500, 9999),// line 4 - no space (target)220new Size2D(100, 20), // line 5 - has space221new Size2D(100, 20), // line 6 - has space222new Size2D(100, 20), // line 7 - has space223new Size2D(100, 20), // line 8 - excluded (last)224];225const lineRangeInfo = createLineRangeInfo(1, sizes, 0);226227for (let targetLine = 0; targetLine <= 4; targetLine++) {228const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);229const result = context.tryFindWidgetOutline(targetLine, 15, 500, defaultLayoutConstants);230assert.ok(result !== undefined);231assert.ok(result.verticalWidgetRange.endExclusive < 9999);232}233234for (let targetLine = 5; targetLine <= 10 /* test outside line range */; targetLine++) {235const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);236const result = context.tryFindWidgetOutline(targetLine, 15, 500, defaultLayoutConstants);237assert.ok(result !== undefined);238assert.ok(result.verticalWidgetRange.start > 9999);239}240});241242test('horizontal widget range ends at editor content right', () => {243const sizes = [new Size2D(100, 20), new Size2D(100, 20), new Size2D(100, 20)];244const lineRangeInfo = createLineRangeInfo(1, sizes, 0);245const editorTrueContentRight = 500;246247const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);248const result = context.tryFindWidgetOutline(2, 15, editorTrueContentRight, defaultLayoutConstants);249250assert.ok(result !== undefined);251assert.strictEqual(result.horizontalWidgetRange.endExclusive, editorTrueContentRight);252});253});254255suite('edge cases', () => {256test('handles single line range', () => {257const sizes = [new Size2D(100, 20)];258const lineRangeInfo = createLineRangeInfo(5, sizes, 50);259260const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);261262assert.strictEqual(context.availableSpaceSizes.length, 1);263assert.deepStrictEqual(context.availableSpaceHeightPrefixSums, [0, 20]);264});265266test('handles empty content lines (width 0)', () => {267const sizes = [new Size2D(0, 20), new Size2D(0, 20)];268const lineRangeInfo = createLineRangeInfo(1, sizes);269270const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);271272assert.strictEqual(context.availableSpaceSizes[0].width, 500);273assert.strictEqual(context.availableSpaceSizes[1].width, 500);274});275276test('handles varying line heights', () => {277const sizes = [new Size2D(100, 10), new Size2D(100, 30), new Size2D(100, 20)];278const lineRangeInfo = createLineRangeInfo(1, sizes, 100);279280const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);281282// Verify prefix sums account for varying heights283assert.deepStrictEqual(context.availableSpaceHeightPrefixSums, [0, 10, 40, 60]);284});285286test('handles very large line numbers', () => {287const sizes = [new Size2D(100, 20)];288const lineRangeInfo = createLineRangeInfo(10000, sizes, 0);289290const context = new WidgetPlacementContext(lineRangeInfo, 500, () => 0);291292const outline = context.getWidgetVerticalOutline(10000, 50, defaultLayoutConstants);293assert.ok(outline !== undefined);294});295});296});297298299