Skip to content
Draft
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
6 changes: 5 additions & 1 deletion addons/addon-webgl/src/TextureAtlas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { IColorContrastCache } from 'browser/Types';
import { DIM_OPACITY, TEXT_BASELINE } from './Constants';
import { tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer';
import { loadCustomGlyphDefinitions, tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer';
import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from './Types';
import { NULL_COLOR, channels, color, rgba } from 'common/Color';
Expand Down Expand Up @@ -100,6 +100,10 @@ export class TextureAtlas implements ITextureAtlas {
alpha: this._config.allowTransparency,
willReadFrequently: true
}));
// Start loading custom glyph definitions asynchronously if custom glyphs are enabled
if (this._config.customGlyphs !== false) {
loadCustomGlyphDefinitions();
}
}

public dispose(): void {
Expand Down
39 changes: 37 additions & 2 deletions addons/addon-webgl/src/customGlyphs/CustomGlyphRasterizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,38 @@
*/

import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { customGlyphDefinitions } from './CustomGlyphDefinitions';
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';

/**
* Cached custom glyph definitions. This is loaded dynamically to support code splitting.
*/
let customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefinition | undefined } | undefined;

/**
* Promise that resolves when custom glyph definitions are loaded.
*/
let loadingPromise: Promise<void> | undefined;

/**
* Begins loading the custom glyph definitions. This should be called as early as possible
* to ensure the definitions are ready when needed.
* @returns A promise that resolves when the definitions are loaded.
*/
export function loadCustomGlyphDefinitions(): Promise<void> {
if (customGlyphDefinitions) {
return Promise.resolve();
}
if (!loadingPromise) {
loadingPromise = import('./CustomGlyphDefinitions').then(module => {
customGlyphDefinitions = module.customGlyphDefinitions;
}).catch(err => {
// Reset the loading promise on failure to allow retry
loadingPromise = undefined;
throw err;
});
}
return loadingPromise;
}

/**
* Try drawing a custom block element or box drawing character, returning whether it was
Expand All @@ -24,6 +54,11 @@ export function tryDrawCustomGlyph(
devicePixelRatio: number,
backgroundColor?: string
): boolean {
// If definitions are not loaded yet, return false to fall back to font rendering
if (!customGlyphDefinitions) {
return false;
}

const unifiedCharDefinition = customGlyphDefinitions[c];
if (unifiedCharDefinition) {
// Normalize to array for uniform handling
Expand Down
19 changes: 15 additions & 4 deletions bin/esbuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ const commonOptions = {
target: 'es2021',
sourcemap: true,
treeShaking: true,
splitting: true,
chunkNames: 'chunks/[name]-[hash]',
logLevel: 'warning',
outExtension: { '.js': '.mjs' },
};

/** @type {esbuild.BuildOptions} */
Expand Down Expand Up @@ -103,7 +106,8 @@ if (config.addon) {
bundleConfig = {
...bundleConfig,
entryPoints: [`addons/addon-${config.addon}/src/${getAddonEntryPoint(config.addon)}.ts`],
outfile: `addons/addon-${config.addon}/lib/addon-${config.addon}.mjs`,
outdir: `addons/addon-${config.addon}/lib`,
entryNames: `addon-${config.addon}`,
};
outConfig = {
...outConfig,
Expand All @@ -128,7 +132,8 @@ if (config.addon) {
bundleConfig = {
...bundleConfig,
entryPoints: [`demo/client/client.ts`],
outfile: 'demo/dist/client-bundle.js',
outdir: 'demo/dist',
entryNames: 'client-bundle',
external: ['util', 'os', 'fs', 'path', 'stream', 'Terminal'],
alias: {
// Library ESM imports
Expand Down Expand Up @@ -164,14 +169,19 @@ if (config.addon) {
format: 'cjs',
platform: 'node',
external: ['node-pty'],
// Splitting is not compatible with cjs format
splitting: false,
// Override the .mjs extension from commonOptions
outExtension: {},
}
skipOut = true;
skipOutTest = true;
} else if (config.isHeadless) {
bundleConfig = {
...bundleConfig,
entryPoints: [`src/headless/public/Terminal.ts`],
outfile: `headless/lib-headless/xterm-headless.mjs`
outdir: `headless/lib-headless`,
entryNames: 'xterm-headless',
};
outConfig = {
...outConfig,
Expand All @@ -183,7 +193,8 @@ if (config.addon) {
bundleConfig = {
...bundleConfig,
entryPoints: [`src/browser/public/Terminal.ts`],
outfile: `lib/xterm.mjs`
outdir: `lib`,
entryNames: 'xterm',
};
outConfig = {
...outConfig,
Expand Down
2 changes: 1 addition & 1 deletion demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
<div id="sidebar" class="grid">
</div>
</div>
<script src="dist/client-bundle.js" defer ></script>
<script src="dist/client-bundle.mjs" type="module" defer ></script>
</body>
</html>
2 changes: 1 addition & 1 deletion demo/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
</head>
<body id="test">
<div id="terminal-container"></div>
<script src="/dist/client-bundle.js" defer></script>
<script src="/dist/client-bundle.mjs" type="module" defer></script>
</body>
</html>
59 changes: 53 additions & 6 deletions src/browser/CoreBrowserTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { OscLinkProvider } from 'browser/OscLinkProvider';
import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal } from 'browser/Types';
import { Viewport } from 'browser/Viewport';
import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';
import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
import type { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
import { CompositionHelper } from 'browser/input/CompositionHelper';
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
import { IRenderer } from 'browser/renderer/shared/Types';
Expand All @@ -53,12 +53,34 @@ import { toRgbString } from 'common/input/XParseColor';
import { DecorationService } from 'common/services/DecorationService';
import { IDecorationService } from 'common/services/Services';
import { WindowsOptionsReportType } from '../common/InputHandler';
import { AccessibilityManager } from './AccessibilityManager';
import type { AccessibilityManager } from './AccessibilityManager';
import { Linkifier } from './Linkifier';
import { Emitter, Event } from 'vs/base/common/event';
import { addDisposableListener } from 'vs/base/browser/dom';
import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';

/**
* Lazily loaded AccessibilityManager module.
*/
let accessibilityManagerModule: Promise<typeof import('./AccessibilityManager')> | undefined;
function loadAccessibilityManager(): Promise<typeof import('./AccessibilityManager')> {
if (!accessibilityManagerModule) {
accessibilityManagerModule = import('./AccessibilityManager');
}
return accessibilityManagerModule;
}

/**
* Lazily loaded OverviewRulerRenderer module.
*/
let overviewRulerRendererModule: Promise<typeof import('browser/decorations/OverviewRulerRenderer')> | undefined;
function loadOverviewRulerRenderer(): Promise<typeof import('browser/decorations/OverviewRulerRenderer')> {
if (!overviewRulerRendererModule) {
overviewRulerRendererModule = import('browser/decorations/OverviewRulerRenderer');
}
return overviewRulerRendererModule;
}

export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
public textarea: HTMLTextAreaElement | undefined;
public element: HTMLElement | undefined;
Expand Down Expand Up @@ -277,7 +299,14 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
private _handleScreenReaderModeOptionChange(value: boolean): void {
if (value) {
if (!this._accessibilityManager.value && this._renderService) {
this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this);
loadAccessibilityManager().then(module => {
// Check again after async load in case option changed
if (this.options.screenReaderMode && !this._accessibilityManager.value && this._renderService) {
this._accessibilityManager.value = this._instantiationService.createInstance(module.AccessibilityManager, this);
}
}).catch(() => {
// Failed to load accessibility manager module
});
}
} else {
this._accessibilityManager.clear();
Expand Down Expand Up @@ -588,16 +617,34 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
if (this.options.screenReaderMode) {
// Note that this must be done *after* the renderer is created in order to
// ensure the correct order of the dprchange event
this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this);
loadAccessibilityManager().then(module => {
if (this.options.screenReaderMode && !this._accessibilityManager.value && this._renderService) {
this._accessibilityManager.value = this._instantiationService.createInstance(module.AccessibilityManager, this);
}
}).catch(() => {
// Failed to load accessibility manager module
});
}
this._register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e)));

if (this.options.overviewRuler.width) {
this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement));
loadOverviewRulerRenderer().then(module => {
if (this.options.overviewRuler.width && !this._overviewRulerRenderer && this._viewportElement && this.screenElement) {
this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(module.OverviewRulerRenderer, this._viewportElement, this.screenElement));
}
}).catch(() => {
// Failed to load overview ruler renderer module
});
}
this.optionsService.onSpecificOptionChange('overviewRuler', value => {
if (!this._overviewRulerRenderer && value && this._viewportElement && this.screenElement) {
this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement));
loadOverviewRulerRenderer().then(module => {
if (!this._overviewRulerRenderer && this.options.overviewRuler.width && this._viewportElement && this.screenElement) {
this._overviewRulerRenderer = this._register(this._instantiationService.createInstance(module.OverviewRulerRenderer, this._viewportElement, this.screenElement));
}
}).catch(() => {
// Failed to load overview ruler renderer module
});
}
});
// Measure the character size
Expand Down