Fixes https://github.com/xtermjs/xterm.js/issues/5896 TypeScript path mappings for common/*, browser/*, and headless/* were not rewritten in emitted JavaScript, which forced webpack resolve aliases and NODE_PATH workarounds so runtime could resolve modules. That added build complexity and broke tooling such as ESLint import/no-cycle. Changes: - Convert ~750 imports across src/, addons/, and test/ to relative paths - Remove common/browser/headless paths and src catch-all mappings from tsconfigs (keep @xterm/addon-* demo and addon typings paths) - Remove webpack resolve.alias for common, browser, headless, and vs - Drop NODE_PATH from unit tests, benchmarks, and VS Code launch config - Fix addon-image IIPImageStorage to import ./Types after removing catch-all * Verified: npm run build, esbuild, package, package-headless Co-authored-by: Cursor <cursoragent@cursor.com>
206 lines
6.8 KiB
TypeScript
206 lines
6.8 KiB
TypeScript
/**
|
|
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*/
|
|
|
|
import { RendererConstants } from '../../../src/browser/renderer/shared/Constants';
|
|
import { ICoreBrowserService } from '../../../src/browser/services/Services';
|
|
|
|
const enum Constants {
|
|
/**
|
|
* The time between cursor blinks.
|
|
*/
|
|
BLINK_INTERVAL = 600,
|
|
}
|
|
|
|
export class CursorBlinkStateManager {
|
|
public isCursorVisible: boolean;
|
|
|
|
private _animationFrame: number | undefined;
|
|
private _blinkStartTimeout: number | undefined;
|
|
private _blinkInterval: number | undefined;
|
|
private _idleTimeout: number | undefined;
|
|
private _isIdlePaused: boolean = false;
|
|
|
|
/**
|
|
* The time at which the animation frame was restarted, this is used on the
|
|
* next render to restart the timers so they don't need to restart the timers
|
|
* multiple times over a short period.
|
|
*/
|
|
private _animationTimeRestarted: number | undefined;
|
|
|
|
constructor(
|
|
private _renderCallback: () => void,
|
|
private _coreBrowserService: ICoreBrowserService
|
|
) {
|
|
this.isCursorVisible = true;
|
|
if (this._coreBrowserService.isFocused) {
|
|
this._restartInterval();
|
|
this._resetIdleTimer();
|
|
}
|
|
}
|
|
|
|
public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); }
|
|
|
|
public dispose(): void {
|
|
if (this._blinkInterval) {
|
|
this._coreBrowserService.window.clearInterval(this._blinkInterval);
|
|
this._blinkInterval = undefined;
|
|
}
|
|
if (this._blinkStartTimeout) {
|
|
this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);
|
|
this._blinkStartTimeout = undefined;
|
|
}
|
|
if (this._animationFrame) {
|
|
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
|
|
this._animationFrame = undefined;
|
|
}
|
|
if (this._idleTimeout) {
|
|
this._coreBrowserService.window.clearTimeout(this._idleTimeout);
|
|
this._idleTimeout = undefined;
|
|
}
|
|
}
|
|
|
|
public restartBlinkAnimation(): void {
|
|
if (this._isIdlePaused) {
|
|
this._resetIdleTimer();
|
|
}
|
|
if (this.isPaused) {
|
|
return;
|
|
}
|
|
// Save a timestamp so that the restart can be done on the next interval
|
|
this._animationTimeRestarted = Date.now();
|
|
// Force a cursor render to ensure it's visible and in the correct position
|
|
this.isCursorVisible = true;
|
|
if (!this._animationFrame) {
|
|
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
|
|
this._renderCallback();
|
|
this._animationFrame = undefined;
|
|
});
|
|
}
|
|
}
|
|
|
|
private _restartInterval(timeToStart: number = Constants.BLINK_INTERVAL): void {
|
|
// Clear any existing interval
|
|
if (this._blinkInterval) {
|
|
this._coreBrowserService.window.clearInterval(this._blinkInterval);
|
|
this._blinkInterval = undefined;
|
|
}
|
|
|
|
// Setup the initial timeout which will hide the cursor, this is done before
|
|
// the regular interval is setup in order to support restarting the blink
|
|
// animation in a lightweight way (without thrashing clearInterval and
|
|
// setInterval).
|
|
this._blinkStartTimeout = this._coreBrowserService.window.setTimeout(() => {
|
|
// Check if another animation restart was requested while this was being
|
|
// started
|
|
if (this._animationTimeRestarted) {
|
|
const time = Constants.BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
|
|
this._animationTimeRestarted = undefined;
|
|
if (time > 0) {
|
|
this._restartInterval(time);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Hide the cursor
|
|
this.isCursorVisible = false;
|
|
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
|
|
this._renderCallback();
|
|
this._animationFrame = undefined;
|
|
});
|
|
|
|
// Setup the blink interval
|
|
this._blinkInterval = this._coreBrowserService.window.setInterval(() => {
|
|
// Adjust the animation time if it was restarted
|
|
if (this._animationTimeRestarted) {
|
|
// calc time diff
|
|
// Make restart interval do a setTimeout initially?
|
|
const time = Constants.BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
|
|
this._animationTimeRestarted = undefined;
|
|
this._restartInterval(time);
|
|
return;
|
|
}
|
|
|
|
// Invert visibility and render
|
|
this.isCursorVisible = !this.isCursorVisible;
|
|
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
|
|
this._renderCallback();
|
|
this._animationFrame = undefined;
|
|
});
|
|
}, Constants.BLINK_INTERVAL);
|
|
}, timeToStart);
|
|
}
|
|
|
|
public pause(): void {
|
|
this.isCursorVisible = true;
|
|
this._isIdlePaused = false;
|
|
if (this._blinkInterval) {
|
|
this._coreBrowserService.window.clearInterval(this._blinkInterval);
|
|
this._blinkInterval = undefined;
|
|
}
|
|
if (this._blinkStartTimeout) {
|
|
this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);
|
|
this._blinkStartTimeout = undefined;
|
|
}
|
|
if (this._animationFrame) {
|
|
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
|
|
this._animationFrame = undefined;
|
|
}
|
|
if (this._idleTimeout) {
|
|
this._coreBrowserService.window.clearTimeout(this._idleTimeout);
|
|
this._idleTimeout = undefined;
|
|
}
|
|
}
|
|
|
|
public resume(): void {
|
|
// Clear out any existing timers just in case
|
|
this.pause();
|
|
|
|
this._animationTimeRestarted = undefined;
|
|
this._restartInterval();
|
|
this._resetIdleTimer();
|
|
this.restartBlinkAnimation();
|
|
}
|
|
|
|
/**
|
|
* Resets the idle timer. If the terminal is idle for the idle timeout period,
|
|
* the cursor blinking will stop.
|
|
*/
|
|
private _resetIdleTimer(): void {
|
|
this._isIdlePaused = false;
|
|
if (this._idleTimeout) {
|
|
this._coreBrowserService.window.clearTimeout(this._idleTimeout);
|
|
}
|
|
this._idleTimeout = this._coreBrowserService.window.setTimeout(() => {
|
|
this._stopBlinkingDueToIdle();
|
|
}, RendererConstants.CURSOR_BLINK_IDLE_TIMEOUT);
|
|
}
|
|
|
|
/**
|
|
* Stops cursor blinking due to idle timeout.
|
|
*/
|
|
private _stopBlinkingDueToIdle(): void {
|
|
// Make cursor visible and stop blinking
|
|
this.isCursorVisible = true;
|
|
this._isIdlePaused = true;
|
|
if (this._blinkInterval) {
|
|
this._coreBrowserService.window.clearInterval(this._blinkInterval);
|
|
this._blinkInterval = undefined;
|
|
}
|
|
if (this._blinkStartTimeout) {
|
|
this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);
|
|
this._blinkStartTimeout = undefined;
|
|
}
|
|
if (this._animationFrame) {
|
|
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
|
|
this._animationFrame = undefined;
|
|
}
|
|
// Clear the idle timeout as we've already acted on it
|
|
this._coreBrowserService.window.clearTimeout(this._idleTimeout);
|
|
this._idleTimeout = undefined;
|
|
// Trigger a render to show the cursor in its final visible state
|
|
this._renderCallback();
|
|
}
|
|
}
|