feat: terminal copy and paste by keys off a Mac - Ctrl+C copies a selection (interrupts without one), Ctrl+Shift+C copies and stays from the browser, Ctrl+V and Ctrl+Shift+V paste
This commit is contained in:
1 parent
1297ab36d0
commit
36dc12b69d
1 file changed
+44
@@ -28,6 +28,49 @@
|
|||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- copy and paste by keys ----------
|
||||||
|
// Off a Mac, where Cmd does it, Ctrl+C copies when there is a selection and
|
||||||
|
// interrupts as ever when there is none; Ctrl+Shift+C always copies (and is
|
||||||
|
// kept from the browser, whose developer tools it would open); Ctrl+V and
|
||||||
|
// Ctrl+Shift+V paste. Keys go by position, so a Russian layout works too.
|
||||||
|
const mac = /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
|
||||||
|
function copyText(text) {
|
||||||
|
if (navigator.clipboard && window.isSecureContext) return navigator.clipboard.writeText(text);
|
||||||
|
// Plain http: the old way, through a field that is there for a moment.
|
||||||
|
const ta = document.createElement('textarea'), back = document.activeElement;
|
||||||
|
ta.value = text;
|
||||||
|
ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0';
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.select();
|
||||||
|
const ok = document.execCommand('copy');
|
||||||
|
ta.remove();
|
||||||
|
if (back && back.focus) back.focus();
|
||||||
|
return ok ? Promise.resolve() : Promise.reject(new Error('нет доступа к буферу'));
|
||||||
|
}
|
||||||
|
function clipKeys(s, ev) {
|
||||||
|
if (mac || !ev.ctrlKey || ev.altKey || ev.metaKey) return true;
|
||||||
|
const c = ev.code === 'KeyC', v = ev.code === 'KeyV';
|
||||||
|
if (!c && !v) return true;
|
||||||
|
if (c && !ev.shiftKey && !s.term.hasSelection()) return true; // ^C to the shell
|
||||||
|
// From here the key is ours: xterm does not turn it into a control code.
|
||||||
|
if (ev.type === 'keydown' && c) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (s.term.hasSelection()) {
|
||||||
|
const text = s.term.getSelection();
|
||||||
|
s.term.clearSelection();
|
||||||
|
copyText(text).then(() => flashStatus('скопировано', 'st-ok'), e => flashStatus('не скопировалось: ' + e.message, 'st-err'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ctrl+V: the browser's paste goes on into the terminal's text field.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function flashStatus(t, cls) {
|
||||||
|
statusEl.textContent = t;
|
||||||
|
statusEl.className = cls;
|
||||||
|
clearTimeout(flashStatus.t);
|
||||||
|
flashStatus.t = setTimeout(showStatus, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- one shell ----------
|
// ---------- one shell ----------
|
||||||
class Session {
|
class Session {
|
||||||
// ssh, when given, is where the agent connects by SSH for this shell:
|
// ssh, when given, is where the agent connects by SSH for this shell:
|
||||||
@@ -53,6 +96,7 @@
|
|||||||
this.fitter = new FitAddon.FitAddon();
|
this.fitter = new FitAddon.FitAddon();
|
||||||
this.term.loadAddon(this.fitter);
|
this.term.loadAddon(this.fitter);
|
||||||
this.term.open(this.el.querySelector('.pane-body'));
|
this.term.open(this.el.querySelector('.pane-body'));
|
||||||
|
this.term.attachCustomKeyEventHandler(ev => clipKeys(this, ev));
|
||||||
this.term.onData(d => this.send(TermKeys.withMods(d)));
|
this.term.onData(d => this.send(TermKeys.withMods(d)));
|
||||||
this.term.onResize(() => this.queueIn({ t: 'r', cols: this.term.cols, rows: this.term.rows }));
|
this.term.onResize(() => this.queueIn({ t: 'r', cols: this.term.cols, rows: this.term.rows }));
|
||||||
this.term.onSelectionChange(() => TermKeys.selectionChanged(this));
|
this.term.onSelectionChange(() => TermKeys.selectionChanged(this));
|
||||||
|
|||||||
Reference in new issue
Block a user