Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d49ec968a1 | ||
|
|
877b37d3cc | ||
|
|
f14f55c951 | ||
|
|
8a16a551e0 | ||
|
|
2a5239578a | ||
|
|
d28eb696ec | ||
|
|
4c0c2559c3 | ||
|
|
ee0a3deb6a | ||
|
|
076f051bb5 | ||
|
|
658c57546c | ||
|
|
223a650e3e | ||
|
|
4b6316e510 | ||
|
|
41546230dc | ||
|
|
8413c0f94b | ||
|
|
8828b47cea | ||
|
|
b0f300e61b |
@@ -0,0 +1,10 @@
|
||||
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`
|
||||
@@ -0,0 +1,36 @@
|
||||
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,6 +14,10 @@ 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-beta31",
|
||||
"node-pty": "1.1.0-beta19",
|
||||
"nyc": "^15.1.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
|
||||
@@ -320,37 +320,7 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +352,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: { start: number, newLines: IBufferLine[] }[] = [];
|
||||
const toInsert = [];
|
||||
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--) {
|
||||
@@ -497,20 +467,6 @@ 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.
|
||||
@@ -564,35 +520,6 @@ 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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -125,17 +125,17 @@ 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
|
||||
it('should return \\x1b[5D for alt+left', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: false }).key, '\x1b[1;5D'); // CSI 5 D
|
||||
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
|
||||
});
|
||||
it('should return \\x1b[5C for alt+right', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: false }).key, '\x1b[1;5C'); // CSI 5 C
|
||||
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
|
||||
});
|
||||
it('should return \\x1b[5D for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: false }).key, '\x1b[1;5A'); // CSI 5 D
|
||||
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
|
||||
});
|
||||
it('should return \\x1b[5C for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: false }).key, '\x1b[1;5B'); // CSI 5 C
|
||||
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
|
||||
});
|
||||
it('should return \\x1ba for alt+a', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: false }).key, '\x1ba');
|
||||
@@ -149,17 +149,17 @@ describe('Keyboard', () => {
|
||||
});
|
||||
|
||||
describe('On macOS platforms', () => {
|
||||
it('should return \\x1bb for alt+left', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 37 }, { isMac: true }).key, '\x1bb'); // CSI 5 D
|
||||
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
|
||||
});
|
||||
it('should return \\x1bf for alt+right', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 39 }, { isMac: true }).key, '\x1bf'); // CSI 5 C
|
||||
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
|
||||
});
|
||||
it('should return \\x1bb for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }, { isMac: true }).key, '\x1b[1;3A'); // CSI 5 D
|
||||
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
|
||||
});
|
||||
it('should return \\x1bf for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }, { isMac: true }).key, '\x1b[1;3B'); // CSI 5 C
|
||||
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
|
||||
});
|
||||
it('should return undefined for alt+a', () => {
|
||||
assert.strictEqual(testEvaluateKeyboardEvent({ altKey: true, keyCode: 65 }, { isMac: true }).key, undefined);
|
||||
@@ -176,11 +176,11 @@ describe('Keyboard', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return \\x1b[5A for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A
|
||||
it('should return \\x1b[1;3A for alt+up', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 38 }).key, '\x1b[1;3A'); // CSI 1;3 A
|
||||
});
|
||||
it('should return \\x1b[5B for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
|
||||
it('should return \\x1b[1;3B for alt+down', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 40 }).key, '\x1b[1;3B'); // CSI 1;3 B
|
||||
});
|
||||
it('should return the correct escape sequence for modified F1-F12 keys', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P');
|
||||
@@ -336,6 +336,19 @@ 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');
|
||||
});
|
||||
|
||||
@@ -117,12 +117,6 @@ export function evaluateKeyboardEvent(
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';
|
||||
// HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// macOS uses different escape sequences than linux
|
||||
if (result.key === C0.ESC + '[1;3D') {
|
||||
result.key = C0.ESC + (isMac ? 'b' : '[1;5D');
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
result.key = C0.ESC + 'OD';
|
||||
} else {
|
||||
@@ -136,12 +130,6 @@ export function evaluateKeyboardEvent(
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';
|
||||
// HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// macOS uses different escape sequences than linux
|
||||
if (result.key === C0.ESC + '[1;3C') {
|
||||
result.key = C0.ESC + (isMac ? 'f' : '[1;5C');
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
result.key = C0.ESC + 'OC';
|
||||
} else {
|
||||
@@ -155,12 +143,6 @@ export function evaluateKeyboardEvent(
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';
|
||||
// HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// macOS uses different escape sequences than linux
|
||||
if (!isMac && result.key === C0.ESC + '[1;3A') {
|
||||
result.key = C0.ESC + '[1;5A';
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
result.key = C0.ESC + 'OA';
|
||||
} else {
|
||||
@@ -174,12 +156,6 @@ export function evaluateKeyboardEvent(
|
||||
}
|
||||
if (modifiers) {
|
||||
result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';
|
||||
// HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
|
||||
// http://unix.stackexchange.com/a/108106
|
||||
// macOS uses different escape sequences than linux
|
||||
if (!isMac && result.key === C0.ESC + '[1;3B') {
|
||||
result.key = C0.ESC + '[1;5B';
|
||||
}
|
||||
} else if (applicationCursorMode) {
|
||||
result.key = C0.ESC + 'OB';
|
||||
} else {
|
||||
|
||||
@@ -3302,10 +3302,10 @@ node-preload@^0.2.1:
|
||||
dependencies:
|
||||
process-on-spawn "^1.0.0"
|
||||
|
||||
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==
|
||||
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==
|
||||
dependencies:
|
||||
node-addon-api "^7.1.0"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user