Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a66f9e9066 | ||
|
|
bd71b62ca6 |
@@ -1,10 +0,0 @@
|
||||
This is a TypeScript-based repository with a Ruby client for certain API endpoints. It includes several sub projects called addons which are built separately. It contains a demo application which showcases the functionality of the project.
|
||||
|
||||
Please follow these guidelines when contributing:
|
||||
|
||||
## Development Flow
|
||||
|
||||
- Install dependencies: `npm install && npm run setup`
|
||||
- Build and bundle demo: `npm run build && npm run esbuild`
|
||||
- Run unit tests: `npm run test-unit`
|
||||
- Run playwright-based integration tests: `npm run test-integration`
|
||||
@@ -1,36 +0,0 @@
|
||||
name: "Copilot Setup Steps"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
|
||||
jobs:
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
|
||||
- name: Setup and run tsc
|
||||
run: yarn setup
|
||||
|
||||
- name: Esbuild
|
||||
run: yarn esbuild
|
||||
@@ -14,10 +14,6 @@ declare module '@xterm/addon-web-links' {
|
||||
/**
|
||||
* Creates a new web links addon.
|
||||
* @param handler The callback when the link is called.
|
||||
*
|
||||
* Note that this may not work when the terminal is hosted inside an iframe,
|
||||
* in that case provide a custom handler in that case being mindful of
|
||||
* possible security issues like reverse tabnapping.
|
||||
* @param options Options for the link provider.
|
||||
*/
|
||||
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkProviderOptions);
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@
|
||||
"jsdom": "^18.0.1",
|
||||
"mocha": "^10.1.0",
|
||||
"mustache": "^4.2.0",
|
||||
"node-pty": "1.1.0-beta19",
|
||||
"node-pty": "^1.1.0-beta31",
|
||||
"nyc": "^15.1.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
|
||||
@@ -320,7 +320,37 @@ export class Buffer implements IBuffer {
|
||||
if (toRemove.length > 0) {
|
||||
const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove);
|
||||
reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout);
|
||||
|
||||
// For conpty, it has its own copy of the buffer _without scrollback_ internally. Its behavior
|
||||
// when reflowing larger is to insert empty lines at the bottom of the buffer as when lines
|
||||
// unwrap conpty's view cannot pull scrollback down, so it adds empty lines at the end.
|
||||
let removedInViewport = 0;
|
||||
const isWindowsMode = this._optionsService.rawOptions.windowsMode || this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined;
|
||||
if (isWindowsMode) {
|
||||
for (let i = (toRemove.length / 2) - 1; i >= 0; i--) {
|
||||
if (toRemove[i * 2 + 0] > this.ybase + removedInViewport) {
|
||||
removedInViewport += toRemove[i * 2 + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._reflowLargerAdjustViewport(newCols, newRows, newLayoutResult.countRemoved);
|
||||
|
||||
// Apply empty lines for any removed in viewport for conpty.
|
||||
if (isWindowsMode) {
|
||||
if (removedInViewport > 0) {
|
||||
for (let i = 0; i < removedInViewport; i++) {
|
||||
// Just add the new missing rows on Windows as conpty reprints the screen with it's
|
||||
// view of the world. Once a line enters scrollback for conpty it remains there
|
||||
this.lines.push(new BufferLine(newCols, this.getNullCell(DEFAULT_ATTR_DATA)));
|
||||
}
|
||||
if (this.ybase === this.ydisp) {
|
||||
this.ydisp += removedInViewport;
|
||||
}
|
||||
this.ybase += removedInViewport;
|
||||
this.y -= removedInViewport;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +382,7 @@ export class Buffer implements IBuffer {
|
||||
const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
|
||||
// Gather all BufferLines that need to be inserted into the Buffer here so that they can be
|
||||
// batched up and only committed once
|
||||
const toInsert = [];
|
||||
const toInsert: { start: number, newLines: IBufferLine[] }[] = [];
|
||||
let countToInsert = 0;
|
||||
// Go backwards as many lines may be trimmed and this will avoid considering them
|
||||
for (let y = this.lines.length - 1; y >= 0; y--) {
|
||||
@@ -467,6 +497,20 @@ export class Buffer implements IBuffer {
|
||||
this.savedY = Math.min(this.savedY + linesToAdd, this.ybase + newRows - 1);
|
||||
}
|
||||
|
||||
// For conpty, it has its own copy of the buffer _without scrollback_ internally. Its behavior
|
||||
// when reflowing smaller is to reflow all lines inside the viewport, and removing empty or
|
||||
// whitespace only lines from the bottom, until non-whitespace is hit in order to prevent
|
||||
// content from being pushed into the scrollback.
|
||||
let addedInViewport = 0;
|
||||
const isWindowsMode = this._optionsService.rawOptions.windowsMode || this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined;
|
||||
if (isWindowsMode) {
|
||||
for (let i = toInsert.length - 1; i >= 0; i--) {
|
||||
if (toInsert[i].start > this.ybase + addedInViewport) {
|
||||
addedInViewport += toInsert[i].newLines.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rearrange lines in the buffer if there are any insertions, this is done at the end rather
|
||||
// than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
|
||||
// costly calls to CircularList.splice.
|
||||
@@ -520,6 +564,35 @@ export class Buffer implements IBuffer {
|
||||
this.lines.onTrimEmitter.fire(amountToTrim);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply empty lines to remove calculated earlier for conpty.
|
||||
if (isWindowsMode) {
|
||||
if (addedInViewport > 0) {
|
||||
let emptyLinesAtBottom = 0;
|
||||
for (let i = this.lines.length - 1; i >= this.ybase + this.y; i--) {
|
||||
const line = this.lines.get(i) as BufferLine;
|
||||
if (line.isWrapped || line.getTrimmedLength() > 0) {
|
||||
break;
|
||||
}
|
||||
emptyLinesAtBottom++;
|
||||
}
|
||||
const emptyLinesToRemove = Math.min(addedInViewport, emptyLinesAtBottom);
|
||||
if (emptyLinesToRemove > 0) {
|
||||
for (let i = 0; i < emptyLinesToRemove; i++) {
|
||||
this.lines.pop();
|
||||
}
|
||||
if (this.ybase === this.ydisp) {
|
||||
this.ydisp -= emptyLinesToRemove;
|
||||
}
|
||||
this.ybase -= emptyLinesToRemove;
|
||||
this.y += emptyLinesToRemove;
|
||||
this.lines.onDeleteEmitter.fire({
|
||||
index: this.lines.length - emptyLinesToRemove,
|
||||
amount: emptyLinesToRemove
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -123,19 +123,18 @@ describe('Keyboard', () => {
|
||||
});
|
||||
|
||||
describe('On non-macOS platforms', () => {
|
||||
// Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// Alt + arrow keys should now emit the standard Alt escape sequences instead of Ctrl sequences
|
||||
it('should return \\x1b[1;3D for alt+left', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: false }).key, '\x1b[1;3D'); // CSI 1;3 D
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: false }).key, '\x1b[1;3D'); // Standard Alt+Left
|
||||
});
|
||||
it('should return \\x1b[1;3C for alt+right', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: false }).key, '\x1b[1;3C'); // CSI 1;3 C
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: false }).key, '\x1b[1;3C'); // Standard Alt+Right
|
||||
});
|
||||
it('should return \\x1b[1;3A for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: false }).key, '\x1b[1;3A'); // CSI 1;3 A
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: false }).key, '\x1b[1;3A'); // Standard Alt+Up
|
||||
});
|
||||
it('should return \\x1b[1;3B for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: false }).key, '\x1b[1;3B'); // CSI 1;3 B
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: false }).key, '\x1b[1;3B'); // Standard Alt+Down
|
||||
});
|
||||
it('should return \\x1ba for alt+a', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: false }).key, '\x1ba');
|
||||
@@ -150,16 +149,16 @@ describe('Keyboard', () => {
|
||||
|
||||
describe('On macOS platforms', () => {
|
||||
it('should return \\x1b[1;3D for alt+left', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: true }).key, '\x1b[1;3D'); // CSI 1;3 D
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: true }).key, '\x1b[1;3D'); // Standard Alt+Left
|
||||
});
|
||||
it('should return \\x1b[1;3C for alt+right', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: true }).key, '\x1b[1;3C'); // CSI 1;3 C
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: true }).key, '\x1b[1;3C'); // Standard Alt+Right
|
||||
});
|
||||
it('should return \\x1b[1;3A for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: true }).key, '\x1b[1;3A'); // CSI 1;3 A
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: true }).key, '\x1b[1;3A'); // Standard Alt+Up
|
||||
});
|
||||
it('should return \\x1b[1;3B for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: true }).key, '\x1b[1;3B'); // CSI 1;3 B
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: true }).key, '\x1b[1;3B'); // Standard Alt+Down
|
||||
});
|
||||
it('should return undefined for alt+a', () => {
|
||||
assert.strictEqual(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true }).key, undefined);
|
||||
@@ -177,10 +176,10 @@ describe('Keyboard', () => {
|
||||
});
|
||||
|
||||
it('should return \\x1b[1;3A for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }).key, '\x1b[1;3A'); // CSI 1;3 A
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }).key, '\x1b[1;3A'); // Standard Alt+Up
|
||||
});
|
||||
it('should return \\x1b[1;3B for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }).key, '\x1b[1;3B'); // CSI 1;3 B
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }).key, '\x1b[1;3B'); // Standard Alt+Down
|
||||
});
|
||||
it('should return the correct escape sequence for modified F1-F12 keys', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P');
|
||||
@@ -336,19 +335,6 @@ describe('Keyboard', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 49, key: '!' }).key, '!');
|
||||
});
|
||||
|
||||
// Characters using alt+shift sequences (letters)
|
||||
it('should return proper sequences for alt+shift+letter combinations', () => {
|
||||
// Test alt+shift combinations produce uppercase letters
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 65 }).key, '\x1bA'); // alt+shift+a
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 72 }).key, '\x1bH'); // alt+shift+h
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: true, keyCode: 90 }).key, '\x1bZ'); // alt+shift+z
|
||||
|
||||
// Test alt without shift produces lowercase letters
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 65 }).key, '\x1ba'); // alt+a
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 72 }).key, '\x1bh'); // alt+h
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, shiftKey: false, keyCode: 90 }).key, '\x1bz'); // alt+z
|
||||
});
|
||||
|
||||
it('should return proper sequence for ctrl+@', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, shiftKey: true, keyCode: 50, key: '@' }).key, '\x00');
|
||||
});
|
||||
|
||||
@@ -3302,10 +3302,10 @@ node-preload@^0.2.1:
|
||||
dependencies:
|
||||
process-on-spawn "^1.0.0"
|
||||
|
||||
node-pty@1.1.0-beta19:
|
||||
version "1.1.0-beta19"
|
||||
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta19.tgz#a74dc04429903c5ac49ee81a15a24590da67d4f3"
|
||||
integrity sha512-/p4Zu56EYDdXjjaLWzrIlFyrBnND11LQGP0/L6GEVGURfCNkAlHc3Twg/2I4NPxghimHXgvDlwp7Z2GtvDIh8A==
|
||||
node-pty@^1.1.0-beta31:
|
||||
version "1.1.0-beta9"
|
||||
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta9.tgz#ed643cb3b398d031b4e31c216e8f3b0042435f1d"
|
||||
integrity sha512-/Ue38pvXJdgRZ3+me1FgfglLd301GhJN0NStiotdt61tm43N5htUyR/IXOUzOKuNaFmCwIhy6nwb77Ky41LMbw==
|
||||
dependencies:
|
||||
node-addon-api "^7.1.0"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user