/** * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ import { IDecoration, IDecorationOptions, ILinkHandler, ILogger, IWindowsPty, type IOverviewRulerOptions } from '@sailfish-ai/xterm'; import { CoreMouseEncoding, CoreMouseEventType, CursorInactiveStyle, CursorStyle, IAttributeData, ICharset, IColor, ICoreMouseEvent, ICoreMouseProtocol, IDecPrivateModes, IDisposable, IKittyKeyboardState, IModes, IOscLinkData, IWindowOptions } from 'common/Types'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import type { Emitter, Event } from 'vs/base/common/event'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { serviceBrand: undefined; readonly cols: number; readonly rows: number; readonly buffer: IBuffer; readonly buffers: IBufferSet; isUserScrolling: boolean; onResize: Event; onScroll: Event; scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; scrollLines(disp: number, suppressScrollEvent?: boolean): void; resize(cols: number, rows: number): void; reset(): void; } export interface IBufferResizeEvent { cols: number; rows: number; colsChanged: boolean; rowsChanged: boolean; } export const ICoreMouseService = createDecorator('CoreMouseService'); export interface ICoreMouseService { serviceBrand: undefined; activeProtocol: string; activeEncoding: string; areMouseEventsActive: boolean; addProtocol(name: string, protocol: ICoreMouseProtocol): void; addEncoding(name: string, encoding: CoreMouseEncoding): void; reset(): void; /** * Triggers a mouse event to be sent. * * Returns true if the event passed all protocol restrictions and a report * was sent, otherwise false. The return value may be used to decide whether * the default event action in the bowser component should be omitted. * * Note: The method will change values of the given event object * to fullfill protocol and encoding restrictions. */ triggerMouseEvent(event: ICoreMouseEvent): boolean; /** * Event to announce changes in mouse tracking. */ onProtocolChange: Event; /** * Human readable version of mouse events. */ explainEvents(events: CoreMouseEventType): { [event: string]: boolean }; /** * Process wheel event taking partial scroll into account. */ consumeWheelEvent(ev: WheelEvent, cellHeight?: number, dpr?: number): number; } export const ICoreService = createDecorator('CoreService'); export interface ICoreService { serviceBrand: undefined; /** * Initially the cursor will not be visible until the first time the terminal * is focused. */ isCursorInitialized: boolean; isCursorHidden: boolean; readonly modes: IModes; readonly decPrivateModes: IDecPrivateModes; readonly kittyKeyboard: IKittyKeyboardState; readonly onData: Event; readonly onUserInput: Event; readonly onBinary: Event; readonly onRequestScrollToBottom: Event; reset(): void; /** * Triggers the onData event in the public API. * @param data The data that is being emitted. * @param wasUserInput Whether the data originated from the user (as opposed to * resulting from parsing incoming data). When true this will also: * - Scroll to the bottom of the buffer if option scrollOnUserInput is true. * - Fire the `onUserInput` event (so selection can be cleared). */ triggerDataEvent(data: string, wasUserInput?: boolean): void; /** * Triggers the onBinary event in the public API. * @param data The data that is being emitted. */ triggerBinaryEvent(data: string): void; } export const ICharsetService = createDecorator('CharsetService'); export interface ICharsetService { serviceBrand: undefined; charset: ICharset | undefined; readonly glevel: number; readonly charsets: (ICharset | undefined)[]; reset(): void; /** * Set the G level of the terminal. * @param g */ setgLevel(g: number): void; /** * Set the charset for the given G level of the terminal. * @param g * @param charset */ setgCharset(g: number, charset: ICharset | undefined): void; } export interface IServiceIdentifier { (...args: any[]): void; type: T; _id: string; } export interface IBrandedService { serviceBrand: undefined; } type GetLeadingNonServiceArgs = TArgs extends [] ? [] : TArgs extends [...infer TFirst, infer TLast] ? TLast extends IBrandedService ? GetLeadingNonServiceArgs : TArgs : never; export const IInstantiationService = createDecorator('InstantiationService'); export interface IInstantiationService { serviceBrand: undefined; setService(id: IServiceIdentifier, instance: T): void; getService(id: IServiceIdentifier): T | undefined; createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; } export enum LogLevelEnum { TRACE = 0, DEBUG = 1, INFO = 2, WARN = 3, ERROR = 4, OFF = 5 } export const ILogService = createDecorator('LogService'); export interface ILogService { serviceBrand: undefined; readonly logLevel: LogLevelEnum; trace(message: any, ...optionalParams: any[]): void; debug(message: any, ...optionalParams: any[]): void; info(message: any, ...optionalParams: any[]): void; warn(message: any, ...optionalParams: any[]): void; error(message: any, ...optionalParams: any[]): void; } export const IOptionsService = createDecorator('OptionsService'); export interface IOptionsService { serviceBrand: undefined; /** * Read only access to the raw options object, this is an internal-only fast path for accessing * single options without any validation as we trust TypeScript to enforce correct usage * internally. */ readonly rawOptions: Required; /** * Options as exposed through the public API, this property uses getters and setters with * validation which makes it safer but slower. {@link rawOptions} should be used for pretty much * all internal usage for performance reasons. */ readonly options: Required; /** * Adds an event listener for when any option changes. */ readonly onOptionChange: Event; /** * Adds an event listener for when a specific option changes, this is a convenience method that is * preferred over {@link onOptionChange} when only a single option is being listened to. */ // eslint-disable-next-line @typescript-eslint/naming-convention onSpecificOptionChange(key: T, listener: (arg1: Required[T]) => any): IDisposable; /** * Adds an event listener for when a set of specific options change, this is a convenience method * that is preferred over {@link onOptionChange} when multiple options are being listened to and * handled the same way. */ // eslint-disable-next-line @typescript-eslint/naming-convention onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable; } export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number; export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off'; export interface ITerminalOptions { allowProposedApi?: boolean; allowTransparency?: boolean; altClickMovesCursor?: boolean; cols?: number; convertEol?: boolean; cursorBlink?: boolean; cursorStyle?: CursorStyle; cursorWidth?: number; cursorInactiveStyle?: CursorInactiveStyle; disableStdin?: boolean; documentOverride?: any | null; drawBoldTextInBrightColors?: boolean; fastScrollSensitivity?: number; fontSize?: number; fontFamily?: string; fontWeight?: FontWeight; fontWeightBold?: FontWeight; ignoreBracketedPasteMode?: boolean; letterSpacing?: number; lineHeight?: number; linkHandler?: ILinkHandler | null; logLevel?: LogLevel; logger?: ILogger | null; macOptionIsMeta?: boolean; macOptionClickForcesSelection?: boolean; minimumContrastRatio?: number; reflowCursorLine?: boolean; rescaleOverlappingGlyphs?: boolean; rightClickSelectsWord?: boolean; rows?: number; showCursorImmediately?: boolean; screenReaderMode?: boolean; scrollback?: number; scrollOnUserInput?: boolean; scrollSensitivity?: number; smoothScrollDuration?: number; tabStopWidth?: number; theme?: ITheme; windowsPty?: IWindowsPty; windowOptions?: IWindowOptions; wordSeparator?: string; overviewRuler?: IOverviewRulerOptions; quirks?: ITerminalQuirks; scrollOnEraseInDisplay?: boolean; vtExtensions?: IVtExtensions; [key: string]: any; cancelEvents: boolean; termName: string; } export interface ITheme { foreground?: string; background?: string; cursor?: string; cursorAccent?: string; selectionForeground?: string; selectionBackground?: string; selectionInactiveBackground?: string; scrollbarSliderBackground?: string; scrollbarSliderHoverBackground?: string; scrollbarSliderActiveBackground?: string; overviewRulerBorder?: string; black?: string; red?: string; green?: string; yellow?: string; blue?: string; magenta?: string; cyan?: string; white?: string; brightBlack?: string; brightRed?: string; brightGreen?: string; brightYellow?: string; brightBlue?: string; brightMagenta?: string; brightCyan?: string; brightWhite?: string; extendedAnsi?: string[]; } export interface ITerminalQuirks { allowSetCursorBlink?: boolean; } export interface IVtExtensions { kittyKeyboard?: boolean; kittySgrBoldFaintControl?: boolean; win32InputMode?: boolean; } export const IOscLinkService = createDecorator('OscLinkService'); export interface IOscLinkService { serviceBrand: undefined; /** * Registers a link to the service, returning the link ID. The link data is managed by this * service and will be freed when this current cursor position is trimmed off the buffer. */ registerLink(linkData: IOscLinkData): number; /** * Adds a line to a link if needed. */ addLineToLink(linkId: number, y: number): void; /** Get the link data associated with a link ID. */ getLinkData(linkId: number): IOscLinkData | undefined; } /* * Width and Grapheme_Cluster_Break properties of a character as a bit mask. * * bit 0: shouldJoin - should combine with preceding character. * bit 1..2: wcwidth - see UnicodeCharWidth. * bit 3..31: class of character (currently only 4 bits are used). * This is used to determined grapheme clustering - i.e. which codepoints * are to be combined into a single compound character. * * Use the UnicodeService static function createPropertyValue to create a * UnicodeCharProperties; use extractShouldJoin, extractWidth, and * extractCharKind to extract the components. */ export type UnicodeCharProperties = number; /** * Width in columns of a character. * In a CJK context, "half-width" characters (such as Latin) are width 1, * while "full-width" characters (such as Kanji) are 2 columns wide. * Combining characters (such as accents) are width 0. */ export type UnicodeCharWidth = 0 | 1 | 2; export const IUnicodeService = createDecorator('UnicodeService'); export interface IUnicodeService { serviceBrand: undefined; /** Register an Unicode version provider. */ register(provider: IUnicodeVersionProvider): void; /** Registered Unicode versions. */ readonly versions: string[]; /** Currently active version. */ activeVersion: string; /** Event triggered, when activate version changed. */ readonly onChange: Event; /** * Unicode version dependent */ wcwidth(codepoint: number): UnicodeCharWidth; getStringCellWidth(s: string): number; /** * Return character width and type for grapheme clustering. * If preceding != 0, it is the return code from the previous character; * in that case the result specifies if the characters should be joined. */ charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties; } export interface IUnicodeVersionProvider { readonly version: string; wcwidth(ucs: number): UnicodeCharWidth; charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties; } export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { serviceBrand: undefined; readonly decorations: IterableIterator; readonly onDecorationRegistered: Event; readonly onDecorationRemoved: Event; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; reset(): void; /** * Trigger a callback over the decoration at a cell (in no particular order). This uses a callback * instead of an iterator as it's typically used in hot code paths. */ forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void; } export interface IInternalDecoration extends IDecoration { readonly options: IDecorationOptions; readonly backgroundColorRGB: IColor | undefined; readonly foregroundColorRGB: IColor | undefined; readonly onRenderEmitter: Emitter; } /** * Sailfish Collapse Region - represents a collapsible region in the terminal buffer. */ export interface ICollapseRegion { /** Unique identifier for the collapse region */ readonly id: number; /** Buffer line where the region starts (usually the command line) */ startLine: number; /** Buffer line where the region ends */ endLine: number; /** Line to show when collapsed (typically the command/header line) */ headerLine: number; /** Whether the region is currently collapsed */ collapsed: boolean; /** Calculated: total lines hidden by regions ABOVE this one (for visual offset) */ visualOffset: number; /** * Sailfish: Whether this region is invisible (zero visual height when collapsed). * Invisible regions are always collapsed and have no visible header line. * Used for setup commands that should not be shown to the user. */ invisible?: boolean; // ========== Sailfish Sticky Header Metadata ========== /** Timestamp when the command started (Date.now()) */ startTimestamp?: number; /** Timestamp when the command finished */ endTimestamp?: number; /** Exit code from the command (0 = success) */ exitCode?: number; /** Cached text content from the header line (command text) */ headerContent?: string; } /** * Sailfish Sticky Header Info - information about a header that should be "sticky" * (pinned to the top of the viewport while scrolling through command output). */ export interface IStickyHeaderInfo { /** Region ID this sticky header represents */ regionId: number; /** Buffer line of the header */ headerLine: number; /** Position in the sticky stack (0 = topmost) */ stickyIndex: number; /** Command text from the header line */ commandText: string; /** Timestamp when command started */ startTime?: number; /** Duration in milliseconds (if command is complete) */ duration?: number; /** Exit code (0 = success) */ exitCode?: number; /** Whether the region is currently collapsed */ collapsed: boolean; } /** * Sailfish Collapse Service - manages collapsible regions in the terminal buffer. * This enables native GPU-accelerated collapse/expand of command blocks. */ export const ICollapseService = createDecorator('CollapseService'); export interface ICollapseService extends IDisposable { serviceBrand: undefined; /** All registered collapse regions */ readonly regions: ReadonlyMap; /** Total number of hidden visual lines across all collapsed regions */ readonly totalHiddenLines: number; /** Event fired when a region is registered */ readonly onRegionRegistered: Event; /** Event fired when a region is removed */ readonly onRegionRemoved: Event; /** Event fired when a region's collapsed state changes */ readonly onCollapseStateChanged: Event; /** * Register a new collapse region. * @param startLine Buffer line where the region starts * @param endLine Buffer line where the region ends * @param headerLine Line to show when collapsed (defaults to startLine) * @returns The region ID, or -1 if registration failed */ registerRegion(startLine: number, endLine: number, headerLine?: number): number; /** * Remove a collapse region. * @param regionId The region ID to remove */ removeRegion(regionId: number): void; /** * Collapse a region. * @param regionId The region ID to collapse */ collapse(regionId: number): void; /** * Expand a region. * @param regionId The region ID to expand */ expand(regionId: number): void; /** * Toggle a region's collapsed state. * @param regionId The region ID to toggle */ toggle(regionId: number): void; /** * Collapse all regions. */ collapseAll(): void; /** * Expand all regions. */ expandAll(): void; /** * Convert a buffer line number to a visual line number. * Visual lines account for collapsed regions being hidden. * @param bufferLine The buffer line number * @returns The visual line number */ getVisualLine(bufferLine: number): number; /** * Convert a visual line number to a buffer line number. * @param visualLine The visual line number * @returns The buffer line number */ getBufferLine(visualLine: number): number; /** * Check if a buffer line is hidden (inside a collapsed region). * @param bufferLine The buffer line to check * @returns True if the line is hidden */ isLineHidden(bufferLine: number): boolean; /** * Get the collapse region that contains the given buffer line, if any. * @param bufferLine The buffer line to check * @returns The containing region or undefined */ getRegionAtLine(bufferLine: number): ICollapseRegion | undefined; /** * Reset all collapse regions. */ reset(): void; /** * Update region line numbers after buffer changes (scroll, trim, etc). * @param startLine The line where the change started * @param delta The number of lines added (positive) or removed (negative) */ adjustForBufferChange(startLine: number, delta: number): void; // ========== Sailfish Invisible Region Support ========== /** * Start tracking an invisible region at the current buffer position. * Content written after this will be part of the invisible region. * Call endInvisibleRegion() when done to finalize the region. */ startInvisibleRegion(): void; /** * End the current invisible region and register it. * The region will be automatically collapsed and hidden. * @returns The region ID, or -1 if no region was started */ endInvisibleRegion(): number; /** * Check if we're currently inside an invisible region (between start/end calls). */ readonly isInvisibleRegionActive: boolean; /** * Register an invisible region directly (for when start/end lines are known). * @param startLine Buffer line where the invisible region starts * @param endLine Buffer line where the invisible region ends * @returns The region ID, or -1 if registration failed */ registerInvisibleRegion(startLine: number, endLine: number): number; // ========== Sailfish Sticky Header Support ========== /** * Get headers that should be "sticky" (pinned to viewport top) at current scroll position. * A header is sticky when we've scrolled past it but are still within its region's output. * @param ydisp Current scroll position (buffer line at viewport top) * @param viewportRows Number of rows in the viewport * @param maxHeaders Maximum number of sticky headers to return * @returns Array of sticky header info, ordered by stack position */ getStickyHeaders(ydisp: number, viewportRows: number, maxHeaders: number): IStickyHeaderInfo[]; /** * Update metadata for a region (timing, exit code, header content). * @param regionId The region ID to update * @param metadata The metadata to set */ setRegionMetadata(regionId: number, metadata: { startTimestamp?: number; endTimestamp?: number; exitCode?: number; headerContent?: string; }): void; /** * Event fired when sticky headers change (scroll position changes, regions change, etc.) */ readonly onStickyHeadersChanged: Event; }