Compare commits

...
Author SHA1 Message Date
copilot-swe-agent[bot] d49ec968a1 Initial plan for issue 2025-06-21 17:56:58 +00:00
Daniel Imms 877b37d3cc Merge pull request #5359 from Tyriar/tyriar/copilot_steps
Add copilot setup steps
2025-06-21 10:55:11 -07:00
Daniel Imms f14f55c951 Add copilot setup steps 2025-06-21 10:54:46 -07:00
Daniel Imms 8a16a551e0 Merge pull request #5358 from Tyriar/tyriar/reflow_bounds
Revert "Merge pull request #5321 from Tyriar/5319"
2025-06-18 06:17:46 -07:00
Daniel Imms 2a5239578a Revert "Merge pull request #5321 from Tyriar/5319"
This reverts commit a260f7d288, reversing
changes made to 2042bb8502.
2025-06-18 06:09:53 -07:00
Daniel Imms d28eb696ec Merge pull request #5355 from Tyriar/tyriar/3634
Add note about reverse tabnapping
2025-06-13 08:41:38 -07:00
Daniel Imms 4c0c2559c3 Add note about reverse tabnapping
Fixes #3634
2025-06-13 08:19:31 -07:00
Daniel Imms ee0a3deb6a Merge pull request #5349 from xtermjs/copilot/fix-3382
Add comprehensive tests for alt+shift+letter keyboard combinations
2025-06-05 03:47:13 -07:00
copilot-swe-agent[bot]andTyriar 076f051bb5 Fix ESLint trailing spaces warning in Keyboard.test.ts
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2025-06-05 10:34:53 +00:00
copilot-swe-agent[bot]andTyriar 658c57546c Add comprehensive tests for alt+shift+letter keyboard combinations
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2025-06-05 10:24:34 +00:00
copilot-swe-agent[bot] 223a650e3e Initial plan for issue 2025-06-05 10:13:25 +00:00
Daniel Imms 4b6316e510 Merge pull request #5346 from xtermjs/copilot/fix-4538-2
Remove alt -> ctrl+arrow hack in favor of embedder-specific solutions
2025-05-31 06:59:41 -07:00
copilot-swe-agent[bot]andTyriar 41546230dc Remove alt -> ctrl+arrow hack and update tests
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2025-05-29 13:20:54 +00:00
copilot-swe-agent[bot] 8413c0f94b Initial plan for issue 2025-05-29 13:09:01 +00:00
Daniel Imms 8828b47cea Merge pull request #5345 from Tyriar/tyriar/instruct
Add instructions to help Copilot coding agent
2025-05-29 06:05:50 -07:00
Daniel Imms b0f300e61b Add instructions to help Copilot coding agent 2025-05-29 06:00:41 -07:00
8 changed files with 89 additions and 123 deletions
+10
View File
@@ -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`
+36
View File
@@ -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
+4
View File
@@ -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
View File
@@ -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",
+1 -74
View File
@@ -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
});
}
}
}
}
/**
+33 -20
View File
@@ -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');
});
-24
View File
@@ -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 {
+4 -4
View File
@@ -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"