271 lines
7.9 KiB
TypeScript
271 lines
7.9 KiB
TypeScript
/**
|
|
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*/
|
|
|
|
import { BaseWindow } from './baseWindow';
|
|
import type { IControlWindow } from '../controlBar';
|
|
import type { IBufferCell } from '@xterm/xterm';
|
|
|
|
// Underline style values from common/buffer/Constants.ts
|
|
const enum UnderlineStyle {
|
|
NONE = 0,
|
|
SINGLE = 1,
|
|
DOUBLE = 2,
|
|
CURLY = 3,
|
|
DOTTED = 4,
|
|
DASHED = 5
|
|
}
|
|
|
|
// Internal interface for accessing extended attribute data
|
|
interface IAttributeDataInternal {
|
|
getUnderlineStyle(): number;
|
|
getUnderlineColor(): number;
|
|
isUnderlineColorDefault(): boolean;
|
|
isUnderlineColorPalette(): boolean;
|
|
isUnderlineColorRGB(): boolean;
|
|
}
|
|
|
|
export class CellInspectorWindow extends BaseWindow implements IControlWindow {
|
|
public readonly id = 'cell-inspector';
|
|
public readonly label = 'Cell Inspector';
|
|
|
|
private _container!: HTMLElement;
|
|
private _positionEl!: HTMLElement;
|
|
private _charEl!: HTMLElement;
|
|
private _codeEl!: HTMLElement;
|
|
private _widthEl!: HTMLElement;
|
|
private _fgEl!: HTMLElement;
|
|
private _bgEl!: HTMLElement;
|
|
private _attrsEl!: HTMLElement;
|
|
|
|
public build(container: HTMLElement): void {
|
|
this._container = container;
|
|
|
|
const dl = document.createElement('dl');
|
|
dl.style.fontFamily = 'monospace';
|
|
dl.style.fontSize = '12px';
|
|
|
|
this._positionEl = this._addRow(dl, 'Position');
|
|
this._charEl = this._addRow(dl, 'Character');
|
|
this._codeEl = this._addRow(dl, 'Codepoint');
|
|
this._widthEl = this._addRow(dl, 'Width');
|
|
this._fgEl = this._addRow(dl, 'Foreground');
|
|
this._bgEl = this._addRow(dl, 'Background');
|
|
this._attrsEl = this._addRow(dl, 'Attributes');
|
|
|
|
container.appendChild(dl);
|
|
|
|
// Add mouse move listener
|
|
this._setupMouseListener();
|
|
}
|
|
|
|
private _addRow(dl: HTMLElement, label: string): HTMLElement {
|
|
const dt = document.createElement('dt');
|
|
dt.textContent = label;
|
|
dt.style.fontWeight = 'bold';
|
|
dt.style.marginTop = '4px';
|
|
dl.appendChild(dt);
|
|
|
|
const dd = document.createElement('dd');
|
|
dd.textContent = '-';
|
|
dd.style.margin = '0 0 0 16px';
|
|
dl.appendChild(dd);
|
|
|
|
return dd;
|
|
}
|
|
|
|
private _setupMouseListener(): void {
|
|
const terminal = this._terminal;
|
|
if (!terminal.element) {
|
|
return;
|
|
}
|
|
|
|
const cell = terminal.buffer.active.getNullCell();
|
|
|
|
terminal.element.addEventListener('mousemove', (e: MouseEvent) => {
|
|
const core = (terminal as any)._core;
|
|
const coords = core._mouseService?.getCoords(e, core.screenElement, terminal.cols, terminal.rows);
|
|
if (!coords) {
|
|
this._clearDisplay();
|
|
return;
|
|
}
|
|
|
|
const x = coords[0] - 1;
|
|
const y = coords[1] - 1;
|
|
const bufferY = terminal.buffer.active.viewportY + y;
|
|
|
|
const line = terminal.buffer.active.getLine(bufferY);
|
|
if (!line) {
|
|
this._clearDisplay();
|
|
return;
|
|
}
|
|
|
|
const cellData = line.getCell(x, cell);
|
|
if (!cellData) {
|
|
this._clearDisplay();
|
|
return;
|
|
}
|
|
|
|
this._updateDisplay(x, y, bufferY, cellData);
|
|
});
|
|
|
|
terminal.element.addEventListener('mouseleave', () => {
|
|
this._clearDisplay();
|
|
});
|
|
}
|
|
|
|
private _clearDisplay(): void {
|
|
this._positionEl.textContent = '-';
|
|
this._charEl.textContent = '-';
|
|
this._codeEl.textContent = '-';
|
|
this._widthEl.textContent = '-';
|
|
this._fgEl.textContent = '-';
|
|
this._bgEl.textContent = '-';
|
|
this._attrsEl.textContent = '-';
|
|
}
|
|
|
|
private _updateDisplay(x: number, y: number, bufferY: number, cell: IBufferCell): void {
|
|
// Position
|
|
this._positionEl.textContent = `x=${x}, y=${y} (buffer: ${bufferY})`;
|
|
|
|
// Character
|
|
const chars = cell.getChars();
|
|
this._charEl.textContent = chars.length > 0 ? `"${chars}"` : '(empty)';
|
|
|
|
// Codepoint(s)
|
|
if (chars.length > 0) {
|
|
const codepoints = [...chars].map(c => {
|
|
const cp = c.codePointAt(0)!;
|
|
return `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`;
|
|
});
|
|
this._codeEl.textContent = codepoints.join(' ');
|
|
} else {
|
|
this._codeEl.textContent = '(none)';
|
|
}
|
|
|
|
// Width
|
|
this._widthEl.textContent = cell.getWidth().toString();
|
|
|
|
// Foreground color
|
|
this._fgEl.textContent = this._formatFgColor(
|
|
cell.getFgColor(),
|
|
cell.isFgDefault(),
|
|
cell.isFgPalette(),
|
|
cell.isFgRGB()
|
|
);
|
|
|
|
// Background color
|
|
this._bgEl.textContent = this._formatBgColor(
|
|
cell.getBgColor(),
|
|
cell.isBgDefault(),
|
|
cell.isBgPalette(),
|
|
cell.isBgRGB()
|
|
);
|
|
|
|
// Attributes (SGR codes)
|
|
const attrs: string[] = [];
|
|
if (cell.isBold()) attrs.push('bold (1)');
|
|
if (cell.isDim()) attrs.push('dim (2)');
|
|
if (cell.isItalic()) attrs.push('italic (3)');
|
|
if (cell.isUnderline()) {
|
|
const cellData = cell as unknown as IAttributeDataInternal;
|
|
const style = cellData.getUnderlineStyle();
|
|
const styleName = this._getUnderlineStyleName(style);
|
|
attrs.push(`underline ${styleName} (4:${style})`);
|
|
|
|
// Show underline color if not default
|
|
if (!cellData.isUnderlineColorDefault()) {
|
|
const color = cellData.getUnderlineColor();
|
|
const colorStr = this._formatUnderlineColor(
|
|
color,
|
|
cellData.isUnderlineColorPalette(),
|
|
cellData.isUnderlineColorRGB()
|
|
);
|
|
attrs.push(`underline color: ${colorStr}`);
|
|
}
|
|
}
|
|
if (cell.isBlink()) attrs.push('blink (5)');
|
|
if (cell.isInverse()) attrs.push('inverse (7)');
|
|
if (cell.isInvisible()) attrs.push('invisible (8)');
|
|
if (cell.isStrikethrough()) attrs.push('strikethrough (9)');
|
|
if (cell.isOverline()) attrs.push('overline (53)');
|
|
this._attrsEl.textContent = attrs.length > 0 ? attrs.join(', ') : '(none)';
|
|
}
|
|
|
|
private _formatFgColor(color: number, isDefault: boolean, isPalette: boolean, isRGB: boolean): string {
|
|
if (isDefault) {
|
|
return 'default (39)';
|
|
}
|
|
if (isPalette) {
|
|
// SGR 30-37 for colors 0-7, 90-97 for colors 8-15, or 38;5;n for 0-255
|
|
let sgr: string;
|
|
if (color < 8) {
|
|
sgr = `${30 + color}`;
|
|
} else if (color < 16) {
|
|
sgr = `${90 + color - 8}`;
|
|
} else {
|
|
sgr = `38;5;${color}`;
|
|
}
|
|
return `palette(${color}) (${sgr})`;
|
|
}
|
|
if (isRGB) {
|
|
const r = (color >> 16) & 0xFF;
|
|
const g = (color >> 8) & 0xFF;
|
|
const b = color & 0xFF;
|
|
return `#${color.toString(16).toUpperCase().padStart(6, '0')} (38;2;${r};${g};${b})`;
|
|
}
|
|
return `unknown(${color})`;
|
|
}
|
|
|
|
private _formatBgColor(color: number, isDefault: boolean, isPalette: boolean, isRGB: boolean): string {
|
|
if (isDefault) {
|
|
return 'default (49)';
|
|
}
|
|
if (isPalette) {
|
|
// SGR 40-47 for colors 0-7, 100-107 for colors 8-15, or 48;5;n for 0-255
|
|
let sgr: string;
|
|
if (color < 8) {
|
|
sgr = `${40 + color}`;
|
|
} else if (color < 16) {
|
|
sgr = `${100 + color - 8}`;
|
|
} else {
|
|
sgr = `48;5;${color}`;
|
|
}
|
|
return `palette(${color}) (${sgr})`;
|
|
}
|
|
if (isRGB) {
|
|
const r = (color >> 16) & 0xFF;
|
|
const g = (color >> 8) & 0xFF;
|
|
const b = color & 0xFF;
|
|
return `#${color.toString(16).toUpperCase().padStart(6, '0')} (48;2;${r};${g};${b})`;
|
|
}
|
|
return `unknown(${color})`;
|
|
}
|
|
|
|
private _getUnderlineStyleName(style: UnderlineStyle): string {
|
|
switch (style) {
|
|
case UnderlineStyle.NONE: return 'none';
|
|
case UnderlineStyle.SINGLE: return 'single';
|
|
case UnderlineStyle.DOUBLE: return 'double';
|
|
case UnderlineStyle.CURLY: return 'curly';
|
|
case UnderlineStyle.DOTTED: return 'dotted';
|
|
case UnderlineStyle.DASHED: return 'dashed';
|
|
default: return 'unknown';
|
|
}
|
|
}
|
|
|
|
private _formatUnderlineColor(color: number, isPalette: boolean, isRGB: boolean): string {
|
|
if (isPalette) {
|
|
return `palette(${color}) (58;5;${color})`;
|
|
}
|
|
if (isRGB) {
|
|
const r = (color >> 16) & 0xFF;
|
|
const g = (color >> 8) & 0xFF;
|
|
const b = color & 0xFF;
|
|
return `#${color.toString(16).toUpperCase().padStart(6, '0')} (58;2;${r};${g};${b})`;
|
|
}
|
|
return `unknown(${color})`;
|
|
}
|
|
}
|