From 1dbcf70cee9ae88c69cf9724745cbdb7e5364dcc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 29 Oct 2017 23:29:39 -0700 Subject: [PATCH] Support bracketed paste mode - As described in https://cirw.in/blog/bracketed-paste - Example support in vim: - https://github.com/ConradIrwin/vim-bracketed-paste/blob/master/plugin/bracketed-paste.vim - http://vimdoc.sourceforge.net/htmldoc/term.html#raw-terminal-mode - Required for e.g. ipython, via python-prompt-toolkit: - https://github.com/ipython/ipython - https://github.com/jonathanslenders/python-prompt-toolkit Repro: - Run `ipython` (version 6.2.1) - Copy multiple lines of text and paste into ipython prompt, e.g. ```py print(1) print(2) print(3) ``` - Before: first line would paste and run, and subsequent lines are lost - After: all lines paste into single prompt --- src/InputHandler.test.ts | 14 ++++++++++++++ src/InputHandler.ts | 6 ++++++ src/Interfaces.ts | 2 ++ src/Terminal.ts | 2 ++ src/handlers/Clipboard.test.ts | 8 ++++++++ src/handlers/Clipboard.ts | 12 ++++++++++++ src/utils/TestUtils.test.ts | 2 ++ 7 files changed, 46 insertions(+) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index caf74624..be3594cc 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -66,4 +66,18 @@ describe('InputHandler', () => { assert.equal(terminal.options['cursorBlink'], false); }); }); + describe('setMode', () => { + it('should toggle Terminal.bracketedPasteMode', () => { + let terminal = new MockInputHandlingTerminal(); + terminal.prefix = '?'; + terminal.bracketedPasteMode = false; + let inputHandler = new InputHandler(terminal); + // Set bracketed paste mode + inputHandler.setMode([2004]); + assert.equal(terminal.bracketedPasteMode, true); + // Reset bracketed paste mode + inputHandler.resetMode([2004]); + assert.equal(terminal.bracketedPasteMode, false); + }); + }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c953a250..76518e01 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -930,6 +930,9 @@ export class InputHandler implements IInputHandler { this._terminal.viewport.syncScrollArea(); this._terminal.showCursor(); break; + case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) + this._terminal.bracketedPasteMode = true; + break; } } } @@ -1100,6 +1103,9 @@ export class InputHandler implements IInputHandler { this._terminal.viewport.syncScrollArea(); this._terminal.showCursor(); break; + case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) + this._terminal.bracketedPasteMode = false; + break; } } } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 1b912cac..9606218e 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -48,6 +48,7 @@ export interface ITerminal extends ILinkifierAccessor, IBufferAccessor, IElement buffers: IBufferSet; isFocused: boolean; mouseHelper: IMouseHelper; + bracketedPasteMode: boolean; /** * Emit the 'data' event and populate the given data. @@ -82,6 +83,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { originMode: boolean; insertMode: boolean; wraparoundMode: boolean; + bracketedPasteMode: boolean; defAttr: number; curAttr: number; prefix: string; diff --git a/src/Terminal.ts b/src/Terminal.ts index 394fb4de..cbb90d36 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -132,6 +132,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public originMode: boolean; public insertMode: boolean; public wraparoundMode: boolean; // defaults: xterm - true, vt100 - false + public bracketedPasteMode: boolean; // charset // The current charset @@ -260,6 +261,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.originMode = false; this.insertMode = false; this.wraparoundMode = true; // defaults: xterm - true, vt100 - false + this.bracketedPasteMode = false; // charset this.charset = null; diff --git a/src/handlers/Clipboard.test.ts b/src/handlers/Clipboard.test.ts index 6901b730..d35e5fa4 100644 --- a/src/handlers/Clipboard.test.ts +++ b/src/handlers/Clipboard.test.ts @@ -16,4 +16,12 @@ describe('evaluatePastedTextProcessing', () => { assert.equal(processedText, 'foo\r\nbar\r\n'); assert.equal(windowsProcessedText, 'foo\rbar\r'); }); + it('should bracket pasted text in bracketedPasteMode', () => { + const pastedText = 'foo bar'; + const unbracketedText = Clipboard.bracketTextForPaste(pastedText, false); + const bracketedText = Clipboard.bracketTextForPaste(pastedText, true); + + assert.equal(unbracketedText, 'foo bar'); + assert.equal(bracketedText, '\x1b[200~foo bar\x1b[201~'); + }); }); diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts index a9735352..cf6661fe 100644 --- a/src/handlers/Clipboard.ts +++ b/src/handlers/Clipboard.ts @@ -25,6 +25,17 @@ export function prepareTextForTerminal(text: string, isMSWindows: boolean): stri return text; } +/** + * Bracket text for paste, if necessary, as per https://cirw.in/blog/bracketed-paste + * @param text The pasted text to bracket + */ +export function bracketTextForPaste(text: string, bracketedPasteMode: boolean): string { + if (bracketedPasteMode) { + return '\x1b[200~' + text + '\x1b[201~'; + } + return text; +} + /** * Binds copy functionality to the given terminal. * @param {ClipboardEvent} ev The original copy event to be handled @@ -52,6 +63,7 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void { let dispatchPaste = function(text: string): void { text = prepareTextForTerminal(text, term.browser.isMSWindows); + text = bracketTextForPaste(text, term.bracketedPasteMode); term.handler(text); term.textarea.value = ''; term.emit('paste', text); diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 26573319..606b8018 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -10,6 +10,7 @@ import * as Browser from './Browser'; import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Interfaces'; export class MockTerminal implements ITerminal { + bracketedPasteMode: boolean; mouseHelper: IMouseHelper; renderer: IRenderer; linkifier: ILinkifier; @@ -94,6 +95,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { originMode: boolean; insertMode: boolean; wraparoundMode: boolean; + bracketedPasteMode: boolean; defAttr: number; curAttr: number; prefix: string;