From 56a7195487131fa3200414799b7cb3109d6bbd4a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 31 May 2026 10:08:04 -0700 Subject: [PATCH] Add oxlint import/no-cycle and break existing src/common cycles Enable oxlint's import/no-cycle rule (import plugin) so circular module dependencies are caught in CI, similar to eslint-plugin-import/no-cycle and biome no-import-cycles discussed in the issue. Relocate shared types and dependencies to remove cycles under src/common: - Types.ts: host IInsertEvent, IDeleteEvent, IParams, UnderlineStyle, IBuffer, and IBufferSet; drop imports from CircularList, buffer/Types, parser/Types, and Services - CoreTerminal.ts: define and export ICoreTerminal - ServiceRegistry.ts: define IServiceIdentifier (break Services registry loop) - UnicodeService: stop constructing UnicodeV6; CoreTerminal registers the default v6 provider after service creation - buffer/Types.ts and parser/Types.ts: re-export moved types for callers Fixes https://github.com/xtermjs/xterm.js/issues/5897 Co-authored-by: Cursor --- .oxlintrc.json | 4 +- src/browser/Types.ts | 3 +- src/common/CircularList.ts | 12 +-- src/common/CoreTerminal.ts | 22 +++- src/common/Types.ts | 106 ++++++++++++++++---- src/common/buffer/Constants.ts | 9 +- src/common/buffer/Types.ts | 49 +-------- src/common/input/UnicodeV6.ts | 1 + src/common/parser/Types.ts | 28 +----- src/common/public/BufferNamespaceApi.ts | 2 +- src/common/public/ParserApi.ts | 2 +- src/common/public/UnicodeApi.ts | 2 +- src/common/services/InstantiationService.ts | 4 +- src/common/services/ServiceRegistry.ts | 6 +- src/common/services/Services.ts | 8 +- src/common/services/UnicodeService.test.ts | 2 + src/common/services/UnicodeService.ts | 13 +-- test/playwright/TestUtils.ts | 3 +- 18 files changed, 139 insertions(+), 137 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 7d9cabfc5..237e00028 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,6 +1,7 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", "plugins": [ + "import", "typescript" ], "categories": { @@ -104,7 +105,8 @@ "typescript/no-wrapper-object-types": "error", "typescript/prefer-as-const": "error", "typescript/prefer-namespace-keyword": "error", - "typescript/triple-slash-reference": "error" + "typescript/triple-slash-reference": "error", + "import/no-cycle": "error" }, "overrides": [ { diff --git a/src/browser/Types.ts b/src/browser/Types.ts index 126857e17..0c454c118 100644 --- a/src/browser/Types.ts +++ b/src/browser/Types.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { CharData, IColor, ICoreTerminal, ITerminalOptions } from '../common/Types'; +import { CharData, IColor, ITerminalOptions } from '../common/Types'; +import { ICoreTerminal } from '../common/CoreTerminal'; import { IBuffer } from '../common/buffer/Types'; import { IDisposable, IRenderDimensions as IRenderDimensionsApi, Terminal as ITerminalApi } from '@xterm/xterm'; import { channels, css } from '../common/Color'; diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index f0b12d6a2..21dd081da 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -3,19 +3,11 @@ * @license MIT */ -import { ICircularList } from './Types'; +import { ICircularList, IDeleteEvent, IInsertEvent } from './Types'; import { Disposable } from './Lifecycle'; import { Emitter } from './Event'; -export interface IInsertEvent { - index: number; - amount: number; -} - -export interface IDeleteEvent { - index: number; - amount: number; -} +export type { IDeleteEvent, IInsertEvent } from './Types'; /** * Represents a circular list; a list with a maximum size that wraps around when push is called, diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 2cc48b03a..920de796f 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -21,18 +21,19 @@ * http://linux.die.net/man/7/urxvt */ -import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, IMouseStateService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from './services/Services'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, IMouseStateService, IUnicodeService, LogLevelEnum, IOscLinkService } from './services/Services'; import { InstantiationService } from './services/InstantiationService'; import { LogService } from './services/LogService'; import { BufferService, BufferServiceConstants } from './services/BufferService'; import { OptionsService } from './services/OptionsService'; -import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent } from './Types'; +import { IDisposable, IAttributeData, IScrollEvent, ITerminalOptions, IParams } from './Types'; import { CoreService } from './services/CoreService'; import { MouseStateService } from './services/MouseStateService'; +import { UnicodeV6 } from './input/UnicodeV6'; import { UnicodeService } from './services/UnicodeService'; import { CharsetService } from './services/CharsetService'; import { updateWindowsModeWrappedState } from './WindowsMode'; -import { IFunctionIdentifier, IParams } from './parser/Types'; +import { IFunctionIdentifier } from './parser/Types'; import { IBufferSet } from './buffer/Types'; import { InputHandler } from './InputHandler'; import { WriteBuffer } from './input/WriteBuffer'; @@ -43,6 +44,20 @@ import { Disposable, MutableDisposable, toDisposable } from './Lifecycle'; // Only trigger this warning a single time per session let hasWriteSyncWarnHappened = false; +export interface ICoreTerminal { + mouseStateService: IMouseStateService; + coreService: ICoreService; + optionsService: IOptionsService; + unicodeService: IUnicodeService; + buffers: IBufferSet; + options: Required; + registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise): IDisposable; + registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable; + registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable; + registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; + registerApcHandler(id: IFunctionIdentifier, callback: (data: string) => boolean | Promise): IDisposable; +} + export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected readonly _instantiationService: IInstantiationService; protected readonly _bufferService: IBufferService; @@ -116,6 +131,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.mouseStateService = this._register(this._instantiationService.createInstance(MouseStateService)); this._instantiationService.setService(IMouseStateService, this.mouseStateService); this.unicodeService = this._register(this._instantiationService.createInstance(UnicodeService)); + this.unicodeService.register(new UnicodeV6()); this._instantiationService.setService(IUnicodeService, this.unicodeService); this._charsetService = this._instantiationService.createInstance(CharsetService); this._instantiationService.setService(ICharsetService, this._charsetService); diff --git a/src/common/Types.ts b/src/common/Types.ts index 11c590b12..5b7b42bb2 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -3,26 +3,51 @@ * @license MIT */ -import { IDeleteEvent, IInsertEvent } from './CircularList'; -import { UnderlineStyle } from './buffer/Constants'; -import { IBufferSet } from './buffer/Types'; -import { IParams } from './parser/Types'; -import { IMouseStateService, ICoreService, IOptionsService, IUnicodeService } from './services/Services'; import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from '@xterm/xterm'; import type { Emitter, IEvent } from './Event'; -export interface ICoreTerminal { - mouseStateService: IMouseStateService; - coreService: ICoreService; - optionsService: IOptionsService; - unicodeService: IUnicodeService; - buffers: IBufferSet; - options: Required; - registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise): IDisposable; - registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable; - registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable; - registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; - registerApcHandler(id: IFunctionIdentifier, callback: (data: string) => boolean | Promise): IDisposable; +export interface IInsertEvent { + index: number; + amount: number; +} + +export interface IDeleteEvent { + index: number; + amount: number; +} + +/** sequence params serialized to js arrays */ +export type ParamsArray = (number | number[])[]; + +/** Interface of Params storage class. */ +export interface IParams { + /** from ctor */ + maxLength: number; + maxSubParamsLength: number; + + /** param values and its length */ + params: Int32Array; + length: number; + + /** methods */ + clone(): IParams; + toArray(): ParamsArray; + reset(): void; + resetZdm(): void; + addParam(value: number): void; + addSubParam(value: number): void; + hasSubParams(idx: number): boolean; + getSubParams(idx: number): Int32Array | null; + getSubParamsAll(): {[idx: number]: Int32Array}; +} + +export const enum UnderlineStyle { + NONE = 0, + SINGLE = 1, + DOUBLE = 2, + CURLY = 3, + DOTTED = 4, + DASHED = 5 } export interface IDisposable { @@ -260,6 +285,53 @@ export interface IMarker extends IDisposable { readonly line: number; onDispose: IEvent; } + +export interface IBuffer { + readonly lines: ICircularList; + ydisp: number; + ybase: number; + y: number; + x: number; + tabs: any; + scrollBottom: number; + scrollTop: number; + hasScrollback: boolean; + savedY: number; + savedX: number; + savedCharset: ICharset | undefined; + savedCharsets: (ICharset | undefined)[]; + savedGlevel: number; + savedOriginMode: boolean; + savedWraparoundMode: boolean; + savedCurAttrData: IAttributeData; + isCursorInViewport: boolean; + markers: IMarker[]; + translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string; + getWrappedRangeForLine(y: number): { first: number, last: number }; + nextStop(x?: number): number; + prevStop(x?: number): number; + getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine; + getNullCell(attr?: IAttributeData): ICellData; + getWhitespaceCell(attr?: IAttributeData): ICellData; + addMarker(y: number): IMarker; + clearMarkers(y: number): void; + clearAllMarkers(): void; +} + +export interface IBufferSet extends IDisposable { + alt: IBuffer; + normal: IBuffer; + active: IBuffer; + + onBufferActivate: IEvent<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>; + + activateNormalBuffer(): void; + activateAltBuffer(fillAttr?: IAttributeData): void; + reset(): void; + resize(newCols: number, newRows: number): void; + setupTabStops(i?: number): void; +} + export interface IModes { insertMode: boolean; } diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts index 5ce075cf7..12b2d7ad4 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -147,11 +147,4 @@ export const enum ExtFlags { VARIANT_OFFSET = 0xE0000000 } -export const enum UnderlineStyle { - NONE = 0, - SINGLE = 1, - DOUBLE = 2, - CURLY = 3, - DOTTED = 4, - DASHED = 5 -} +export { UnderlineStyle } from '../Types'; diff --git a/src/common/buffer/Types.ts b/src/common/buffer/Types.ts index 0cf448fcb..d5bee5efd 100644 --- a/src/common/buffer/Types.ts +++ b/src/common/buffer/Types.ts @@ -3,54 +3,7 @@ * @license MIT */ -import { IAttributeData, ICircularList, IBufferLine, ICellData, IMarker, ICharset, IDisposable } from '../Types'; -import type { IEvent } from '../Event'; +export type { IBuffer, IBufferSet } from '../Types'; // BufferIndex denotes a position in the buffer: [rowIndex, colIndex] export type BufferIndex = [number, number]; - -export interface IBuffer { - readonly lines: ICircularList; - ydisp: number; - ybase: number; - y: number; - x: number; - tabs: any; - scrollBottom: number; - scrollTop: number; - hasScrollback: boolean; - savedY: number; - savedX: number; - savedCharset: ICharset | undefined; - savedCharsets: (ICharset | undefined)[]; - savedGlevel: number; - savedOriginMode: boolean; - savedWraparoundMode: boolean; - savedCurAttrData: IAttributeData; - isCursorInViewport: boolean; - markers: IMarker[]; - translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string; - getWrappedRangeForLine(y: number): { first: number, last: number }; - nextStop(x?: number): number; - prevStop(x?: number): number; - getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine; - getNullCell(attr?: IAttributeData): ICellData; - getWhitespaceCell(attr?: IAttributeData): ICellData; - addMarker(y: number): IMarker; - clearMarkers(y: number): void; - clearAllMarkers(): void; -} - -export interface IBufferSet extends IDisposable { - alt: IBuffer; - normal: IBuffer; - active: IBuffer; - - onBufferActivate: IEvent<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>; - - activateNormalBuffer(): void; - activateAltBuffer(fillAttr?: IAttributeData): void; - reset(): void; - resize(newCols: number, newRows: number): void; - setupTabStops(i?: number): void; -} diff --git a/src/common/input/UnicodeV6.ts b/src/common/input/UnicodeV6.ts index c872bc441..617b49006 100644 --- a/src/common/input/UnicodeV6.ts +++ b/src/common/input/UnicodeV6.ts @@ -132,6 +132,7 @@ export class UnicodeV6 implements IUnicodeVersionProvider { public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties { let width = this.wcwidth(codepoint); let shouldJoin = width === 0 && preceding !== 0; + // HACK: Ideally this file would not depend on the service which uses it if (shouldJoin) { const oldWidth = UnicodeService.extractWidth(preceding); if (oldWidth === 0) { diff --git a/src/common/parser/Types.ts b/src/common/parser/Types.ts index 2e148c4d1..7306cdf76 100644 --- a/src/common/parser/Types.ts +++ b/src/common/parser/Types.ts @@ -3,12 +3,10 @@ * @license MIT */ -import { IDisposable } from '../Types'; +import { IDisposable, IParams, ParamsArray } from '../Types'; import { ParserState } from './Constants'; - -/** sequence params serialized to js arrays */ -export type ParamsArray = (number | number[])[]; +export type { IParams, ParamsArray }; /** Params constructor type. */ export interface IParamsConstructor { @@ -18,28 +16,6 @@ export interface IParamsConstructor { fromArray(values: ParamsArray): IParams; } -/** Interface of Params storage class. */ -export interface IParams { - /** from ctor */ - maxLength: number; - maxSubParamsLength: number; - - /** param values and its length */ - params: Int32Array; - length: number; - - /** methods */ - clone(): IParams; - toArray(): ParamsArray; - reset(): void; - resetZdm(): void; - addParam(value: number): void; - addSubParam(value: number): void; - hasSubParams(idx: number): boolean; - getSubParams(idx: number): Int32Array | null; - getSubParamsAll(): {[idx: number]: Int32Array}; -} - /** * Internal state of EscapeSequenceParser. * Used as argument of the error handler to allow diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index 9d56587f8..8a914cfcc 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -5,7 +5,7 @@ import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from '@xterm/xterm'; import { BufferApiView } from './BufferApiView'; -import { ICoreTerminal } from '../Types'; +import { ICoreTerminal } from '../CoreTerminal'; import { Disposable } from '../Lifecycle'; import { Emitter } from '../Event'; diff --git a/src/common/public/ParserApi.ts b/src/common/public/ParserApi.ts index 02dcc44c9..0b886c058 100644 --- a/src/common/public/ParserApi.ts +++ b/src/common/public/ParserApi.ts @@ -5,7 +5,7 @@ import { IParams } from '../parser/Types'; import { IDisposable, IFunctionIdentifier, IParser } from '@xterm/xterm'; -import { ICoreTerminal } from '../Types'; +import { ICoreTerminal } from '../CoreTerminal'; export class ParserApi implements IParser { constructor(private _core: ICoreTerminal) { } diff --git a/src/common/public/UnicodeApi.ts b/src/common/public/UnicodeApi.ts index fff2665e1..9527dbd6d 100644 --- a/src/common/public/UnicodeApi.ts +++ b/src/common/public/UnicodeApi.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ICoreTerminal } from '../Types'; +import { ICoreTerminal } from '../CoreTerminal'; import { IUnicodeHandling, IUnicodeVersionProvider } from '@xterm/xterm'; export class UnicodeApi implements IUnicodeHandling { diff --git a/src/common/services/InstantiationService.ts b/src/common/services/InstantiationService.ts index f4dbf53c1..5d2489a86 100644 --- a/src/common/services/InstantiationService.ts +++ b/src/common/services/InstantiationService.ts @@ -9,8 +9,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IInstantiationService, IServiceIdentifier } from './Services'; -import { getServiceDependencies } from './ServiceRegistry'; +import { IInstantiationService } from './Services'; +import { IServiceIdentifier, getServiceDependencies } from './ServiceRegistry'; export class ServiceCollection { diff --git a/src/common/services/ServiceRegistry.ts b/src/common/services/ServiceRegistry.ts index f4565b166..282a7bbe1 100644 --- a/src/common/services/ServiceRegistry.ts +++ b/src/common/services/ServiceRegistry.ts @@ -9,7 +9,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IServiceIdentifier } from './Services'; +export interface IServiceIdentifier { + (...args: any[]): void; + type: T; + _id: string; +} const enum Constants { DI_TARGET = 'di$target', diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 0df3786f6..5308f843d 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -6,7 +6,7 @@ import type { IDecoration, IDecorationOptions, ILinkHandler, ILogger, IWindowsPty, IOverviewRulerOptions } from '@xterm/xterm'; import { CoreMouseEncoding, CoreMouseEventType, CursorInactiveStyle, CursorStyle, IAttributeData, ICharset, IColor, ICoreMouseEvent, ICoreMouseProtocol, IDecPrivateModes, IDisposable, IKittyKeyboardState, IModes, IOscLinkData, IWindowOptions } from '../Types'; import { IBuffer, IBufferSet } from '../buffer/Types'; -import { createDecorator } from './ServiceRegistry'; +import { createDecorator, IServiceIdentifier } from './ServiceRegistry'; import type { Emitter, IEvent } from '../Event'; export const IBufferService = createDecorator('BufferService'); @@ -119,12 +119,6 @@ export interface ICharsetService { setgCharset(g: number, charset: ICharset | undefined): void; } -export interface IServiceIdentifier { - (...args: any[]): void; - type: T; - _id: string; -} - export interface IBrandedService { serviceBrand: undefined; } diff --git a/src/common/services/UnicodeService.test.ts b/src/common/services/UnicodeService.test.ts index d43605542..6d5903fb0 100644 --- a/src/common/services/UnicodeService.test.ts +++ b/src/common/services/UnicodeService.test.ts @@ -4,6 +4,7 @@ */ import { assert } from 'chai'; +import { UnicodeV6 } from '../input/UnicodeV6'; import { UnicodeService } from './UnicodeService'; import { IUnicodeVersionProvider } from './Services'; @@ -21,6 +22,7 @@ describe('unicode provider', () => { let us: UnicodeService; beforeEach(() => { us = new UnicodeService(); + us.register(new UnicodeV6()); }); it('default to V6', () => { assert.equal(us.activeVersion, '6'); diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index 1d4d6cc8f..61283c466 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { UnicodeV6 } from '../input/UnicodeV6'; import { IUnicodeService, IUnicodeVersionProvider, UnicodeCharProperties, UnicodeCharWidth } from './Services'; import { Emitter } from '../Event'; @@ -12,7 +11,7 @@ export class UnicodeService implements IUnicodeService { private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null); private _active: string = ''; - private _activeProvider: IUnicodeVersionProvider; + private _activeProvider!: IUnicodeVersionProvider; private readonly _onChange = new Emitter(); public readonly onChange = this._onChange.event; @@ -30,13 +29,6 @@ export class UnicodeService implements IUnicodeService { return ((state & 0xffffff) << 3) | ((width & 3) << 1) | (shouldJoin?1:0); } - constructor() { - const defaultProvider = new UnicodeV6(); - this.register(defaultProvider); - this._active = defaultProvider.version; - this._activeProvider = defaultProvider; - } - public dispose(): void { this._onChange.dispose(); } @@ -60,6 +52,9 @@ export class UnicodeService implements IUnicodeService { public register(provider: IUnicodeVersionProvider): void { this._providers[provider.version] = provider; + if (!this._active) { + this.activeVersion = provider.version; + } } /** diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index a27d2d20d..05f3a8e31 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -7,7 +7,8 @@ import { Browser, JSHandle, Page } from '@playwright/test'; import { deepStrictEqual, strictEqual } from 'assert'; import type { IRenderDimensions as IRenderDimensionsInternal } from '../../src/browser/renderer/shared/Types'; import type { IRenderService } from '../../src/browser/services/Services'; -import type { ICoreTerminal, IDisposable, IMarker } from '../../src/common/Types'; +import type { IDisposable, IMarker } from '../../src/common/Types'; +import type { ICoreTerminal } from '../../src/common/CoreTerminal'; import * as playwright from '@playwright/test'; import { IBuffer, IBufferCell, IBufferLine, IBufferNamespace, IBufferRange, IDecoration, IDecorationOptions, IModes, IRenderDimensions, ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from '@xterm/xterm';