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
5 changes: 5 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"dbaeumer.vscode-eslint"
]
}
17 changes: 11 additions & 6 deletions addons/addon-ligatures/src/fontLigatures/flatten.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ILookupTree, IFlattenedLookupTree, ILookupTreeEntry, IFlattenedLookupTreeEntry } from './types';

export default function flatten(tree: ILookupTree): IFlattenedLookupTree {
export default function flatten(tree: ILookupTree, visited: Map<ILookupTreeEntry, IFlattenedLookupTreeEntry> = new Map()): IFlattenedLookupTree {
const result: IFlattenedLookupTree = {};
for (const [glyphId, entry] of Object.entries(tree.individual)) {
result[glyphId] = flattenEntry(entry);
result[glyphId] = flattenEntry(entry, visited);
}

for (const { range, entry } of tree.range) {
const flattened = flattenEntry(entry);
const flattened = flattenEntry(entry, visited);
for (let glyphId = range[0]; glyphId < range[1]; glyphId++) {
result[glyphId] = flattened;
}
Expand All @@ -16,15 +16,20 @@ export default function flatten(tree: ILookupTree): IFlattenedLookupTree {
return result;
}

function flattenEntry(entry: ILookupTreeEntry): IFlattenedLookupTreeEntry {
function flattenEntry(entry: ILookupTreeEntry, visited: Map<ILookupTreeEntry, IFlattenedLookupTreeEntry>): IFlattenedLookupTreeEntry {
if (visited.has(entry)) {
return visited.get(entry)!;
}

const result: IFlattenedLookupTreeEntry = {};
visited.set(entry, result);

if (entry.forward) {
result.forward = flatten(entry.forward);
result.forward = flatten(entry.forward, visited);
}

if (entry.reverse) {
result.reverse = flatten(entry.reverse);
result.reverse = flatten(entry.reverse, visited);
}

if (entry.lookup) {
Expand Down
55 changes: 38 additions & 17 deletions addons/addon-ligatures/src/fontLigatures/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ export default function mergeTrees(trees: ILookupTree[]): ILookupTree {
range: []
};

const mergedEntries = new WeakMap<ILookupTreeEntry, Set<ILookupTreeEntry>>();
for (const tree of trees) {
mergeSubtree(result, tree);
mergeSubtree(result, tree, mergedEntries);
}

return result;
Expand All @@ -26,15 +27,16 @@ export default function mergeTrees(trees: ILookupTree[]): ILookupTree {
*
* @param mainTree The tree where the values should be merged
* @param mergeTree The tree to be merged into the mainTree
* @param mergedEntries WeakMap to track already merged entry pairs
*/
function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void {
function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree, mergedEntries: WeakMap<ILookupTreeEntry, Set<ILookupTreeEntry>>): void {
// Need to fix this recursively (and handle lookups)
for (const [glyphId, value] of Object.entries(mergeTree.individual)) {
// The main tree is guaranteed to have no overlaps between the
// individual and range values, so if we match an invididual, there
// must not be a range
if (mainTree.individual[glyphId]) {
mergeTreeEntry(mainTree.individual[glyphId], value);
mergeTreeEntry(mainTree.individual[glyphId], value, mergedEntries);
} else {
let matched = false;
for (const [index, { range, entry }] of mainTree.range.entries()) {
Expand All @@ -50,7 +52,7 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void {
// If they overlap, we have to split the range and then
// merge the overlap
mainTree.individual[glyphId] = value;
mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry));
mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries);

// When there's an overlap, we also have to fix up the range
// that we had already processed
Expand Down Expand Up @@ -101,7 +103,7 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void {
mainTree.individual[overlap.both] = entryToMerge;
}

mergeTreeEntry(entryToMerge, cloneEntry(entry));
mergeTreeEntry(entryToMerge, cloneEntry(entry), mergedEntries);

for (const second of overlap.second) {
if (Array.isArray(second)) {
Expand All @@ -124,7 +126,7 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void {
// If they overlap, we have to split the range and then
// merge the overlap
mainTree.individual[remainingRange] = cloneEntry(entry);
mergeTreeEntry(mainTree.individual[remainingRange], cloneEntry(resultEntry));
mergeTreeEntry(mainTree.individual[remainingRange], cloneEntry(resultEntry), mergedEntries);

// When there's an overlap, we also have to fix up the range
// that we had already processed
Expand Down Expand Up @@ -158,14 +160,14 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void {
}

// If they overlap, we have to merge the overlap
mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry));
mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries);

// Update the remaining ranges
remainingRanges.splice(remainingIndex, 1, ...overlap.second);
break;
} else {
if (Number(glyphId) === remainingRange) {
mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry));
mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries);
break;
}
}
Expand All @@ -191,8 +193,20 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void {
*
* @param mainTree The entry where the values should be merged
* @param mergeTree The entry to merge into the mainTree
* @param mergedEntries WeakMap to track already merged entry pairs
*/
function mergeTreeEntry(mainTree: ILookupTreeEntry, mergeTree: ILookupTreeEntry): void {
function mergeTreeEntry(mainTree: ILookupTreeEntry, mergeTree: ILookupTreeEntry, mergedEntries: WeakMap<ILookupTreeEntry, Set<ILookupTreeEntry>>): void {
// Check if we've already merged this pair
let mergedSet = mergedEntries.get(mainTree);
if (mergedSet?.has(mergeTree)) {
return;
}
if (!mergedSet) {
mergedSet = new Set();
mergedEntries.set(mainTree, mergedSet);
}
mergedSet.add(mergeTree);

if (
mergeTree.lookup && (
!mainTree.lookup ||
Expand All @@ -207,15 +221,15 @@ function mergeTreeEntry(mainTree: ILookupTreeEntry, mergeTree: ILookupTreeEntry)
if (!mainTree.forward) {
mainTree.forward = mergeTree.forward;
} else {
mergeSubtree(mainTree.forward, mergeTree.forward);
mergeSubtree(mainTree.forward, mergeTree.forward, mergedEntries);
}
}

if (mergeTree.reverse) {
if (!mainTree.reverse) {
mainTree.reverse = mergeTree.reverse;
} else {
mergeSubtree(mainTree.reverse, mergeTree.reverse);
mergeSubtree(mainTree.reverse, mergeTree.reverse, mergedEntries);
}
}
}
Expand Down Expand Up @@ -326,16 +340,22 @@ function rangeOrIndividual(start: number, end: number): number | [number, number
* Clones an individual lookup tree entry.
*
* @param entry Lookup tree entry to clone
* @param visited Map to track already cloned entries (prevents infinite loops)
*/
function cloneEntry(entry: ILookupTreeEntry): ILookupTreeEntry {
function cloneEntry(entry: ILookupTreeEntry, visited: Map<ILookupTreeEntry, ILookupTreeEntry> = new Map()): ILookupTreeEntry {
if (visited.has(entry)) {
return visited.get(entry)!;
}

const result: ILookupTreeEntry = {};
visited.set(entry, result);

if (entry.forward) {
result.forward = cloneTree(entry.forward);
result.forward = cloneTree(entry.forward, visited);
}

if (entry.reverse) {
result.reverse = cloneTree(entry.reverse);
result.reverse = cloneTree(entry.reverse, visited);
}

if (entry.lookup) {
Expand All @@ -355,18 +375,19 @@ function cloneEntry(entry: ILookupTreeEntry): ILookupTreeEntry {
* Clones a lookup tree.
*
* @param tree Lookup tree to clone
* @param visited Map to track already cloned entries (prevents infinite loops)
*/
function cloneTree(tree: ILookupTree): ILookupTree {
function cloneTree(tree: ILookupTree, visited: Map<ILookupTreeEntry, ILookupTreeEntry> = new Map()): ILookupTree {
const individual: { [glyphId: string]: ILookupTreeEntry } = {};
for (const [glyphId, entry] of Object.entries(tree.individual)) {
individual[glyphId] = cloneEntry(entry);
individual[glyphId] = cloneEntry(entry, visited);
}

return {
individual,
range: tree.range.map(({ range, entry }) => ({
range: range.slice() as [number, number],
entry: cloneEntry(entry)
entry: cloneEntry(entry, visited)
}))
};
}
80 changes: 52 additions & 28 deletions addons/addon-ligatures/src/fontLigatures/processors/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,41 @@ export function processLookaheadPosition(
currentEntries: IEntryMeta[]
): IEntryMeta[] {
const nextEntries: IEntryMeta[] = [];
const processedEntries = new Set<ILookupTreeEntry>();

for (const currentEntry of currentEntries) {
for (const glyph of glyphs) {
const entry: ILookupTreeEntry = {};
if (!currentEntry.entry.forward) {
currentEntry.entry.forward = {
individual: {},
range: []
};
}
nextEntries.push({
entry,
substitutions: currentEntry.substitutions
});
// Skip if we've already processed this entry object
if (processedEntries.has(currentEntry.entry)) {
continue;
}
processedEntries.add(currentEntry.entry);

if (!currentEntry.entry.forward) {
currentEntry.entry.forward = {
individual: {},
range: []
};
}

// All glyphs at this position share ONE entry - lookahead just needs to match,
// all paths lead to the same result
const sharedEntry: ILookupTreeEntry = {};

for (const glyph of glyphs) {
if (Array.isArray(glyph)) {
currentEntry.entry.forward.range.push({
entry,
entry: sharedEntry,
range: glyph
});
} else {
currentEntry.entry.forward.individual[glyph] = entry;
currentEntry.entry.forward.individual[glyph] = sharedEntry;
}
}

nextEntries.push({
entry: sharedEntry,
substitutions: currentEntry.substitutions
});
}

return nextEntries;
Expand All @@ -76,29 +88,41 @@ export function processBacktrackPosition(
currentEntries: IEntryMeta[]
): IEntryMeta[] {
const nextEntries: IEntryMeta[] = [];
const processedEntries = new Set<ILookupTreeEntry>();

for (const currentEntry of currentEntries) {
for (const glyph of glyphs) {
const entry: ILookupTreeEntry = {};
if (!currentEntry.entry.reverse) {
currentEntry.entry.reverse = {
individual: {},
range: []
};
}
nextEntries.push({
entry,
substitutions: currentEntry.substitutions
});
// Skip if we've already processed this entry object
if (processedEntries.has(currentEntry.entry)) {
continue;
}
processedEntries.add(currentEntry.entry);

if (!currentEntry.entry.reverse) {
currentEntry.entry.reverse = {
individual: {},
range: []
};
}

// All glyphs at this position share ONE entry - backtrack just needs to match,
// all paths lead to the same result
const sharedEntry: ILookupTreeEntry = {};

for (const glyph of glyphs) {
if (Array.isArray(glyph)) {
currentEntry.entry.reverse.range.push({
entry,
entry: sharedEntry,
range: glyph
});
} else {
currentEntry.entry.reverse.individual[glyph] = entry;
currentEntry.entry.reverse.individual[glyph] = sharedEntry;
}
}

nextEntries.push({
entry: sharedEntry,
substitutions: currentEntry.substitutions
});
}

return nextEntries;
Expand Down
2 changes: 0 additions & 2 deletions demo/client/components/window/addonSerializeWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
* @license MIT
*/

import type { Terminal } from '@xterm/xterm';
import { BaseWindow } from './baseWindow';
import type { IControlWindow } from '../controlBar';
import type { AddonCollection } from '../../types';

export class AddonSerializeWindow extends BaseWindow implements IControlWindow {
public readonly id = 'addon-serialize';
Expand Down
10 changes: 1 addition & 9 deletions demo/client/components/window/cellInspectorWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

import { BaseWindow } from './baseWindow';
import type { IControlWindow } from '../controlBar';
import type { Terminal, IBufferCell } from '@xterm/xterm';
import type { AddonCollection } from '../../types';
import type { IBufferCell } from '@xterm/xterm';

// Underline style values from common/buffer/Constants.ts
const enum UnderlineStyle {
Expand Down Expand Up @@ -40,13 +39,6 @@ export class CellInspectorWindow extends BaseWindow implements IControlWindow {
private _bgEl: HTMLElement;
private _attrsEl: HTMLElement;

constructor(
terminal: Terminal,
addons: AddonCollection,
) {
super(terminal, addons);
}

public build(container: HTMLElement): void {
this._container = container;

Expand Down
6 changes: 4 additions & 2 deletions demo/client/components/window/gpuWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ export class GpuWindow extends BaseWindow implements IControlWindow {
}

private _styleAtlasPage(canvas: HTMLCanvasElement): void {
canvas.style.width = `${canvas.width / window.devicePixelRatio}px`;
canvas.style.height = `${canvas.height / window.devicePixelRatio}px`;
// eslint-disable-next-line no-restricted-syntax
const dpr = window.devicePixelRatio;
canvas.style.width = `${canvas.width / dpr}px`;
canvas.style.height = `${canvas.height / dpr}px`;
}
}
Loading
Loading