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
128 changes: 54 additions & 74 deletions src/components/terminal/terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default class TerminalComponent {
rows: options.rows || 24,
cols: options.cols || 80,
port: options.port || 8767,
renderer: options.renderer || "auto", // 'auto' | 'canvas' | 'webgl'
fontSize: terminalSettings.fontSize,
fontFamily: terminalSettings.fontFamily,
fontWeight: terminalSettings.fontWeight,
Expand Down Expand Up @@ -80,7 +81,7 @@ export default class TerminalComponent {
system.openInBrowser(uri);
}
});
this.webglAddon = new WebglAddon();
this.webglAddon = null;

// Load addons
this.terminal.loadAddon(this.fitAddon);
Expand Down Expand Up @@ -244,36 +245,12 @@ export default class TerminalComponent {
// Ensure scroll position is within valid bounds
const safeScrollPosition = Math.min(targetScrollPosition, maxScroll);

// Only adjust if we have significant content and the position is different
// Only adjust if we have significant content and the position differs
if (
buffer.length > this.terminal.rows &&
Math.abs(buffer.viewportY - safeScrollPosition) > 2
buffer.viewportY !== safeScrollPosition
) {
// Gradually adjust to prevent jarring movements
const steps = 3;
const diff = safeScrollPosition - buffer.viewportY;
const stepSize = Math.ceil(Math.abs(diff) / steps);

let currentStep = 0;
const adjustStep = () => {
if (currentStep >= steps) return;

const currentPos = buffer.viewportY;
const remaining = safeScrollPosition - currentPos;
const adjustment =
Math.sign(remaining) * Math.min(stepSize, Math.abs(remaining));

if (Math.abs(adjustment) >= 1) {
this.terminal.scrollLines(adjustment);
}

currentStep++;
if (currentStep < steps && Math.abs(remaining) > 1) {
setTimeout(adjustStep, 50);
}
};

setTimeout(adjustStep, 100);
this.terminal.scrollToLine(safeScrollPosition);
}
}

Expand Down Expand Up @@ -468,7 +445,6 @@ export default class TerminalComponent {
position: relative;
background: ${this.options.theme.background};
overflow: hidden;
padding: 0.25rem;
box-sizing: border-box;
`;

Expand All @@ -490,32 +466,49 @@ export default class TerminalComponent {
this.container.style.background = this.options.theme.background;

try {
try {
this.terminal.loadAddon(this.webglAddon);
this.terminal.open(container);
} catch (error) {
console.error("Failed to load WebglAddon:", error);
this.webglAddon.dispose();
}

if (!this.terminal.element) {
// webgl loading failed for some reason, attach with DOM renderer
this.terminal.open(container);
// Open first to ensure a stable renderer is attached
this.terminal.open(container);

// Renderer selection: 'canvas' (default core), 'webgl', or 'auto'
if (
this.options.renderer === "webgl" ||
this.options.renderer === "auto"
) {
try {
const addon = new WebglAddon();
this.terminal.loadAddon(addon);
if (typeof addon.onContextLoss === "function") {
addon.onContextLoss(() => this._handleWebglContextLoss());
}
this.webglAddon = addon;
} catch (error) {
console.error("Failed to enable WebGL renderer:", error);
try {
this.webglAddon?.dispose?.();
} catch {}
this.webglAddon = null; // stay on canvas
}
}
const terminalSettings = getTerminalSettings();
// Load ligatures addon if enabled
if (terminalSettings.fontLigatures) {
this.loadLigaturesAddon();
}

// Wait for terminal to render then fit
setTimeout(() => {
this.fitAddon.fit();
this.terminal.focus();

// Initialize touch selection after terminal is mounted
this.setupTouchSelection();
}, 10);
// First render pass: schedule a fit + focus once the frame is ready
if (typeof requestAnimationFrame === "function") {
requestAnimationFrame(() => {
this.fitAddon.fit();
this.terminal.focus();
this.setupTouchSelection();
});
} else {
setTimeout(() => {
this.fitAddon.fit();
this.terminal.focus();
this.setupTouchSelection();
}, 0);
}
} catch (error) {
console.error("Failed to mount terminal:", error);
}
Expand Down Expand Up @@ -726,32 +719,6 @@ export default class TerminalComponent {
* Focus terminal
*/
focus() {
// Ensure cursor is visible before focusing to prevent half-visibility
if (this.terminal.buffer && this.terminal.buffer.active) {
const buffer = this.terminal.buffer.active;
const cursorY = buffer.cursorY;
const cursorViewportPos = buffer.baseY + cursorY;
const viewportTop = buffer.viewportY;
const viewportBottom = viewportTop + this.terminal.rows - 1;

// Check if cursor is fully visible (with margin to prevent half-visibility)
const isCursorFullyVisible =
cursorViewportPos >= viewportTop + 1 &&
cursorViewportPos <= viewportBottom - 2;

// If cursor is not fully visible, scroll to make it properly visible
if (!isCursorFullyVisible && buffer.length > this.terminal.rows) {
const targetScroll = Math.max(
0,
Math.min(
buffer.length - this.terminal.rows,
cursorViewportPos - Math.floor(this.terminal.rows * 0.25),
),
);
this.terminal.scrollToLine(targetScroll);
}
}

this.terminal.focus();
}

Expand Down Expand Up @@ -1018,3 +985,16 @@ export default class TerminalComponent {
onBell() {}
onProcessExit(exitData) {}
}

// Internal helpers for WebGL renderer lifecycle
TerminalComponent.prototype._handleWebglContextLoss = function () {
try {
console.warn("WebGL context lost; falling back to canvas renderer");
try {
this.webglAddon?.dispose?.();
} catch {}
this.webglAddon = null;
} catch (e) {
console.error("Error handling WebGL context loss:", e);
}
};
85 changes: 54 additions & 31 deletions src/components/terminal/terminalManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ class TerminalManager {
id: `terminal-${terminalId}`,
});

// Terminal styles
const terminalStyles = this.getTerminalStyles();
const terminalStyle = tag("style", {
textContent: terminalStyles,
});
document.body.appendChild(terminalStyle);
// Terminal styles (inject once)
if (!document.getElementById("acode-terminal-styles")) {
const terminalStyles = this.getTerminalStyles();
const terminalStyle = tag("style", {
id: "acode-terminal-styles",
textContent: terminalStyles,
});
document.body.appendChild(terminalStyle);
}

// Create EditorFile for terminal
const terminalFile = new EditorFile(terminalName, {
Expand Down Expand Up @@ -188,12 +191,15 @@ class TerminalManager {
id: `terminal-${terminalId}`,
});

// Terminal styles
const terminalStyles = this.getTerminalStyles();
const terminalStyle = tag("style", {
textContent: terminalStyles,
});
document.body.appendChild(terminalStyle);
// Terminal styles (inject once)
if (!document.getElementById("acode-terminal-styles")) {
const terminalStyles = this.getTerminalStyles();
const terminalStyle = tag("style", {
id: "acode-terminal-styles",
textContent: terminalStyles,
});
document.body.appendChild(terminalStyle);
}

// Create EditorFile for terminal
const terminalFile = new EditorFile(terminalName, {
Expand Down Expand Up @@ -257,10 +263,25 @@ class TerminalManager {
setupTerminalHandlers(terminalFile, terminalComponent, terminalId) {
// Handle tab focus/blur
terminalFile.onfocus = () => {
setTimeout(() => {
// Guarded fit on focus: only fit if cols/rows would change, then focus
const run = () => {
try {
const pd = terminalComponent.fitAddon?.proposeDimensions?.();
if (
pd &&
(pd.cols !== terminalComponent.terminal.cols ||
pd.rows !== terminalComponent.terminal.rows)
) {
terminalComponent.fitAddon.fit();
}
} catch {}
terminalComponent.focus();
terminalComponent.fit();
}, 10);
};
if (typeof requestAnimationFrame === "function") {
requestAnimationFrame(run);
} else {
setTimeout(run, 0);
}
};

// Handle tab close
Expand All @@ -273,8 +294,14 @@ class TerminalManager {
const RESIZE_DEBOUNCE = 200;
let lastResizeTime = 0;

let lastWidth = 0;
let lastHeight = 0;
const resizeObserver = new ResizeObserver((entries) => {
const now = Date.now();
const entry = entries && entries[0];
const cr = entry?.contentRect;
const width = cr?.width ?? terminalFile.content?.clientWidth ?? 0;
const height = cr?.height ?? terminalFile.content?.clientHeight ?? 0;

// Clear any pending resize
if (resizeTimeout) {
Expand All @@ -289,24 +316,14 @@ class TerminalManager {
return;
}

// Get current terminal state
const currentRows = terminalComponent.terminal.rows;
const currentCols = terminalComponent.terminal.cols;

// Fit the terminal to new container size
terminalComponent.fit();

// Check if dimensions actually changed after fit
const newRows = terminalComponent.terminal.rows;
const newCols = terminalComponent.terminal.cols;

// Only fit if actual size changed to reduce reflows
if (
Math.abs(newRows - currentRows) > 1 ||
Math.abs(newCols - currentCols) > 1
Math.abs(width - lastWidth) > 0.5 ||
Math.abs(height - lastHeight) > 0.5
) {
// console.log(
// `Terminal ${terminalId} resized: ${currentRows}x${currentCols} -> ${newRows}x${newCols}`,
// );
terminalComponent.fit();
lastWidth = width;
lastHeight = height;
}

// Update last resize time
Expand All @@ -322,6 +339,8 @@ class TerminalManager {
const containerElement = terminalFile.content;
if (containerElement && containerElement instanceof Element) {
resizeObserver.observe(containerElement);
// store observer so we can disconnect on close
terminalFile._resizeObserver = resizeObserver;
} else {
console.warn("Terminal container not available for ResizeObserver");
}
Expand Down Expand Up @@ -472,7 +491,11 @@ class TerminalManager {
background: #1e1e1e;
overflow: hidden;
position: relative;
}

.terminal-content .xterm {
padding: 0.25rem;
box-sizing: border-box;
}
`;
}
Expand Down
3 changes: 0 additions & 3 deletions src/lib/installPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,6 @@ export default async function installPlugin(
const isDirEntry = !!zip.files[file].dir || /\/$/.test(correctFile);

// If the original path is absolute or otherwise unsafe, skip it and warn later
console.log(
`Skipping unsafe path: ${file} : ${isUnsafeAbsolutePath(file)}`,
);
if (isUnsafeAbsolutePath(file)) {
ignoredUnsafeEntries.add(file);
return;
Expand Down
6 changes: 3 additions & 3 deletions src/sidebarApps/extensions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -417,12 +417,12 @@ function updateHeight($el) {
removeHeight($explore, $el !== $explore);

try {
let height = $header.getBoundingClientRect().height;
const tileHeight = $el.get(":scope>.tile").getBoundingClientRect().height;
let height = $header?.getBoundingClientRect().height;
const tileHeight = $el.get(":scope>.tile")?.getBoundingClientRect().height;
if ($el === $searchResult) {
height += 60;
} else {
height += $searchResult.getBoundingClientRect().height + tileHeight;
height += $searchResult?.getBoundingClientRect().height + tileHeight;
}

setHeight($el, height);
Expand Down