Skip to content
Open
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
5 changes: 5 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/media/xterm.css
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@
pointer-events: none;
}

/* Reserve a stable right lane for the scrollbar without forcing visibility. */
.xterm.always-show-scrollbar .xterm-viewport {
right: 14px;
}

.xterm .xterm-scrollable-element > .xterm-invisible.xterm-fade {
transition: opacity 800ms linear;
}
Expand Down
55 changes: 35 additions & 20 deletions src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ const enum Constants {

DefaultCols = 80,
DefaultRows = 30,
MaxCanvasWidth = 4096
// Conservative upper bound to avoid exceeding typical GPU/browser max texture/canvas widths.
MaxCanvasWidth = 8192
}

let xtermConstructor: Promise<typeof XTermTerminal> | undefined;
Expand Down Expand Up @@ -751,6 +752,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}

private _toXtermCols(cols: number): number {
// xterm renders one extra column on the right to hide tiny visual gaps caused by pixel rounding.
return Math.max(cols + 1, 2);
}

@debounce(50)
private _fireMaximumDimensionsChanged(): void {
this._onMaximumDimensionsChanged.fire();
Expand All @@ -767,7 +773,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return undefined;
}
const computedStyle = dom.getWindow(this.xterm.raw.element).getComputedStyle(this.xterm.raw.element);
const horizontalPadding = parseInt(computedStyle.paddingLeft) + parseInt(computedStyle.paddingRight) + 14/*scroll bar padding*/;
const horizontalPadding = parseInt(computedStyle.paddingLeft) + parseInt(computedStyle.paddingRight) + (this.xterm?.viewportRightOffset ?? 0);
const verticalPadding = parseInt(computedStyle.paddingTop) + parseInt(computedStyle.paddingBottom);
TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension(
Math.min(Constants.MaxCanvasWidth, width - horizontalPadding),
Expand Down Expand Up @@ -804,7 +810,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {

const disableShellIntegrationReporting = (this.shellLaunchConfig.executable === undefined || this.shellType === undefined) || !shellIntegrationSupportedShellTypes.includes(this.shellType);
const xterm = this._scopedInstantiationService.createInstance(XtermTerminal, this._resource, Terminal, {
cols: this._cols,
// xterm gets one extra render column for visual alignment; the PTY keeps base columns.
cols: this._toXtermCols(this._cols || Constants.DefaultCols),
rows: this._rows,
xtermColorProvider: this._scopedInstantiationService.createInstance(TerminalInstanceColorProvider, this._targetRef),
capabilities: this.capabilities,
Expand All @@ -815,17 +822,20 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._resizeDebouncer = this._register(new TerminalResizeDebouncer(
() => this._isVisible,
() => xterm,
async (cols, rows) => {
xterm.resize(cols, rows);
await this._updatePtyDimensions(xterm.raw);
async (xtermCols, rows) => {
const ptyCols = this.cols;
xterm.resize(xtermCols, rows);
await this._updatePtyDimensions(xterm.raw, ptyCols, rows);
},
async (cols) => {
xterm.resize(cols, xterm.raw.rows);
await this._updatePtyDimensions(xterm.raw);
async (xtermCols) => {
const ptyCols = this.cols;
xterm.resize(xtermCols, xterm.raw.rows);
await this._updatePtyDimensions(xterm.raw, ptyCols, this.rows);
},
async (rows) => {
xterm.resize(xterm.raw.cols, rows);
await this._updatePtyDimensions(xterm.raw);
const ptyCols = this.cols;
xterm.resize(this._toXtermCols(ptyCols), rows);
await this._updatePtyDimensions(xterm.raw, ptyCols, rows);
Comment thread
dripfyyy marked this conversation as resolved.
}
));
this._register(toDisposable(() => this._resizeDebouncer = undefined));
Expand Down Expand Up @@ -1586,10 +1596,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Re-evaluate dimensions if the container has been set since the xterm instance was created
if (this._container && this._cols === 0 && this._rows === 0) {
this._initDimensions();
this.xterm?.resize(this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows);
this.xterm?.resize(this._toXtermCols(this._cols || Constants.DefaultCols), this._rows || Constants.DefaultRows);
}
const originalIcon = this.shellLaunchConfig.icon;
await this._processManager.createProcess(this._shellLaunchConfig, this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows).then(result => {
await this._processManager.createProcess(this._shellLaunchConfig, this.cols || Constants.DefaultCols, this.rows || Constants.DefaultRows).then(result => {
if (result) {
if (hasKey(result, { message: true })) {
this._onProcessExit(result);
Expand Down Expand Up @@ -2027,24 +2037,29 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (isNaN(cols) || isNaN(rows)) {
return;
}
const xtermCols = this._toXtermCols(cols);

if (cols !== this.xterm.raw.cols || rows !== this.xterm.raw.rows) {
if (xtermCols !== this.xterm.raw.cols || rows !== this.xterm.raw.rows) {
if (this._fixedRows || this._fixedCols) {
await this._updateProperty(ProcessPropertyType.FixedDimensions, { cols: this._fixedCols, rows: this._fixedRows });
}
this._onDimensionsChanged.fire();
}

TerminalInstance._lastKnownGridDimensions = { cols, rows };
this._resizeDebouncer!.resize(cols, rows, immediate ?? false);
this._resizeDebouncer!.resize(xtermCols, rows, immediate ?? false);
}

private async _updatePtyDimensions(rawXterm: XTermTerminal): Promise<void> {
private async _updatePtyDimensions(rawXterm: XTermTerminal, ptyCols: number = this.cols, ptyRows: number = this.rows): Promise<void> {
const pixelWidth = rawXterm.dimensions?.css.canvas.width;
const pixelHeight = rawXterm.dimensions?.css.canvas.height;
const roundedPixelWidth = pixelWidth ? Math.round(pixelWidth) : undefined;
const roundedPixelHeight = pixelHeight ? Math.round(pixelHeight) : undefined;
await this._processManager.setDimensions(rawXterm.cols, rawXterm.rows, undefined, roundedPixelWidth, roundedPixelHeight);
const cellWidth = rawXterm.dimensions?.css.cell.width;
// Subtract that extra render column before reporting pixel width to the PTY so process sizing
// stays based on the real terminal columns.
const adjustedPixelWidth = pixelWidth !== undefined && cellWidth ? Math.max(0, pixelWidth - cellWidth) : pixelWidth;
const roundedPixelWidth = adjustedPixelWidth !== undefined ? Math.round(adjustedPixelWidth) : undefined;
const roundedPixelHeight = pixelHeight !== undefined ? Math.round(pixelHeight) : undefined;
await this._processManager.setDimensions(ptyCols, ptyRows, undefined, roundedPixelWidth, roundedPixelHeight);
}

setShellType(shellType: TerminalShellType | undefined) {
Expand Down Expand Up @@ -2191,7 +2206,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
const proposedCols = Math.max(this.maxCols, Math.min(this.xterm.getLongestViewportWrappedLineLength(), maxColsForTexture));
// Don't switch to fixed dimensions if the content already fits as it makes the scroll
// bar look bad being off the edge
if (proposedCols > this.xterm.raw.cols) {
if (proposedCols > this.cols) {
this._fixedCols = proposedCols;
}
}
Expand Down
Loading