Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3504e2ebd5 | ||
|
|
6c22e3df6b | ||
|
|
33bf765b80 | ||
|
|
d960793f0c | ||
|
|
4975f389ff | ||
|
|
4649ad7e7b | ||
|
|
a425c325b7 | ||
|
|
c77025b18c | ||
|
|
c6abc85810 | ||
|
|
710223ab27 | ||
|
|
ca9df42a73 | ||
|
|
9f033cb1d2 | ||
|
|
9613f36280 | ||
|
|
41673f2c20 | ||
|
|
6c98f82ffe | ||
|
|
a90ef52bda | ||
|
|
86884d2d88 | ||
|
|
47c486e1be | ||
|
|
e07b9684bb | ||
|
|
10fb48869e | ||
|
|
9f3746a3a1 | ||
|
|
50fc84f7d7 | ||
|
|
3e0f0331b3 | ||
|
|
b918f0d34a | ||
|
|
f0e3159ece | ||
|
|
ecfc7336c5 | ||
|
|
aec8c68bf6 | ||
|
|
0fb63561fb | ||
|
|
106ea62b4c | ||
|
|
e73bf11849 | ||
|
|
8d9eda5d0a | ||
|
|
cf0b7ce2d0 | ||
|
|
60419a7cc6 | ||
|
|
1b12b2d15d | ||
|
|
b5844990c4 | ||
|
|
2652768310 | ||
|
|
1f8a6bece4 | ||
|
|
8817ddf020 | ||
|
|
5d61956aff | ||
|
|
83ece031a7 | ||
|
|
566fad4108 | ||
|
|
a4aa111eaa | ||
|
|
f6ed184d49 | ||
|
|
c1820501a8 | ||
|
|
c7f50c5c28 | ||
|
|
371c96e1da | ||
|
|
11bd8b7eb1 | ||
|
|
cb418eb8d1 | ||
|
|
f811fb937e | ||
|
|
2a6ccb8009 | ||
|
|
bf49f99810 | ||
|
|
e027a5f97b | ||
|
|
d1ecb571d8 | ||
|
|
1e3937830f | ||
|
|
38b2fcb460 | ||
|
|
c7ed42226f | ||
|
|
d0dfad4c92 | ||
|
|
c66123b91f | ||
|
|
8d912e891e | ||
|
|
23fd6b8e60 | ||
|
|
0320e6e22a | ||
|
|
9e70d8fc84 | ||
|
|
27e28eccac | ||
|
|
0cb0313fc9 | ||
|
|
94a34c3703 | ||
|
|
c04c836f12 | ||
|
|
c5a9d2c52d | ||
|
|
c0050672ab | ||
|
|
705f94518f | ||
|
|
a096217526 | ||
|
|
795768667e | ||
|
|
5e9a8c6321 | ||
|
|
03df0565bd | ||
|
|
3ef6856b36 | ||
|
|
48e9f33263 | ||
|
|
9559dd47da | ||
|
|
ad7f8c303c | ||
|
|
edb7a0edda | ||
|
|
aedcbba211 | ||
|
|
ab63b51681 | ||
|
|
63f5d0c266 | ||
|
|
349ebd3846 | ||
|
|
ce496bc389 | ||
|
|
be1af82ee0 | ||
|
|
11bfb03add | ||
|
|
b89d4b102e | ||
|
|
0abd121653 | ||
|
|
eeb095b095 | ||
|
|
266a289520 | ||
|
|
3c1d6b575a | ||
|
|
bd08862074 | ||
|
|
70e0c0385a | ||
|
|
454cf1fb26 |
@@ -1,16 +0,0 @@
|
||||
node_modules/
|
||||
*.swp
|
||||
.lock-wscript
|
||||
lib/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
*.node
|
||||
example/*.log
|
||||
docs/
|
||||
npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
.vscode/
|
||||
.git/
|
||||
@@ -40,7 +40,6 @@
|
||||
"readonly": "generic"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/class-name-casing": "warn",
|
||||
"@typescript-eslint/consistent-type-definitions": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"warn",
|
||||
@@ -61,10 +60,6 @@
|
||||
"warn",
|
||||
2
|
||||
],
|
||||
"@typescript-eslint/interface-name-prefix": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"warn",
|
||||
{
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
FROM node:12
|
||||
MAINTAINER Paris Kasidiaris <paris@sourcelair.com>
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Set an entrypoint, to automatically install node modules
|
||||
ENTRYPOINT ["/bin/bash", "-c", "if [[ ! -d node_modules ]]; then npm install; fi; exec \"${@:0}\";"]
|
||||
CMD ["npm", "run", "start"]
|
||||
|
||||
# First, install dependencies to improve layer caching
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
# Add the code
|
||||
COPY . /usr/src/app
|
||||
|
||||
# Run the tests and build, to make sure everything is working nicely
|
||||
RUN npm run build && npm run test
|
||||
@@ -165,6 +165,9 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**DockerStacks**](https://docker-stacks.com/): Local LAMP/LEMP development studio
|
||||
- [**Codecademy**](https://codecademy.com/): Uses xterm.js in its courses on Bash.
|
||||
- [**Laravel Ssh Web Client**](https://github.com/roke22/Laravel-ssh-client): Laravel server inventory with ssh web client to connect at server using xterm.js
|
||||
- [**Repl.it**](https://repl.it): Collaborative browser based IDE with support for 50+ different languages.
|
||||
- [**TeleType**](https://github.com/akshaykmr/TeleType): cli tool that allows you to share your terminal online conveniently. Show off mad cli-fu, help a colleague, teach, or troubleshoot.
|
||||
- [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages supported, with results displayed by xterm.js.
|
||||
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
|
||||
|
||||
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 in our list. Note: Please add any new contributions to the end of the list only.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-serialize",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -270,6 +270,35 @@ describe('SerializeAddon', () => {
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize CJK correctly', async () => {
|
||||
const lines = [
|
||||
'中文中文',
|
||||
'12中文',
|
||||
'中文12',
|
||||
'1中文中文中' // this line is going to be wrapped at last character because it has line length of 11 (1+2*5)
|
||||
];
|
||||
const expected = [
|
||||
'中文中文',
|
||||
'12中文',
|
||||
'中文12',
|
||||
'1中文中文',
|
||||
'中'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize CJK Mixed with tab correctly', async () => {
|
||||
const lines = [
|
||||
'中文\t12' // CJK mixed with tab
|
||||
];
|
||||
const expected = [
|
||||
'中文\x1b[4C12'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
});
|
||||
|
||||
function newArray<T>(initial: T | ((index: number) => T), count: number): T[] {
|
||||
|
||||
@@ -138,7 +138,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
// Count number of null cells encountered after the last non-null cell and move the cursor
|
||||
// if a non-null cell is found (eg. \t or cursor move)
|
||||
if (cell.getChars() === '') {
|
||||
this._nullCellCount++;
|
||||
this._nullCellCount += cell.getWidth();
|
||||
} else if (this._nullCellCount > 0) {
|
||||
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
||||
this._nullCellCount = 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-webgl",
|
||||
"version": "0.8.0",
|
||||
"version": "0.9.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -196,7 +196,7 @@ export class RectangleRenderer {
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0;
|
||||
const startRowEndCol = model.viewportCappedStartRow === model.viewportCappedEndRow ? model.endCol : terminal.cols;
|
||||
const startRowEndCol = model.viewportCappedStartRow === model.viewportEndRow ? model.endCol : terminal.cols;
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
0,
|
||||
|
||||
@@ -43,7 +43,6 @@ export class RenderModel implements IRenderModel {
|
||||
public clear(): void {
|
||||
fill(this.cells, 0, 0);
|
||||
fill(this.lineLengths, 0, 0);
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
public clearSelection(): void {
|
||||
|
||||
@@ -40,4 +40,8 @@ export class WebglAddon implements ITerminalAddon {
|
||||
public get textureAtlas(): HTMLCanvasElement | undefined {
|
||||
return this._renderer?.textureAtlas;
|
||||
}
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
this._renderer?.clearCharAtlas();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,12 @@ let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
let itWebgl: (expectation: string, callback?: (this: Mocha.ITestCallbackContext, done: MochaDone) => any) => Mocha.ITest | void;
|
||||
|
||||
describe('WebGL Renderer Integration Tests', async () => {
|
||||
const browserType = getBrowserType();
|
||||
const isHeadless = process.argv.indexOf('--headless') !== -1;
|
||||
// Firefox works only in non-headless mode https://github.com/microsoft/playwright/issues/1032
|
||||
const areTestsEnabled = browserType.name() === 'chromium' || (browserType.name() === 'firefox' && !isHeadless);
|
||||
itWebgl = areTestsEnabled ? it : it.skip;
|
||||
const itWebgl = areTestsEnabled ? it : it.skip;
|
||||
|
||||
itWebgl('dispose removes renderer canvases', async function(): Promise<void> {
|
||||
await setupBrowser();
|
||||
|
||||
@@ -121,6 +121,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Force a full refresh
|
||||
this._model.clear();
|
||||
this._model.clearSelection();
|
||||
}
|
||||
|
||||
public onDevicePixelRatioChange(): void {
|
||||
@@ -158,6 +159,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Force a full refresh
|
||||
this._model.clear();
|
||||
this._model.clearSelection();
|
||||
}
|
||||
|
||||
public onCharSizeChanged(): void {
|
||||
@@ -214,6 +216,14 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._glyphRenderer.setAtlas(this._charAtlas);
|
||||
}
|
||||
|
||||
public clearCharAtlas(): void {
|
||||
this._charAtlas?.clearTexture();
|
||||
this._model.clear();
|
||||
this._updateModel(0, this._terminal.rows - 1);
|
||||
this._glyphRenderer.updateSelection(this._model);
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._renderLayers.forEach(l => l.reset(this._terminal));
|
||||
}
|
||||
@@ -243,6 +253,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// Tell renderer the frame is beginning
|
||||
if (this._glyphRenderer.beginFrame()) {
|
||||
this._model.clear();
|
||||
this._model.clearSelection();
|
||||
}
|
||||
|
||||
// Update model to reflect what's drawn
|
||||
|
||||
@@ -101,7 +101,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
protected _doWarmUp(): void {
|
||||
private _doWarmUp(): void {
|
||||
// Pre-fill with ASCII 33-126
|
||||
for (let i = 33; i < 126; i++) {
|
||||
const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR);
|
||||
@@ -115,17 +115,26 @@ export class WebglCharAtlas implements IDisposable {
|
||||
|
||||
public beginFrame(): boolean {
|
||||
if (this._currentRowY > TEXTURE_CAPACITY) {
|
||||
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
this._cacheMap = {};
|
||||
this._currentRowHeight = 0;
|
||||
this._currentRowX = 0;
|
||||
this._currentRowY = 0;
|
||||
this._doWarmUp();
|
||||
this.clearTexture();
|
||||
this.warmUp();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public clearTexture(): void {
|
||||
if (this._currentRowX === 0 && this._currentRowY === 0) {
|
||||
return;
|
||||
}
|
||||
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
this._cacheMap = {};
|
||||
this._cacheMapCombined = {};
|
||||
this._currentRowHeight = 0;
|
||||
this._currentRowX = 0;
|
||||
this._currentRowY = 0;
|
||||
this._didWarmUp = false;
|
||||
}
|
||||
|
||||
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number): IRasterizedGlyph {
|
||||
let rasterizedGlyphSet = this._cacheMapCombined[chars];
|
||||
if (!rasterizedGlyphSet) {
|
||||
|
||||
+7
-2
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-webgl' {
|
||||
/**
|
||||
@@ -15,7 +15,7 @@ declare module 'xterm-addon-webgl' {
|
||||
constructor(preserveDrawingBuffer?: boolean);
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* Activates the addon.
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
@@ -24,5 +24,10 @@ declare module 'xterm-addon-webgl' {
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
|
||||
/**
|
||||
* Clears the terminal's texture atlas and triggers a redraw.
|
||||
*/
|
||||
public clearTextureAtlas(): void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: xtermjs/xterm.js:latest
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/usr/src/app
|
||||
ports:
|
||||
- ${XTERMJS_PORT:-3000}:3000
|
||||
command: ["npm", "start"]
|
||||
|
||||
watch:
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/usr/src/app
|
||||
command: ["npm", "run", "watch"]
|
||||
+14
-14
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "4.8.0",
|
||||
"version": "4.9.0",
|
||||
"main": "lib/xterm.js",
|
||||
"style": "css/xterm.css",
|
||||
"types": "typings/xterm.d.ts",
|
||||
@@ -36,35 +36,35 @@
|
||||
"vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.11",
|
||||
"@types/chai": "^4.2.12",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/glob": "^7.1.3",
|
||||
"@types/jsdom": "^16.2.3",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/jsdom": "^16.2.4",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@types/node": "^10.17.17",
|
||||
"@types/utf8": "^2.1.6",
|
||||
"@types/webpack": "^4.41.21",
|
||||
"@types/webpack": "^4.41.22",
|
||||
"@types/ws": "^7.2.6",
|
||||
"@typescript-eslint/eslint-plugin": "^2.34.0",
|
||||
"@typescript-eslint/parser": "^2.34.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.0.0",
|
||||
"@typescript-eslint/parser": "^3.10.1",
|
||||
"chai": "^4.2.0",
|
||||
"deep-equal": "^2.0.3",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint": "^7.8.1",
|
||||
"express": "^4.17.1",
|
||||
"express-ws": "^4.0.0",
|
||||
"glob": "^7.0.5",
|
||||
"jsdom": "^16.3.0",
|
||||
"mocha": "^8.0.1",
|
||||
"jsdom": "^16.4.0",
|
||||
"mocha": "^8.1.3",
|
||||
"mustache": "^4.0.1",
|
||||
"node-pty": "^0.9.0",
|
||||
"nyc": "^15.1.0",
|
||||
"playwright-core": "^0.11.1",
|
||||
"source-map-loader": "^1.0.1",
|
||||
"ts-loader": "^8.0.0",
|
||||
"typescript": "3.9",
|
||||
"source-map-loader": "^1.1.0",
|
||||
"ts-loader": "^8.0.3",
|
||||
"typescript": "4.0",
|
||||
"utf8": "^3.0.0",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"ws": "^7.3.1",
|
||||
"xterm-benchmark": "^0.1.3"
|
||||
|
||||
@@ -57,7 +57,7 @@ export class AccessibilityManager extends Disposable {
|
||||
|
||||
this._rowContainer = document.createElement('div');
|
||||
this._rowContainer.classList.add('xterm-accessibility-tree');
|
||||
this._rowContainer.setAttribute('role', 'list');
|
||||
this._rowContainer.setAttribute('role', 'document');
|
||||
this._rowElements = [];
|
||||
for (let i = 0; i < this._terminal.rows; i++) {
|
||||
this._rowElements[i] = this._createAccessibilityTreeNode();
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('Linkifier', () => {
|
||||
bufferService.buffer.lines.push(stringToRow(text));
|
||||
}
|
||||
|
||||
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void {
|
||||
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: Mocha.Done): void {
|
||||
addRow(rowText);
|
||||
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
|
||||
linkifier.linkifyRows();
|
||||
@@ -76,7 +76,7 @@ describe('Linkifier', () => {
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function assertLinkifiesMultiLineLink(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void {
|
||||
function assertLinkifiesMultiLineLink(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: Mocha.Done): void {
|
||||
addRow(rowText);
|
||||
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
|
||||
linkifier.linkifyRows();
|
||||
|
||||
@@ -125,6 +125,13 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
|
||||
private _askForLink(position: IBufferCellPosition, useLineCache: boolean): void {
|
||||
if (!this._activeProviderReplies || !useLineCache) {
|
||||
this._activeProviderReplies?.forEach(reply => {
|
||||
reply?.forEach(linkWithState => {
|
||||
if (linkWithState.link.dispose) {
|
||||
linkWithState.link.dispose();
|
||||
}
|
||||
});
|
||||
});
|
||||
this._activeProviderReplies = new Map();
|
||||
this._activeLine = position.y;
|
||||
}
|
||||
|
||||
@@ -1042,7 +1042,7 @@ describe('Terminal', () => {
|
||||
linkifier.attachToDom({} as any, mouseZoneManager);
|
||||
});
|
||||
|
||||
function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void {
|
||||
function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: Mocha.Done): void {
|
||||
terminal.writeSync(rowText);
|
||||
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
|
||||
linkifier.linkifyRows();
|
||||
|
||||
@@ -778,7 +778,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
if (!this.viewport!.onWheel(ev)) {
|
||||
return this.cancel(ev);
|
||||
}
|
||||
}, { passive: true }));
|
||||
}, { passive: false }));
|
||||
|
||||
this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => {
|
||||
if (this._coreMouseService.areMouseEventsActive) return;
|
||||
@@ -791,7 +791,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
if (!this.viewport!.onTouchMove(ev)) {
|
||||
return this.cancel(ev);
|
||||
}
|
||||
}, { passive: true }));
|
||||
}, { passive: false }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vendored
+1
@@ -275,6 +275,7 @@ interface ILink {
|
||||
activate(event: MouseEvent, text: string): void;
|
||||
hover?(event: MouseEvent, text: string): void;
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
dispose?(): void;
|
||||
}
|
||||
|
||||
interface ILinkDecorations {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, ILinkProvider, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm';
|
||||
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, ILinkProvider, IUnicodeHandling, IUnicodeVersionProvider, FontWeight } from 'xterm';
|
||||
import { ITerminal } from 'browser/Types';
|
||||
import { IBufferLine, ICellData } from 'common/Types';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
@@ -169,15 +169,16 @@ export class Terminal implements ITerminalApi {
|
||||
public paste(data: string): void {
|
||||
this._core.paste(data);
|
||||
}
|
||||
public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
|
||||
public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
|
||||
public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell'): boolean;
|
||||
public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
|
||||
public getOption(key: 'fontWeight' | 'fontWeightBold'): FontWeight;
|
||||
public getOption(key: string): any;
|
||||
public getOption(key: any): any {
|
||||
return this._core.optionsService.getOption(key);
|
||||
}
|
||||
public setOption(key: 'bellSound' | 'fontFamily' | 'termName' | 'wordSeparator', value: string): void;
|
||||
public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void;
|
||||
public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void;
|
||||
public setOption(key: 'logLevel', value: 'debug' | 'info' | 'warn' | 'error' | 'off'): void;
|
||||
public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void;
|
||||
public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void;
|
||||
|
||||
@@ -75,6 +75,7 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
|
||||
// No need to draw the selection
|
||||
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
|
||||
this._state.ydisp = this._bufferService.buffer.ydisp;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,7 +89,7 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
||||
const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
|
||||
const startRowEndCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
|
||||
this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
|
||||
|
||||
// Draw middle rows
|
||||
|
||||
@@ -26,7 +26,7 @@ export abstract class BaseCharAtlas implements IDisposable {
|
||||
* Perform any work needed to warm the cache before it can be used. Used by the default
|
||||
* implementation of warmUp(), and will only be called once.
|
||||
*/
|
||||
protected _doWarmUp(): void { }
|
||||
private _doWarmUp(): void { }
|
||||
|
||||
/**
|
||||
* Called when we start drawing a new frame.
|
||||
|
||||
@@ -306,7 +306,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
||||
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
|
||||
const endCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
|
||||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
|
||||
// Draw middle rows
|
||||
const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
|
||||
|
||||
@@ -86,7 +86,7 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
}
|
||||
|
||||
private _onIntersectionChange(entry: IntersectionObserverEntry): void {
|
||||
this._isPaused = !entry.isIntersecting;
|
||||
this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting;
|
||||
if (!this._isPaused && this._needsFullRefresh) {
|
||||
this.refreshRows(0, this._rowCount - 1);
|
||||
this._needsFullRefresh = false;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { OptionsService, DEFAULT_OPTIONS } from 'common/services/OptionsService';
|
||||
|
||||
describe('OptionsService', () => {
|
||||
describe('constructor', () => {
|
||||
const originalError = console.error;
|
||||
beforeEach(() => {
|
||||
console.error = () => {};
|
||||
});
|
||||
afterEach(() => {
|
||||
console.error = originalError;
|
||||
});
|
||||
it('uses default value if invalid constructor option value passed', () => {
|
||||
assert.equal(new OptionsService({tabStopWidth: 0}).getOption('tabStopWidth'), DEFAULT_OPTIONS.tabStopWidth);
|
||||
});
|
||||
});
|
||||
describe('setOption', () => {
|
||||
let service: OptionsService;
|
||||
beforeEach(() => {
|
||||
service = new OptionsService({});
|
||||
});
|
||||
it('applies valid fontWeight option values', () => {
|
||||
service.setOption('fontWeight', 'bold');
|
||||
assert.equal(service.getOption('fontWeight'), 'bold', '"bold" keyword value should be applied');
|
||||
|
||||
service.setOption('fontWeight', 'normal');
|
||||
assert.equal(service.getOption('fontWeight'), 'normal', '"normal" keyword value should be applied');
|
||||
|
||||
service.setOption('fontWeight', '600');
|
||||
assert.equal(service.getOption('fontWeight'), '600', 'String numeric values should be applied');
|
||||
|
||||
service.setOption('fontWeight', 350);
|
||||
assert.equal(service.getOption('fontWeight'), 350, 'Values between 1 and 1000 should be applied as is');
|
||||
|
||||
service.setOption('fontWeight', 1);
|
||||
assert.equal(service.getOption('fontWeight'), 1, 'Range should include minimum value: 1');
|
||||
|
||||
service.setOption('fontWeight', 1000);
|
||||
assert.equal(service.getOption('fontWeight'), 1000, 'Range should include maximum value: 1000');
|
||||
});
|
||||
it('normalizes invalid fontWeight option values', () => {
|
||||
service.setOption('fontWeight', 350);
|
||||
assert.doesNotThrow(() => service.setOption('fontWeight', 10000), 'fontWeight should be normalized instead of throwing');
|
||||
assert.equal(service.getOption('fontWeight'), DEFAULT_OPTIONS.fontWeight, 'Values greater than 1000 should be reset to default');
|
||||
|
||||
service.setOption('fontWeight', 350);
|
||||
service.setOption('fontWeight', -10);
|
||||
assert.equal(service.getOption('fontWeight'), DEFAULT_OPTIONS.fontWeight, 'Values less than 1 should be reset to default');
|
||||
|
||||
service.setOption('fontWeight', 350);
|
||||
service.setOption('fontWeight', 'bold700');
|
||||
assert.equal(service.getOption('fontWeight'), DEFAULT_OPTIONS.fontWeight, 'Wrong string literals should be reset to default');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/services/Services';
|
||||
import { IOptionsService, ITerminalOptions, IPartialTerminalOptions, FontWeight } from 'common/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { isMac } from 'common/Platform';
|
||||
import { clone } from 'common/Clone';
|
||||
@@ -56,6 +56,8 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
|
||||
cancelEvents: false
|
||||
});
|
||||
|
||||
const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
|
||||
|
||||
/**
|
||||
* The set of options that only have an effect when set in the Terminal constructor.
|
||||
*/
|
||||
@@ -73,8 +75,12 @@ export class OptionsService implements IOptionsService {
|
||||
this.options = clone(DEFAULT_OPTIONS);
|
||||
for (const k of Object.keys(options)) {
|
||||
if (k in this.options) {
|
||||
const newValue = options[k as keyof IPartialTerminalOptions] as any;
|
||||
this.options[k] = newValue;
|
||||
try {
|
||||
const newValue = options[k as keyof IPartialTerminalOptions] as any;
|
||||
this.options[k] = this._sanitizeAndValidateOption(k, newValue);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,14 +111,20 @@ export class OptionsService implements IOptionsService {
|
||||
switch (key) {
|
||||
case 'bellStyle':
|
||||
case 'cursorStyle':
|
||||
case 'fontWeight':
|
||||
case 'fontWeightBold':
|
||||
case 'rendererType':
|
||||
case 'wordSeparator':
|
||||
if (!value) {
|
||||
value = DEFAULT_OPTIONS[key];
|
||||
}
|
||||
break;
|
||||
case 'fontWeight':
|
||||
case 'fontWeightBold':
|
||||
if (typeof value === 'number' && 1 <= value && value <= 1000) {
|
||||
// already valid numeric value
|
||||
break;
|
||||
}
|
||||
value = FONT_WEIGHT_OPTIONS.indexOf(value) !== -1 ? value : DEFAULT_OPTIONS[key];
|
||||
break;
|
||||
case 'cursorWidth':
|
||||
value = Math.floor(value);
|
||||
// Fall through for bounds check
|
||||
|
||||
@@ -179,7 +179,7 @@ export interface IOptionsService {
|
||||
getOption<T>(key: string): T | undefined;
|
||||
}
|
||||
|
||||
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
|
||||
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
|
||||
export type RendererType = 'dom' | 'canvas';
|
||||
|
||||
|
||||
@@ -755,6 +755,57 @@ describe('API Integration Tests', function(): void {
|
||||
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'provide 2', 'provide 1', 'hover 9-11']);
|
||||
await page.evaluate(`window.disposable.dispose()`);
|
||||
});
|
||||
|
||||
it('should dispose links when hovering away', async () => {
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
await writeSync(page, 'foo bar baz');
|
||||
// Wait for renderer to catch up as links are cleared on render
|
||||
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.disposable = window.term.registerLinkProvider({
|
||||
provideLinks: (position, cb) => {
|
||||
window.calls.push('provide ' + position);
|
||||
if (position === 1) {
|
||||
cb([{
|
||||
range: { start: { x: 1, y: 1 }, end: { x: 3, y: 1 } },
|
||||
text: '',
|
||||
activate: () => window.calls.push('activate'),
|
||||
dispose: () => window.calls.push('dispose 1-3'),
|
||||
hover: () => window.calls.push('hover 1-3'),
|
||||
leave: () => window.calls.push('leave 1-3')
|
||||
}, {
|
||||
range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },
|
||||
text: '',
|
||||
activate: () => window.calls.push('activate'),
|
||||
dispose: () => window.calls.push('dispose 5-7'),
|
||||
hover: () => window.calls.push('hover 5-7'),
|
||||
leave: () => window.calls.push('leave 5-7')
|
||||
}, {
|
||||
range: { start: { x: 9, y: 1 }, end: { x: 11, y: 1 } },
|
||||
text: '',
|
||||
activate: () => window.calls.push('activate'),
|
||||
dispose: () => window.calls.push('dispose 9-11'),
|
||||
hover: () => window.calls.push('hover 9-11'),
|
||||
leave: () => window.calls.push('leave 9-11')
|
||||
}]);
|
||||
}
|
||||
}
|
||||
});
|
||||
`);
|
||||
const dims = await getDimensions();
|
||||
await moveMouseCell(page, dims, 2, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3']);
|
||||
await moveMouseCell(page, dims, 6, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7']);
|
||||
await moveMouseCell(page, dims, 6, 2);
|
||||
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2']);
|
||||
await moveMouseCell(page, dims, 10, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2', 'provide 1', 'hover 9-11']);
|
||||
await moveMouseCell(page, dims, 10, 2);
|
||||
await pollFor(page, `window.calls`, ['provide 1', 'hover 1-3', 'leave 1-3', 'hover 5-7', 'leave 5-7', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2', 'provide 1', 'hover 9-11', 'leave 9-11', 'dispose 1-3', 'dispose 5-7', 'dispose 9-11', 'provide 2']);
|
||||
await page.evaluate(`window.disposable.dispose()`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Vendored
+14
-4
@@ -11,9 +11,9 @@
|
||||
|
||||
declare module 'xterm' {
|
||||
/**
|
||||
* A string representing text font weight.
|
||||
* A string or number representing text font weight.
|
||||
*/
|
||||
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
|
||||
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;
|
||||
|
||||
/**
|
||||
* A string representing log level.
|
||||
@@ -933,7 +933,7 @@ declare module 'xterm' {
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param key The option key.
|
||||
*/
|
||||
getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
|
||||
getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
|
||||
/**
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param key The option key.
|
||||
@@ -944,6 +944,11 @@ declare module 'xterm' {
|
||||
* @param key The option key.
|
||||
*/
|
||||
getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
|
||||
/**
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param key The option key.
|
||||
*/
|
||||
getOption(key: 'fontWeight' | 'fontWeightBold'): FontWeight;
|
||||
/**
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param key The option key.
|
||||
@@ -961,7 +966,7 @@ declare module 'xterm' {
|
||||
* @param key The option key.
|
||||
* @param value The option value.
|
||||
*/
|
||||
setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void;
|
||||
setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void;
|
||||
/**
|
||||
* Sets an option on the terminal.
|
||||
* @param key The option key.
|
||||
@@ -1158,6 +1163,11 @@ declare module 'xterm' {
|
||||
* @param text The text of the link.
|
||||
*/
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
|
||||
/**
|
||||
* Called when the link is released and no longer used by xterm.js.
|
||||
*/
|
||||
dispose?(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user