Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions src/client/wetty/term/confiruragtion.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { expect } from 'chai';
import 'mocha';
import * as sinon from 'sinon';
import { modifierHandler } from './confiruragtion';

describe('modifierHandler', () => {
let inputStub: sinon.SinonStub;

beforeEach(() => {
inputStub = sinon.stub();
(window as any).wetty_term = {

Check warning on line 11 in src/client/wetty/term/confiruragtion.spec.ts

View workflow job for this annotation

GitHub Actions / Build & Test / Build & Test

Unexpected any. Specify a different type
input: inputStub
};
});

afterEach(() => {
delete (window as any).wetty_term;

Check warning on line 17 in src/client/wetty/term/confiruragtion.spec.ts

View workflow job for this annotation

GitHub Actions / Build & Test / Build & Test

Unexpected any. Specify a different type
sinon.restore();
});

it('should allow normal Enter to pass through', () => {
const event = {
type: 'keydown',
key: 'Enter',
shiftKey: false,
altKey: false,
ctrlKey: false,
metaKey: false
} as KeyboardEvent;

const result = modifierHandler(event);
expect(result).to.be.true;
expect(inputStub.called).to.be.false;
});

it('should intercept Shift+Enter and send CSI u sequence', () => {
const event = {
type: 'keydown',
key: 'Enter',
shiftKey: true,
altKey: false,
ctrlKey: false,
metaKey: false
} as KeyboardEvent;

const result = modifierHandler(event);
expect(result).to.be.false;
expect(inputStub.calledOnceWith('\x1b[13;2u', false)).to.be.true;
});

it('should intercept Ctrl+Tab and send CSI u sequence', () => {
const event = {
type: 'keydown',
key: 'Tab',
shiftKey: false,
altKey: false,
ctrlKey: true,
metaKey: false
} as KeyboardEvent;

const result = modifierHandler(event);
expect(result).to.be.false;
expect(inputStub.calledOnceWith('\x1b[9;5u', false)).to.be.true;
});

it('should intercept Ctrl+Shift+Backspace and send CSI u sequence', () => {
const event = {
type: 'keydown',
key: 'Backspace',
shiftKey: true,
altKey: false,
ctrlKey: true,
metaKey: false
} as KeyboardEvent;

const result = modifierHandler(event);
expect(result).to.be.false;
expect(inputStub.calledOnceWith('\x1b[127;6u', false)).to.be.true;
});

it('should ignore non-keydown events', () => {
const event = {
type: 'keyup',
key: 'Enter',
shiftKey: true
} as KeyboardEvent;

const result = modifierHandler(event);
expect(result).to.be.true;
expect(inputStub.called).to.be.false;
});

it('should ignore modified keys that are not in the special list', () => {
const event = {
type: 'keydown',
key: 'a',
shiftKey: true,
altKey: false,
ctrlKey: false,
metaKey: false
} as KeyboardEvent;

const result = modifierHandler(event);
expect(result).to.be.true;
expect(inputStub.called).to.be.false;
});
});
46 changes: 40 additions & 6 deletions src/client/wetty/term/confiruragtion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,38 @@
import type { Options } from './options';
import type { Term } from '../term';

export function modifierHandler(e: KeyboardEvent): boolean {
// We only care about keydown events with modifiers
if (e.type !== 'keydown') return true;

const modifiers =
(e.shiftKey ? 1 : 0) |

Check failure on line 13 in src/client/wetty/term/confiruragtion.ts

View workflow job for this annotation

GitHub Actions / Build & Test / Build & Test

Unexpected use of '|'

Check failure on line 13 in src/client/wetty/term/confiruragtion.ts

View workflow job for this annotation

GitHub Actions / Build & Test / Build & Test

Unexpected use of '|'

Check failure on line 13 in src/client/wetty/term/confiruragtion.ts

View workflow job for this annotation

GitHub Actions / Build & Test / Build & Test

Unexpected use of '|'
(e.altKey ? 2 : 0) |
(e.ctrlKey ? 4 : 0) |
(e.metaKey ? 8 : 0);

// If no modifiers, let xterm handle it normally
if (modifiers === 0) return true;

// Key codes for special keys we want to support generically
const specialKeys: Record<string, number> = {
Enter: 13,
Tab: 9,
Backspace: 127,
Escape: 27,
};

if (specialKeys[e.key]) {
const code = specialKeys[e.key];
const mod = modifiers + 1; // CSI u uses 1-based modifier mapping
// Send the CSI u sequence: ESC [ code ; mod u
window.wetty_term?.input(`\x1b[${code};${mod}u`, false);
return false; // Intercepted
}

return true;
}

export function configureTerm(term: Term): void {
const options = loadOptions();
try {
Expand Down Expand Up @@ -42,19 +74,21 @@
}
editor.addEventListener('load', editorOnLoad);

toggle.addEventListener('click', e => {
editor?.contentWindow?.loadOptions(loadOptions());
optionsElem.classList.toggle('opened');
e.preventDefault();
term.attachCustomKeyEventHandler(e => {

Check failure on line 77 in src/client/wetty/term/confiruragtion.ts

View workflow job for this annotation

GitHub Actions / Build & Test / Build & Test

Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`
return copyShortcut(e) && modifierHandler(e);
});

term.attachCustomKeyEventHandler(copyShortcut);

document.addEventListener(
'mouseup',
() => {
if (term.hasSelection()) copySelected(term.getSelection());
},
false,
);

toggle.addEventListener('click', e => {
editor?.contentWindow?.loadOptions(loadOptions());
optionsElem.classList.toggle('opened');
e.preventDefault();
});
}
Loading