Skip to content

Commit

Permalink
Merge branch 'master' into hover_bug
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyriar committed Apr 21, 2024
2 parents 34eb0ed + 499afa1 commit 2411578
Show file tree
Hide file tree
Showing 30 changed files with 246 additions and 70 deletions.
8 changes: 5 additions & 3 deletions .devcontainer/devcontainer.json
@@ -1,13 +1,15 @@
{
"name": "xterm.js",
"image": "mcr.microsoft.com/devcontainers/typescript-node:0-18-buster",
"image": "mcr.microsoft.com/devcontainers/typescript-node:18-bookworm",
"features": {
"ghcr.io/devcontainers/features/node:1": {} // yarn
"ghcr.io/devcontainers/features/node:1": {
"version": 18
} // yarn
},
"forwardPorts": [
3000
],
"postCreateCommand": "yarn install",
"postCreateCommand": "yarn install && yarn setup",
"customizations": {
"vscode": {
"extensions": [
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
@@ -1 +1 @@
16
18
2 changes: 1 addition & 1 deletion .vscode/launch.json
Expand Up @@ -61,7 +61,7 @@
"runtimeExecutable": "npm",
"runtimeArgs": ["start"],
"stopOnEntry": true,
"runtimeVersion": "16",
"runtimeVersion": "18",
"serverReadyAction": {
"action": "openExternally",
"pattern": "App listening to (http://.*?:[0-9]+)"
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -222,6 +222,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**Cloudtutor.io**](https://cloudtutor.io): innovative online learning platform that offers users access to an interactive lab.
- [**Helix Editor Playground**](https://github.com/tomgroenwoldt/helix-editor-playground): Online playground for the terminal based helix editor.
- [**Coder**](https://github.com/coder/coder): Self-Hosted Remote Development Environments
- [**Wave Terminal**](https://waveterm.dev): An open-source, ai-native, terminal built for seamless workflows.
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)

Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
Expand Down
4 changes: 3 additions & 1 deletion addons/addon-attach/webpack.config.js
Expand Up @@ -25,7 +25,9 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
4 changes: 3 additions & 1 deletion addons/addon-canvas/webpack.config.js
Expand Up @@ -33,7 +33,9 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
4 changes: 3 additions & 1 deletion addons/addon-fit/webpack.config.js
Expand Up @@ -25,7 +25,9 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
4 changes: 3 additions & 1 deletion addons/addon-image/webpack.config.js
Expand Up @@ -33,7 +33,9 @@ const addon = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
Expand Down
4 changes: 3 additions & 1 deletion addons/addon-ligatures/webpack.config.js
Expand Up @@ -25,7 +25,9 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production',
externals: {
Expand Down
6 changes: 3 additions & 3 deletions addons/addon-ligatures/yarn.lock
Expand Up @@ -86,9 +86,9 @@ fd-slicer@~1.1.0:
pend "~1.2.0"

follow-redirects@^1.15.0:
version "1.15.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a"
integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==
version "1.15.6"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==

font-finder@^1.0.3:
version "1.0.4"
Expand Down
4 changes: 3 additions & 1 deletion addons/addon-search/webpack.config.js
Expand Up @@ -32,7 +32,9 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
3 changes: 2 additions & 1 deletion addons/addon-serialize/webpack.config.js
Expand Up @@ -34,7 +34,8 @@ module.exports = {
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd',
globalObject: 'this'
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
4 changes: 3 additions & 1 deletion addons/addon-unicode-graphemes/webpack.config.js
Expand Up @@ -32,7 +32,9 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
3 changes: 2 additions & 1 deletion addons/addon-unicode11/webpack.config.js
Expand Up @@ -33,7 +33,8 @@ module.exports = {
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd',
globalObject: 'this'
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
4 changes: 3 additions & 1 deletion addons/addon-web-links/webpack.config.js
Expand Up @@ -25,7 +25,9 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
4 changes: 3 additions & 1 deletion addons/addon-webgl/webpack.config.js
Expand Up @@ -33,7 +33,9 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
30 changes: 29 additions & 1 deletion demo/client.ts
Expand Up @@ -44,7 +44,7 @@ if ('WebAssembly' in window) {

// Pulling in the module's types relies on the <reference> above, it's looks a
// little weird here as we're importing "this" module
import { Terminal as TerminalType, ITerminalOptions } from '@xterm/xterm';
import { Terminal as TerminalType, ITerminalOptions, type IDisposable } from '@xterm/xterm';

export interface IWindowWithTerminal extends Window {
term: TerminalType;
Expand Down Expand Up @@ -255,6 +255,7 @@ if (document.location.pathname === '/test') {
document.getElementById('add-grapheme-clusters').addEventListener('click', addGraphemeClusters);
document.getElementById('add-decoration').addEventListener('click', addDecoration);
document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler);
document.getElementById('decoration-stress-test').addEventListener('click', decorationStressTest);
document.getElementById('weblinks-test').addEventListener('click', testWeblinks);
document.getElementById('bce').addEventListener('click', coloredErase);
addVtButtons();
Expand Down Expand Up @@ -1170,6 +1171,33 @@ function addOverviewRuler(): void {
term.registerDecoration({ marker: term.registerMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' } });
}

let decorationStressTestDecorations: IDisposable[] | undefined;
function decorationStressTest(): void {
if (decorationStressTestDecorations) {
for (const d of decorationStressTestDecorations) {
d.dispose();
}
decorationStressTestDecorations = undefined;
} else {
const t = term as Terminal;
const buffer = t.buffer.active;
const cursorY = buffer.baseY + buffer.cursorY;
decorationStressTestDecorations = [];
for (const x of [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]) {
for (let y = 0; y < t.buffer.active.length; y++) {
const cursorOffsetY = y - cursorY;
decorationStressTestDecorations.push(t.registerDecoration({
marker: t.registerMarker(cursorOffsetY),
x,
width: 4,
backgroundColor: '#FF0000',
overviewRulerOptions: { color: '#FF0000' }
}));
}
}
}
}

(console as any).image = (source: ImageData | HTMLCanvasElement, scale: number = 1) => {
function getBox(width: number, height: number): any {
return {
Expand Down
1 change: 1 addition & 0 deletions demo/index.html
Expand Up @@ -102,6 +102,7 @@ <h3>Test</h3>
<dt>Decorations</dt>
<dd><button id="add-decoration" title="Add a decoration to the terminal">Decoration</button></dd>
<dd><button id="add-overview-ruler" title="Add an overview ruler to the terminal">Add Overview Ruler</button></dd>
<dd><button id="decoration-stress-test" title="Toggle between adding and removing a decoration to each line">Stress Test</button></dd>

<dt>Weblinks Addon</dt>
<dd><button id="weblinks-test" title="Various url conditions from demo data, hover&click to test">Test URLs</button></dd>
Expand Down
17 changes: 15 additions & 2 deletions src/browser/Viewport.ts
Expand Up @@ -36,6 +36,8 @@ export class Viewport extends Disposable implements IViewport {
private _activeBuffer: IBuffer;
private _renderDimensions: IRenderDimensions;

private _smoothScrollAnimationFrame: number = 0;

// Stores a partial line amount when scrolling, this is used to keep track of how much of a line
// is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
// quick fix and could have a more robust solution in place that reset the value when needed.
Expand All @@ -49,6 +51,8 @@ export class Viewport extends Disposable implements IViewport {
target: -1
};

private _ensureTimeout: number;

private readonly _onRequestScrollLines = this.register(new EventEmitter<{ amount: number, suppressScrollEvent: boolean }>());
public readonly onRequestScrollLines = this._onRequestScrollLines.event;

Expand Down Expand Up @@ -81,7 +85,7 @@ export class Viewport extends Disposable implements IViewport {
this.register(this._optionsService.onSpecificOptionChange('scrollback', () => this.syncScrollArea()));

// Perform this async to ensure the ICharSizeService is ready.
setTimeout(() => this.syncScrollArea());
this._ensureTimeout = window.setTimeout(() => this.syncScrollArea());
}

private _handleThemeChange(colors: ReadonlyColorSet): void {
Expand Down Expand Up @@ -211,7 +215,12 @@ export class Viewport extends Disposable implements IViewport {

// Continue or finish smooth scroll
if (percent < 1) {
this._coreBrowserService.window.requestAnimationFrame(() => this._smoothScroll());
if (!this._smoothScrollAnimationFrame) {
this._smoothScrollAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
this._smoothScrollAnimationFrame = 0;
this._smoothScroll();
});
}
} else {
this._clearSmoothScrollState();
}
Expand Down Expand Up @@ -398,4 +407,8 @@ export class Viewport extends Disposable implements IViewport {
this._viewportElement.scrollTop += deltaY;
return this._bubbleScroll(ev, deltaY);
}

public dispose(): void {
clearTimeout(this._ensureTimeout);
}
}
10 changes: 6 additions & 4 deletions src/browser/public/Terminal.ts
Expand Up @@ -20,6 +20,8 @@ import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOption
*/
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];

let $value = 0;

export class Terminal extends Disposable implements ITerminalApi {
private _core: ITerminal;
private _addonManager: AddonManager;
Expand Down Expand Up @@ -249,16 +251,16 @@ export class Terminal extends Disposable implements ITerminalApi {
}

private _verifyIntegers(...values: number[]): void {
for (const value of values) {
if (value === Infinity || isNaN(value) || value % 1 !== 0) {
for ($value of values) {
if ($value === Infinity || isNaN($value) || $value % 1 !== 0) {
throw new Error('This API only accepts integers');
}
}
}

private _verifyPositiveIntegers(...values: number[]): void {
for (const value of values) {
if (value && (value === Infinity || isNaN(value) || value % 1 !== 0 || value < 0)) {
for ($value of values) {
if ($value && ($value === Infinity || isNaN($value) || $value % 1 !== 0 || $value < 0)) {
throw new Error('This API only accepts positive integers');
}
}
Expand Down
8 changes: 3 additions & 5 deletions src/browser/renderer/dom/DomRenderer.ts
Expand Up @@ -343,18 +343,16 @@ export class DomRenderer extends Disposable implements IRenderer {
}

this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode);
if (!this._selectionRenderModel.hasSelection) {
return;
}

// Translate from buffer position to viewport position
const viewportStartRow = this._selectionRenderModel.viewportStartRow;
const viewportEndRow = this._selectionRenderModel.viewportEndRow;
const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;
const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;

// No need to draw the selection
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
return;
}

// Create the selections
const documentFragment = this._document.createDocumentFragment();

Expand Down
10 changes: 7 additions & 3 deletions src/browser/services/CoreBrowserService.ts
Expand Up @@ -13,7 +13,7 @@ export class CoreBrowserService extends Disposable implements ICoreBrowserServic

private _isFocused = false;
private _cachedIsFocused: boolean | undefined = undefined;
private _screenDprMonitor = new ScreenDprMonitor(this._window);
private _screenDprMonitor = this.register(new ScreenDprMonitor(this._window));

private readonly _onDprChange = this.register(new EventEmitter<number>());
public readonly onDprChange = this._onDprChange.event;
Expand All @@ -31,8 +31,12 @@ export class CoreBrowserService extends Disposable implements ICoreBrowserServic
this.register(this.onWindowChange(w => this._screenDprMonitor.setWindow(w)));
this.register(forwardEvent(this._screenDprMonitor.onDprChange, this._onDprChange));

this._textarea.addEventListener('focus', () => this._isFocused = true);
this._textarea.addEventListener('blur', () => this._isFocused = false);
this.register(
addDisposableDomListener(this._textarea, 'focus', () => (this._isFocused = true))
);
this.register(
addDisposableDomListener(this._textarea, 'blur', () => (this._isFocused = false))
);
}

public get window(): Window & typeof globalThis {
Expand Down

0 comments on commit 2411578

Please sign in to comment.