Skip to content
Merged
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
32 changes: 32 additions & 0 deletions extensions/mssql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2151,6 +2151,38 @@
"description": "%mssql.resultsGrid.inMemoryDataProcessingThreshold%",
"minimum": 1
},
"mssql.resultsGrid.alternatingRowColors": {
"type": "boolean",
"default": false,
"description": "%mssql.resultsGrid.alternatingRowColors%"
},
"mssql.resultsGrid.showGridLines": {
"type": "string",
"default": "both",
"enum": [
"both",
"horizontal",
"vertical",
"none"
],
"enumDescriptions": [
"%mssql.resultsGrid.showGridLines.both%",
"%mssql.resultsGrid.showGridLines.horizontal%",
"%mssql.resultsGrid.showGridLines.vertical%",
"%mssql.resultsGrid.showGridLines.none%"
],
"description": "%mssql.resultsGrid.showGridLines%"
},
"mssql.resultsGrid.rowPadding": {
"type": [
"number",
"null"
],
"default": null,
"minimum": 0,
"maximum": 10,
"description": "%mssql.resultsGrid.rowPadding%"
},
"mssql.query.displayBitAsNumber": {
"type": "boolean",
"default": true,
Expand Down
7 changes: 7 additions & 0 deletions extensions/mssql/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@
"mssql.resultsGrid.useHeaderOnlyEnumDescription": "Use only column headers to size columns automatically",
"mssql.resultsGrid.disableAutoSizingEnumDescription": "Disable automatic column sizing",
"mssql.resultsGrid.inMemoryDataProcessingThreshold": "Controls the max number of rows allowed to do filtering and sorting in memory. If the number is exceeded, sorting and filtering will be disabled. Warning: Increasing this may impact performance.",
"mssql.resultsGrid.alternatingRowColors": "Enable alternating row background colors (zebra striping) in the results grid. The color is derived from the current VS Code theme.",
"mssql.resultsGrid.showGridLines": "Control which grid lines are visible in the results grid.",
"mssql.resultsGrid.showGridLines.both": "Show both horizontal and vertical grid lines",
"mssql.resultsGrid.showGridLines.horizontal": "Show only horizontal grid lines",
"mssql.resultsGrid.showGridLines.vertical": "Show only vertical grid lines",
"mssql.resultsGrid.showGridLines.none": "Hide all grid lines",
"mssql.resultsGrid.rowPadding": "Extra vertical padding in pixels added to each row in the results grid, controlling row density. Valid range: 0-10.",
"mssql.query.displayBitAsNumber": "Should BIT columns be displayed as numbers (1 or 0)? If false, BIT columns will be displayed as 'true' or 'false'",
"mssql.intelliSense.enableIntelliSense": "Should IntelliSense be enabled",
"mssql.intelliSense.enableErrorChecking": "Should IntelliSense error checking be enabled",
Expand Down
3 changes: 3 additions & 0 deletions extensions/mssql/src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ export const configOpenQueryResultsInTabByDefaultDoNotShowPrompt =
export const configAutoColumnSizingMode = "resultsGrid.autoSizeColumnsMode";
export const configInMemoryDataProcessingThreshold =
"mssql.resultsGrid.inMemoryDataProcessingThreshold";
export const configResultsGridAlternatingRowColors = "resultsGrid.alternatingRowColors";
export const configResultsGridShowGridLines = "resultsGrid.showGridLines";
export const configResultsGridRowPadding = "resultsGrid.rowPadding";
export const configAutoDisableNonTSqlLanguageService = "mssql.autoDisableNonTSqlLanguageService";
export const copilotDebugLogging = "mssql.copilotDebugLogging";
export const configSelectedAzureSubscriptions = "mssql.selectedAzureSubscriptions";
Expand Down
75 changes: 59 additions & 16 deletions extensions/mssql/src/queryResult/queryResultWebViewController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class QueryResultWebviewController extends ReactWebviewViewController<
},
executionPlanState: {},
fontSettings: {},
gridSettings: {},
autoSizeColumnsMode: qr.ResultsGridAutoSizeStyle.HeadersAndData,
});

Expand All @@ -77,39 +78,63 @@ export class QueryResultWebviewController extends ReactWebviewViewController<

context.subscriptions.push(
this.vscodeWrapper.onDidChangeConfiguration((e) => {
let stateChanged = false;
if (e.affectsConfiguration("mssql.resultsFontFamily")) {
const newValue = this.getFontFamilyConfig();
for (const [uri, state] of this._queryResultStateMap) {
state.fontSettings.fontFamily = this.vscodeWrapper
.getConfiguration(Constants.extensionName)
.get(Constants.extConfigResultKeys.ResultsFontFamily);
state.fontSettings.fontFamily = newValue;
this._queryResultStateMap.set(uri, state);
}
stateChanged = true;
}
if (e.affectsConfiguration("mssql.resultsFontSize")) {
const newValue = this.getFontSizeConfig();
for (const [uri, state] of this._queryResultStateMap) {
state.fontSettings.fontSize =
(this.vscodeWrapper
.getConfiguration(Constants.extensionName)
.get(Constants.extConfigResultKeys.ResultsFontSize) as number) ??
(this.vscodeWrapper
.getConfiguration("editor")
.get("fontSize") as number);
state.fontSettings.fontSize = newValue;
this._queryResultStateMap.set(uri, state);
}
stateChanged = true;
}
if (e.affectsConfiguration("mssql.resultsGrid.autoSizeColumns")) {
if (e.affectsConfiguration("mssql.resultsGrid.autoSizeColumnsMode")) {
const newValue = this.getAutoSizeColumnsConfig();
for (const [uri, state] of this._queryResultStateMap) {
state.autoSizeColumnsMode = this.getAutoSizeColumnsConfig();
state.autoSizeColumnsMode = newValue;
this._queryResultStateMap.set(uri, state);
}
stateChanged = true;
}
if (e.affectsConfiguration("mssql.resultsGrid.inMemoryDataProcessingThreshold")) {
const newValue = getInMemoryGridDataProcessingThreshold();
for (const [uri, state] of this._queryResultStateMap) {
state.inMemoryDataProcessingThreshold = this.vscodeWrapper
.getConfiguration(Constants.extensionName)
.get(Constants.configInMemoryDataProcessingThreshold);
state.inMemoryDataProcessingThreshold = newValue;
this._queryResultStateMap.set(uri, state);
}
stateChanged = true;
}
if (
e.affectsConfiguration("mssql.resultsGrid.alternatingRowColors") ||
e.affectsConfiguration("mssql.resultsGrid.showGridLines") ||
e.affectsConfiguration("mssql.resultsGrid.rowPadding")
) {
const newValue = this.getGridSettingsConfig();
for (const [uri, state] of this._queryResultStateMap) {
state.gridSettings = newValue;
this._queryResultStateMap.set(uri, state);
}
stateChanged = true;
}
if (stateChanged) {
// Push updates to all open panel controllers
for (const [uri] of this._queryResultStateMap) {
this.updatePanelState(uri);
}
// Push update to the webview view if it is visible
if (this.isVisible() && this.state?.uri) {
const currentUri = this.state.uri;
if (this._queryResultStateMap.has(currentUri)) {
this.state = this.getQueryResultState(currentUri);
}
}
}
}),
);
Expand Down Expand Up @@ -250,9 +275,9 @@ export class QueryResultWebviewController extends ReactWebviewViewController<
executionPlanState: {},
fontSettings: {
fontSize: this.getFontSizeConfig(),

fontFamily: this.getFontFamilyConfig(),
},
gridSettings: this.getGridSettingsConfig(),
autoSizeColumnsMode: this.getAutoSizeColumnsConfig(),
inMemoryDataProcessingThreshold: getInMemoryGridDataProcessingThreshold(),
initializationError: undefined,
Expand Down Expand Up @@ -321,6 +346,7 @@ export class QueryResultWebviewController extends ReactWebviewViewController<
fontSize: this.getFontSizeConfig(),
fontFamily: this.getFontFamilyConfig(),
},
gridSettings: this.getGridSettingsConfig(),
autoSizeColumnsMode: this.getAutoSizeColumnsConfig(),
inMemoryDataProcessingThreshold: getInMemoryGridDataProcessingThreshold(),
} as qr.QueryResultWebviewState;
Expand Down Expand Up @@ -364,6 +390,23 @@ export class QueryResultWebviewController extends ReactWebviewViewController<
.get(Constants.extConfigResultKeys.ResultsFontFamily) as string;
}

public getGridSettingsConfig(): qr.GridSettings {
const config = this.vscodeWrapper.getConfiguration(Constants.extensionName);
const validGridLineModes: qr.GridLinesMode[] = ["both", "horizontal", "vertical", "none"];
const gridLinesValue = config.get(Constants.configResultsGridShowGridLines) as string;
const showGridLines: qr.GridLinesMode = validGridLineModes.includes(
gridLinesValue as qr.GridLinesMode,
)
? (gridLinesValue as qr.GridLinesMode)
: "both";
return {
alternatingRowColors:
(config.get(Constants.configResultsGridAlternatingRowColors) as boolean) ?? false,
showGridLines,
rowPadding: config.get(Constants.configResultsGridRowPadding) as number | undefined,
};
}

public getDefaultViewModeConfig(): qr.QueryResultViewMode {
const configValue = this.vscodeWrapper
.getConfiguration(Constants.extensionName)
Expand Down
38 changes: 38 additions & 0 deletions extensions/mssql/src/reactviews/media/slickgrid.css
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,41 @@
display: inline-block;
flex: 1 1 auto;
}

/* ── Results grid: row padding (via CSS custom property set on the grid container) ── */
.slick-cell {
padding-top: calc(1px + var(--results-row-padding, 0px));
padding-bottom: calc(2px + var(--results-row-padding, 0px));
}

/* ── Results grid: alternating row colors ── */
/* SlickGrid adds .even/.odd classes by data row index, so striping stays stable
when scrolling in the virtualized grid (unlike :nth-child which uses DOM order). */
.results-grid--alternating .slick-row.even {
background-color: var(--vscode-list-alternatingBackground, rgba(128, 128, 128, 0.05));
}

/* ── Results grid: grid line visibility ── */
/* All four modes apply an explicit class. "both" overrides the default silver
borders with VS Code theme colors; other modes additionally clear borders
for the suppressed direction. */
.results-grid--gridlines-horizontal .slick-cell {
border-right-color: transparent;
border-bottom-color: var(--vscode-panel-border, silver);
}

.results-grid--gridlines-vertical .slick-cell {
border-right-color: var(--vscode-panel-border, silver);
border-bottom-color: transparent;
}

.results-grid--gridlines-none .slick-cell {
border-right-color: transparent;
border-bottom-color: transparent;
}

/* When grid lines are shown, use VS Code theme colors */
.results-grid--gridlines-both .slick-cell {
border-right-color: var(--vscode-panel-border, silver);
border-bottom-color: var(--vscode-panel-border, silver);
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const QueryResultsGridView = () => {
useQueryResultSelector((state) => state.tabStates?.resultViewMode) ??
qr.QueryResultViewMode.Grid;
const fontSettings = useQueryResultSelector((state) => state.fontSettings);
const gridSettings = useQueryResultSelector((state) => state.gridSettings);
const tabStates = useQueryResultSelector((state) => state.tabStates);

const gridViewContainerRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -354,18 +355,30 @@ export const QueryResultsGridView = () => {
return undefined;
}

const gridLineClass = `results-grid--gridlines-${gridSettings?.showGridLines ?? "both"}`;
const gridClasses = [
classes.gridContainer,
gridSettings?.alternatingRowColors ? "results-grid--alternating" : "",
gridLineClass,
]
.filter(Boolean)
.join(" ");

return (
<div
key={gridKey}
id={gridKey}
className={classes.gridContainer}
style={{
fontFamily: fontSettings.fontFamily
? fontSettings.fontFamily
: "var(--vscode-font-family)",
fontSize: `${fontSettings.fontSize ?? 12}px`,
height: `${maximizedGridKey === gridKey ? `100%` : `${gridHeights[index]}px`}`,
}}>
className={gridClasses}
style={
{
fontFamily: fontSettings?.fontFamily
? fontSettings.fontFamily
: "var(--vscode-editor-font-family)",
fontSize: `${fontSettings?.fontSize ?? 12}px`,
height: `${maximizedGridKey === gridKey ? `100%` : `${gridHeights[index]}px`}`,
"--results-row-padding": `${gridSettings?.rowPadding ?? 0}px`,
} as React.CSSProperties
}>
<div
style={{ flex: 1, minWidth: 0, overflow: "hidden" }}
ref={containerRef}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const ResultGrid = forwardRef<ResultGridHandle, ResultGridProps>((props: ResultG
(state) => state.inMemoryDataProcessingThreshold,
) ?? 5000;
const fontSettings = useQueryResultSelector((state) => state.fontSettings);
const gridSettings = useQueryResultSelector((state) => state.gridSettings);
const autoSizeColumnsMode =
useQueryResultSelector((state) => state.autoSizeColumnsMode) ??
qr.ResultsGridAutoSizeStyle.HeadersAndData;
Expand Down Expand Up @@ -164,6 +165,17 @@ const ResultGrid = forwardRef<ResultGridHandle, ResultGridProps>((props: ResultG
disposeAllTables();
}, [uri]);

// When row-height-affecting settings change, dispose the existing table so it is recreated
// with the correct dimensions. This covers both rowPadding and fontSize, both of which
// feed into the ROW_HEIGHT and COLUMN_WIDTH calculations inside createTable.
useEffect(() => {
if (tableRef.current) {
tableRef.current.dispose();
tableRef.current = null;
isTableCreated.current = false;
}
}, [gridSettings?.rowPadding, fontSettings?.fontSize]);

// On Column Info change, create the table. Ideally this should run only once.
useEffect(() => {
const createTable = async () => {
Expand All @@ -175,8 +187,10 @@ const ResultGrid = forwardRef<ResultGridHandle, ResultGridProps>((props: ResultG

// Setting up dimensions based on font settings
const DEFAULT_FONT_SIZE = 12;
const ROW_HEIGHT = fontSettings.fontSize! + 12; // 12 px is the padding
const COLUMN_WIDTH = Math.max((fontSettings.fontSize! / DEFAULT_FONT_SIZE) * 120, 120); // Scale width with font size, but keep a minimum of 120px
const fontSize = fontSettings?.fontSize ?? DEFAULT_FONT_SIZE;
const rowPadding = gridSettings?.rowPadding ?? 0;
const ROW_HEIGHT = fontSize + 12 + rowPadding * 2; // 12 px base padding, plus extra row padding on each side
const COLUMN_WIDTH = Math.max((fontSize / DEFAULT_FONT_SIZE) * 120, 120); // Scale width with font size, but keep a minimum of 120px

let columns: Slick.Column<Slick.SlickData>[] = columnInfo?.map((col, index) => {
return {
Expand Down Expand Up @@ -306,7 +320,7 @@ const ResultGrid = forwardRef<ResultGridHandle, ResultGridProps>((props: ResultG
} else {
void createTable();
}
}, [resultSetSummary]);
}, [resultSetSummary, gridSettings?.rowPadding, fontSettings?.fontSize]);

// Update key bindings on slickgrid when key bindings change
useEffect(() => {
Expand Down
9 changes: 9 additions & 0 deletions extensions/mssql/src/sharedInterfaces/queryResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ export interface FontSettings {
fontFamily?: string;
}

export type GridLinesMode = "both" | "horizontal" | "vertical" | "none";

export interface GridSettings {
alternatingRowColors?: boolean;
showGridLines?: GridLinesMode;
rowPadding?: number | null;
}

export interface QueryResultWebviewState extends ExecutionPlanWebviewState {
uri?: string;
title?: string;
Expand All @@ -71,6 +79,7 @@ export interface QueryResultWebviewState extends ExecutionPlanWebviewState {
selection?: ISlickRange[];
executionPlanState: ExecutionPlanState;
fontSettings: FontSettings;
gridSettings?: GridSettings;
autoSizeColumnsMode?: ResultsGridAutoSizeStyle;
inMemoryDataProcessingThreshold?: number;
initializationError?: string;
Expand Down
27 changes: 27 additions & 0 deletions extensions/mssql/test/unit/queryResultUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,33 @@ suite("QueryResult Utils Tests", () => {
});
});

suite("getGridSettings constants", () => {
test("alternatingRowColors config key has correct section-relative path", () => {
expect(Constants.configResultsGridAlternatingRowColors).to.equal(
"resultsGrid.alternatingRowColors",
);
});

test("showGridLines config key has correct path", () => {
expect(Constants.configResultsGridShowGridLines).to.equal("resultsGrid.showGridLines");
});

test("rowPadding config key has correct path", () => {
expect(Constants.configResultsGridRowPadding).to.equal("resultsGrid.rowPadding");
});

test("default gridSettings returns rowPadding=undefined when config is undefined", () => {
const mockConfig = {
get: sandbox.stub().returns(undefined),
} as unknown as vscode.WorkspaceConfiguration;

sandbox.stub(vscode.workspace, "getConfiguration").returns(mockConfig);

const rowPadding = mockConfig.get(Constants.configResultsGridRowPadding) ?? undefined;
expect(rowPadding).to.equal(undefined);
});
});

suite("bucketizeRowCount", () => {
const testCases: { value: number; expected: number }[] = [
{ value: 0, expected: 50 },
Expand Down
Loading
Loading