]>
Commit | Line | Data |
---|---|---|
42a1e4ef | 1 | /** |
1d300911 PK |
2 | * Clipboard handler module: exports methods for handling all clipboard-related events in the |
3 | * terminal. | |
a224acb5 | 4 | * @module xterm/handlers/Clipboard |
1d300911 | 5 | * @license MIT |
a224acb5 PK |
6 | */ |
7 | ||
824a9c6d PK |
8 | import { ITerminal } from '../Interfaces'; |
9 | ||
a34bb972 PK |
10 | interface IWindow extends Window { |
11 | clipboardData?: { | |
12 | getData(format: string): string; | |
13 | setData(format: string, data: string); | |
14 | }; | |
15 | } | |
16 | ||
17 | declare var window: IWindow; | |
18 | ||
42a1e4ef PK |
19 | /** |
20 | * Prepares text copied from terminal selection, to be saved in the clipboard by: | |
21 | * 1. stripping all trailing white spaces | |
22 | * 2. converting all non-breaking spaces to regular spaces | |
23 | * @param {string} text The copied text that needs processing for storing in clipboard | |
24 | * @returns {string} | |
42a1e4ef | 25 | */ |
a34bb972 | 26 | export function prepareTextForClipboard(text: string): string { |
824a9c6d | 27 | let space = String.fromCharCode(32), |
42a1e4ef PK |
28 | nonBreakingSpace = String.fromCharCode(160), |
29 | allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'), | |
30 | processedText = text.split('\n').map(function (line) { | |
31 | // Strip all trailing white spaces and convert all non-breaking spaces | |
32 | // to regular spaces. | |
824a9c6d | 33 | let processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space); |
42a1e4ef PK |
34 | |
35 | return processedLine; | |
36 | }).join('\n'); | |
37 | ||
38 | return processedText; | |
39 | } | |
40 | ||
0532e5cb JD |
41 | /** |
42 | * Prepares text to be pasted into the terminal by normalizing the line endings | |
43 | * @param text The pasted text that needs processing before inserting into the terminal | |
44 | */ | |
45 | export function prepareTextForTerminal(text: string, isMSWindows: boolean): string { | |
46 | if (isMSWindows) { | |
47 | return text.replace(/\r?\n/g, '\n'); | |
48 | } | |
49 | return text; | |
50 | } | |
51 | ||
42a1e4ef PK |
52 | /** |
53 | * Binds copy functionality to the given terminal. | |
a224acb5 | 54 | * @param {ClipboardEvent} ev The original copy event to be handled |
42a1e4ef | 55 | */ |
69e02739 | 56 | export function copyHandler(ev: ClipboardEvent, term: ITerminal) { |
824a9c6d PK |
57 | // We cast `window` to `any` type, because TypeScript has not declared the `clipboardData` |
58 | // property that we use below for Internet Explorer. | |
a34bb972 | 59 | let copiedText = window.getSelection().toString(), |
42a1e4ef PK |
60 | text = prepareTextForClipboard(copiedText); |
61 | ||
5808de64 | 62 | if (term.browser.isMSIE) { |
a34bb972 | 63 | window.clipboardData.setData('Text', text); |
5808de64 | 64 | } else { |
65 | ev.clipboardData.setData('text/plain', text); | |
66 | } | |
67 | ||
42a1e4ef PK |
68 | ev.preventDefault(); // Prevent or the original text will be copied. |
69 | } | |
70 | ||
71 | /** | |
a224acb5 PK |
72 | * Redirect the clipboard's data to the terminal's input handler. |
73 | * @param {ClipboardEvent} ev The original paste event to be handled | |
74 | * @param {Terminal} term The terminal on which to apply the handled paste event | |
42a1e4ef | 75 | */ |
69e02739 | 76 | export function pasteHandler(ev: ClipboardEvent, term: ITerminal) { |
42a1e4ef | 77 | ev.stopPropagation(); |
77ca1549 | 78 | |
a34bb972 | 79 | let text: string; |
824a9c6d PK |
80 | |
81 | let dispatchPaste = function(text) { | |
0532e5cb | 82 | text = prepareTextForTerminal(text, term.browser.isMSWindows); |
42a1e4ef PK |
83 | term.handler(text); |
84 | term.textarea.value = ''; | |
85 | return term.cancel(ev); | |
77ca1549 | 86 | }; |
87 | ||
5808de64 | 88 | if (term.browser.isMSIE) { |
a34bb972 PK |
89 | if (window.clipboardData) { |
90 | text = window.clipboardData.getData('Text'); | |
77ca1549 | 91 | dispatchPaste(text); |
92 | } | |
93 | } else { | |
94 | if (ev.clipboardData) { | |
824a9c6d | 95 | text = ev.clipboardData.getData('text/plain'); |
77ca1549 | 96 | dispatchPaste(text); |
97 | } | |
42a1e4ef PK |
98 | } |
99 | } | |
100 | ||
a224acb5 PK |
101 | /** |
102 | * Bind to right-click event and allow right-click copy and paste. | |
103 | * | |
104 | * **Logic** | |
105 | * If text is selected and right-click happens on selected text, then | |
106 | * do nothing to allow seamless copying. | |
107 | * If no text is selected or right-click is outside of the selection | |
108 | * area, then bring the terminal's input below the cursor, in order to | |
109 | * trigger the event on the textarea and allow-right click paste, without | |
110 | * caring about disappearing selection. | |
69e02739 | 111 | * @param {MouseEvent} ev The original right click event to be handled |
a224acb5 PK |
112 | * @param {Terminal} term The terminal on which to apply the handled paste event |
113 | */ | |
69e02739 | 114 | export function rightClickHandler(ev: MouseEvent, term: ITerminal) { |
824a9c6d | 115 | let s = document.getSelection(), |
35637797 | 116 | selectedText = prepareTextForClipboard(s.toString()), |
824a9c6d PK |
117 | clickIsOnSelection = false, |
118 | x = ev.clientX, | |
119 | y = ev.clientY; | |
42a1e4ef | 120 | |
00dcb4f6 | 121 | if (s.rangeCount) { |
824a9c6d | 122 | let r = s.getRangeAt(0), |
20b6f209 PK |
123 | cr = r.getClientRects(); |
124 | ||
125 | for (let i = 0; i < cr.length; i++) { | |
126 | let rect = cr[i]; | |
42a1e4ef | 127 | |
00dcb4f6 PK |
128 | clickIsOnSelection = ( |
129 | (x > rect.left) && (x < rect.right) && | |
130 | (y > rect.top) && (y < rect.bottom) | |
131 | ); | |
35637797 PK |
132 | |
133 | if (clickIsOnSelection) { | |
00dcb4f6 PK |
134 | break; |
135 | } | |
42a1e4ef | 136 | } |
35637797 PK |
137 | // If we clicked on selection and selection is not a single space, |
138 | // then mark the right click as copy-only. We check for the single | |
139 | // space selection, as this can happen when clicking on an | |
140 | // and there is not much pointing in copying a single space. | |
141 | if (selectedText.match(/^\s$/) || !selectedText.length) { | |
142 | clickIsOnSelection = false; | |
143 | } | |
42a1e4ef PK |
144 | } |
145 | ||
146 | // Bring textarea at the cursor position | |
147 | if (!clickIsOnSelection) { | |
148 | term.textarea.style.position = 'fixed'; | |
35637797 PK |
149 | term.textarea.style.width = '20px'; |
150 | term.textarea.style.height = '20px'; | |
151 | term.textarea.style.left = (x - 10) + 'px'; | |
152 | term.textarea.style.top = (y - 10) + 'px'; | |
824a9c6d | 153 | term.textarea.style.zIndex = '1000'; |
42a1e4ef PK |
154 | term.textarea.focus(); |
155 | ||
156 | // Reset the terminal textarea's styling | |
157 | setTimeout(function () { | |
158 | term.textarea.style.position = null; | |
159 | term.textarea.style.width = null; | |
160 | term.textarea.style.height = null; | |
161 | term.textarea.style.left = null; | |
162 | term.textarea.style.top = null; | |
163 | term.textarea.style.zIndex = null; | |
35637797 | 164 | }, 4); |
42a1e4ef PK |
165 | } |
166 | } |