Compare commits

...
6 changed files with 112 additions and 15 deletions
+5 -1
View File
@@ -5,7 +5,7 @@
import { IColorContrastCache } from 'browser/Types'; import { IColorContrastCache } from 'browser/Types';
import { DIM_OPACITY, TEXT_BASELINE } from './Constants'; 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 { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from './Types'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from './Types';
import { NULL_COLOR, channels, color, rgba } from 'common/Color'; import { NULL_COLOR, channels, color, rgba } from 'common/Color';
@@ -100,6 +100,10 @@ export class TextureAtlas implements ITextureAtlas {
alpha: this._config.allowTransparency, alpha: this._config.allowTransparency,
willReadFrequently: true willReadFrequently: true
})); }));
// Start loading custom glyph definitions asynchronously if custom glyphs are enabled
if (this._config.customGlyphs !== false) {
loadCustomGlyphDefinitions();
}
} }
public dispose(): void { public dispose(): void {
@@ -4,8 +4,38 @@
*/ */
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { customGlyphDefinitions } from './CustomGlyphDefinitions'; import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, 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 * Try drawing a custom block element or box drawing character, returning whether it was
@@ -24,6 +54,11 @@ export function tryDrawCustomGlyph(
devicePixelRatio: number, devicePixelRatio: number,
backgroundColor?: string backgroundColor?: string
): boolean { ): boolean {
// If definitions are not loaded yet, return false to fall back to font rendering
if (!customGlyphDefinitions) {
return false;
}
const unifiedCharDefinition = customGlyphDefinitions[c]; const unifiedCharDefinition = customGlyphDefinitions[c];
if (unifiedCharDefinition) { if (unifiedCharDefinition) {
// Normalize to array for uniform handling // Normalize to array for uniform handling
+15 -4
View File
@@ -26,7 +26,10 @@ const commonOptions = {
target: 'es2021', target: 'es2021',
sourcemap: true, sourcemap: true,
treeShaking: true, treeShaking: true,
splitting: true,
chunkNames: 'chunks/[name]-[hash]',
logLevel: 'warning', logLevel: 'warning',
outExtension: { '.js': '.mjs' },
}; };
/** @type {esbuild.BuildOptions} */ /** @type {esbuild.BuildOptions} */
@@ -103,7 +106,8 @@ if (config.addon) {
bundleConfig = { bundleConfig = {
...bundleConfig, ...bundleConfig,
entryPoints: [`addons/addon-${config.addon}/src/${getAddonEntryPoint(config.addon)}.ts`], 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 = {
...outConfig, ...outConfig,
@@ -128,7 +132,8 @@ if (config.addon) {
bundleConfig = { bundleConfig = {
...bundleConfig, ...bundleConfig,
entryPoints: [`demo/client/client.ts`], entryPoints: [`demo/client/client.ts`],
outfile: 'demo/dist/client-bundle.js', outdir: 'demo/dist',
entryNames: 'client-bundle',
external: ['util', 'os', 'fs', 'path', 'stream', 'Terminal'], external: ['util', 'os', 'fs', 'path', 'stream', 'Terminal'],
alias: { alias: {
// Library ESM imports // Library ESM imports
@@ -164,6 +169,10 @@ if (config.addon) {
format: 'cjs', format: 'cjs',
platform: 'node', platform: 'node',
external: ['node-pty'], external: ['node-pty'],
// Splitting is not compatible with cjs format
splitting: false,
// Override the .mjs extension from commonOptions
outExtension: {},
} }
skipOut = true; skipOut = true;
skipOutTest = true; skipOutTest = true;
@@ -171,7 +180,8 @@ if (config.addon) {
bundleConfig = { bundleConfig = {
...bundleConfig, ...bundleConfig,
entryPoints: [`src/headless/public/Terminal.ts`], entryPoints: [`src/headless/public/Terminal.ts`],
outfile: `headless/lib-headless/xterm-headless.mjs` outdir: `headless/lib-headless`,
entryNames: 'xterm-headless',
}; };
outConfig = { outConfig = {
...outConfig, ...outConfig,
@@ -183,7 +193,8 @@ if (config.addon) {
bundleConfig = { bundleConfig = {
...bundleConfig, ...bundleConfig,
entryPoints: [`src/browser/public/Terminal.ts`], entryPoints: [`src/browser/public/Terminal.ts`],
outfile: `lib/xterm.mjs` outdir: `lib`,
entryNames: 'xterm',
}; };
outConfig = { outConfig = {
...outConfig, ...outConfig,
+1 -1
View File
@@ -23,6 +23,6 @@
<div id="sidebar" class="grid"> <div id="sidebar" class="grid">
</div> </div>
</div> </div>
<script src="dist/client-bundle.js" defer ></script> <script src="dist/client-bundle.mjs" type="module" defer ></script>
</body> </body>
</html> </html>
+1 -1
View File
@@ -18,6 +18,6 @@
</head> </head>
<body id="test"> <body id="test">
<div id="terminal-container"></div> <div id="terminal-container"></div>
<script src="/dist/client-bundle.js" defer></script> <script src="/dist/client-bundle.mjs" type="module" defer></script>
</body> </body>
</html> </html>
+53 -6
View File
@@ -28,7 +28,7 @@ import { OscLinkProvider } from 'browser/OscLinkProvider';
import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal } from 'browser/Types'; import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal } from 'browser/Types';
import { Viewport } from 'browser/Viewport'; import { Viewport } from 'browser/Viewport';
import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer'; 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 { CompositionHelper } from 'browser/input/CompositionHelper';
import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
import { IRenderer } from 'browser/renderer/shared/Types'; import { IRenderer } from 'browser/renderer/shared/Types';
@@ -53,12 +53,34 @@ import { toRgbString } from 'common/input/XParseColor';
import { DecorationService } from 'common/services/DecorationService'; import { DecorationService } from 'common/services/DecorationService';
import { IDecorationService } from 'common/services/Services'; import { IDecorationService } from 'common/services/Services';
import { WindowsOptionsReportType } from '../common/InputHandler'; import { WindowsOptionsReportType } from '../common/InputHandler';
import { AccessibilityManager } from './AccessibilityManager'; import type { AccessibilityManager } from './AccessibilityManager';
import { Linkifier } from './Linkifier'; import { Linkifier } from './Linkifier';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { addDisposableListener } from 'vs/base/browser/dom'; import { addDisposableListener } from 'vs/base/browser/dom';
import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; 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 { export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
public textarea: HTMLTextAreaElement | undefined; public textarea: HTMLTextAreaElement | undefined;
public element: HTMLElement | undefined; public element: HTMLElement | undefined;
@@ -277,7 +299,14 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
private _handleScreenReaderModeOptionChange(value: boolean): void { private _handleScreenReaderModeOptionChange(value: boolean): void {
if (value) { if (value) {
if (!this._accessibilityManager.value && this._renderService) { 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 { } else {
this._accessibilityManager.clear(); this._accessibilityManager.clear();
@@ -588,16 +617,34 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
if (this.options.screenReaderMode) { if (this.options.screenReaderMode) {
// Note that this must be done *after* the renderer is created in order to // Note that this must be done *after* the renderer is created in order to
// ensure the correct order of the dprchange event // 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))); this._register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e)));
if (this.options.overviewRuler.width) { 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 => { this.optionsService.onSpecificOptionChange('overviewRuler', value => {
if (!this._overviewRulerRenderer && value && this._viewportElement && this.screenElement) { 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 // Measure the character size