-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow right-click paste (again) #293
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/** | ||
* xterm.js: xterm, in the browser | ||
* Copyright (c) 2016, SourceLair Private Company <www.sourcelair.com> (MIT License) | ||
*/ | ||
|
||
/** | ||
* Clipboard handler module. This module contains methods for handling all | ||
* clipboard-related events appropriately in the terminal. | ||
* @module xterm/handlers/Clipboard | ||
*/ | ||
|
||
/** | ||
* Prepares text copied from terminal selection, to be saved in the clipboard by: | ||
* 1. stripping all trailing white spaces | ||
* 2. converting all non-breaking spaces to regular spaces | ||
* @param {string} text The copied text that needs processing for storing in clipboard | ||
* @returns {string} | ||
*/ | ||
function prepareTextForClipboard(text) { | ||
var space = String.fromCharCode(32), | ||
nonBreakingSpace = String.fromCharCode(160), | ||
allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'), | ||
processedText = text.split('\n').map(function (line) { | ||
// Strip all trailing white spaces and convert all non-breaking spaces | ||
// to regular spaces. | ||
var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space); | ||
|
||
return processedLine; | ||
}).join('\n'); | ||
|
||
return processedText; | ||
} | ||
|
||
/** | ||
* Binds copy functionality to the given terminal. | ||
* @param {ClipboardEvent} ev The original copy event to be handled | ||
*/ | ||
function copyHandler (ev) { | ||
var copiedText = window.getSelection().toString(), | ||
text = prepareTextForClipboard(copiedText); | ||
|
||
ev.clipboardData.setData('text/plain', text); | ||
ev.preventDefault(); // Prevent or the original text will be copied. | ||
} | ||
|
||
/** | ||
* Redirect the clipboard's data to the terminal's input handler. | ||
* @param {ClipboardEvent} ev The original paste event to be handled | ||
* @param {Terminal} term The terminal on which to apply the handled paste event | ||
*/ | ||
function pasteHandler(ev, term) { | ||
ev.stopPropagation(); | ||
if (ev.clipboardData) { | ||
var text = ev.clipboardData.getData('text/plain'); | ||
term.handler(text); | ||
term.textarea.value = ''; | ||
return term.cancel(ev); | ||
} | ||
} | ||
|
||
/** | ||
* Bind to right-click event and allow right-click copy and paste. | ||
* | ||
* **Logic** | ||
* If text is selected and right-click happens on selected text, then | ||
* do nothing to allow seamless copying. | ||
* If no text is selected or right-click is outside of the selection | ||
* area, then bring the terminal's input below the cursor, in order to | ||
* trigger the event on the textarea and allow-right click paste, without | ||
* caring about disappearing selection. | ||
* @param {ClipboardEvent} ev The original paste event to be handled | ||
* @param {Terminal} term The terminal on which to apply the handled paste event | ||
*/ | ||
function rightClickHandler(ev, term) { | ||
var s = document.getSelection(), | ||
sText = prepareTextForClipboard(s.toString()), | ||
r = s.getRangeAt(0); | ||
|
||
var x = ev.clientX, | ||
y = ev.clientY; | ||
|
||
var cr = r.getClientRects(), | ||
clickIsOnSelection = false, | ||
i, rect; | ||
|
||
for (i=0; i<cr.length; i++) { | ||
rect = cr[i]; | ||
clickIsOnSelection = ( | ||
(x > rect.left) && (x < rect.right) && | ||
(y > rect.top) && (y < rect.bottom) | ||
); | ||
// If we clicked on selection and selection is not a single space, | ||
// then mark the right click as copy-only. We check for the single | ||
// space selection, as this can happen when clicking on an | ||
// and there is not much pointing in copying a single space. | ||
// Single space is char | ||
if (clickIsOnSelection && (sText !== ' ')) { | ||
break; | ||
} | ||
} | ||
|
||
// Bring textarea at the cursor position | ||
if (!clickIsOnSelection) { | ||
term.textarea.style.position = 'fixed'; | ||
term.textarea.style.width = '10px'; | ||
term.textarea.style.height = '10px'; | ||
term.textarea.style.left = x + 'px'; | ||
term.textarea.style.top = y + 'px'; | ||
term.textarea.style.zIndex = 1000; | ||
term.textarea.focus(); | ||
|
||
// Reset the terminal textarea's styling | ||
setTimeout(function () { | ||
term.textarea.style.position = null; | ||
term.textarea.style.width = null; | ||
term.textarea.style.height = null; | ||
term.textarea.style.left = null; | ||
term.textarea.style.top = null; | ||
term.textarea.style.zIndex = null; | ||
}, 1); | ||
} | ||
} | ||
|
||
export { | ||
prepareTextForClipboard, copyHandler, pasteHandler, rightClickHandler | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ | |
import { CompositionHelper } from './CompositionHelper.js'; | ||
import { EventEmitter } from './EventEmitter.js'; | ||
import { Viewport } from './Viewport.js'; | ||
import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js'; | ||
|
||
/** | ||
* Terminal Emulation References: | ||
|
@@ -431,52 +432,20 @@ Terminal.bindBlur = function (term) { | |
* Initialize default behavior | ||
*/ | ||
Terminal.prototype.initGlobal = function() { | ||
Terminal.bindPaste(this); | ||
var term = this; | ||
|
||
Terminal.bindKeys(this); | ||
Terminal.bindCopy(this); | ||
Terminal.bindFocus(this); | ||
Terminal.bindBlur(this); | ||
}; | ||
|
||
/** | ||
* Bind to paste event and allow both keyboard and right-click pasting, without having the | ||
* contentEditable value set to true. | ||
*/ | ||
Terminal.bindPaste = function(term) { | ||
on([term.textarea, term.element], 'paste', function(ev) { | ||
ev.stopPropagation(); | ||
if (ev.clipboardData) { | ||
var text = ev.clipboardData.getData('text/plain'); | ||
term.handler(text); | ||
term.textarea.value = ''; | ||
return term.cancel(ev); | ||
} | ||
// Bind clipboard functionality | ||
on(this.element, 'copy', copyHandler); | ||
on(this.textarea, 'paste', function (ev) { | ||
pasteHandler.call(this, ev, term); | ||
}); | ||
on(this.element, 'contextmenu', function (ev) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this might conflict with vscode which overrides the context menu to show options to create new terminal, paste and copy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure? I am not cancelling the event. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @parisk wouldn't it cause 2 context menus to appear or something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. All I am doing here is move the textarea when the context menu appears. Is there any easy way to try this out? |
||
rightClickHandler.call(this, ev, term); | ||
}); | ||
}; | ||
|
||
/** | ||
* Prepares text copied from terminal selection, to be saved in the clipboard by: | ||
* 1. stripping all trailing white spaces | ||
* 2. converting all non-breaking spaces to regular spaces | ||
* @param {string} text The copied text that needs processing for storing in clipboard | ||
* @returns {string} | ||
* @static | ||
*/ | ||
Terminal.prepareCopiedTextForClipboard = function (text) { | ||
var space = String.fromCharCode(32), | ||
nonBreakingSpace = String.fromCharCode(160), | ||
allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'), | ||
processedText = text.split('\n').map(function (line) { | ||
/** | ||
* Strip all trailing white spaces and convert all non-breaking spaces to regular | ||
* spaces. | ||
*/ | ||
var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space); | ||
|
||
return processedLine; | ||
}).join('\n'); | ||
|
||
return processedText; | ||
}; | ||
|
||
/** | ||
|
@@ -515,16 +484,6 @@ Terminal.bindKeys = function(term) { | |
term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper)); | ||
}; | ||
|
||
/** | ||
* Binds copy functionality to the given terminal. | ||
* @static | ||
*/ | ||
Terminal.bindCopy = function(term) { | ||
on(term.element, 'copy', function(ev) { | ||
return; // temporary | ||
}); | ||
}; | ||
|
||
|
||
/** | ||
* Insert the given row to the terminal or produce a new one | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
var assert = require('chai').assert; | ||
var Terminal = require('../src/xterm'); | ||
var Clipboard = require('../src/handlers/Clipboard'); | ||
|
||
|
||
describe('evaluateCopiedTextProcessing', function () { | ||
it('should strip trailing whitespaces and replace nbsps with spaces', function () { | ||
var nonBreakingSpace = String.fromCharCode(160), | ||
copiedText = 'echo' + nonBreakingSpace + 'hello' + nonBreakingSpace, | ||
processedText = Clipboard.prepareTextForClipboard(copiedText); | ||
|
||
// No trailing spaces | ||
assert.equal(processedText.match(/\s+$/), null); | ||
|
||
// No non-breaking space | ||
assert.equal(processedText.indexOf(nonBreakingSpace), -1); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should copy be exposed via the API so it can be done programmatically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All methods are already exposed in Clipboard.js. This also would not work in browsers, so I am skeptical about exposing such a public API.