Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(247)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/terminal/xterm.js/src/CompositionHelper.js

Issue 2372303003: DevTools: introduce external service client (behind experiment). (Closed)
Patch Set: external linter Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /**
2 * xterm.js: xterm, in the browser
3 * Copyright (c) 2016, SourceLair Limited <www.sourcelair.com> (MIT License)
4 */
5
6 /**
7 * Encapsulates the logic for handling compositionstart, compositionupdate and c ompositionend
8 * events, displaying the in-progress composition to the UI and forwarding the f inal composition
9 * to the handler.
10 * @param {HTMLTextAreaElement} textarea The textarea that xterm uses for input.
11 * @param {HTMLElement} compositionView The element to display the in-progress c omposition in.
12 * @param {Terminal} terminal The Terminal to forward the finished composition t o.
13 */
14 function CompositionHelper(textarea, compositionView, terminal) {
15 this.textarea = textarea;
16 this.compositionView = compositionView;
17 this.terminal = terminal;
18
19 // Whether input composition is currently happening, eg. via a mobile keyboard , speech input
20 // or IME. This variable determines whether the compositionText should be disp layed on the UI.
21 this.isComposing = false;
22
23 // The input currently being composed, eg. via a mobile keyboard, speech input or IME.
24 this.compositionText = null;
25
26 // The position within the input textarea's value of the current composition.
27 this.compositionPosition = { start: null, end: null };
28
29 // Whether a composition is in the process of being sent, setting this to fals e will cancel
30 // any in-progress composition.
31 this.isSendingComposition = false;
32 }
33
34 /**
35 * Handles the compositionstart event, activating the composition view.
36 */
37 CompositionHelper.prototype.compositionstart = function() {
38 this.isComposing = true;
39 this.compositionPosition.start = this.textarea.value.length;
40 this.compositionView.textContent = '';
41 this.compositionView.classList.add('active');
42 };
43
44 /**
45 * Handles the compositionupdate event, updating the composition view.
46 * @param {CompositionEvent} ev The event.
47 */
48 CompositionHelper.prototype.compositionupdate = function(ev) {
49 this.compositionView.textContent = ev.data;
50 this.updateCompositionElements();
51 var self = this;
52 setTimeout(function() {
53 self.compositionPosition.end = self.textarea.value.length;
54 }, 0);
55 };
56
57 /**
58 * Handles the compositionend event, hiding the composition view and sending the composition to
59 * the handler.
60 */
61 CompositionHelper.prototype.compositionend = function() {
62 this.finalizeComposition(true);
63 };
64
65 /**
66 * Handles the keydown event, routing any necessary events to the CompositionHel per functions.
67 * @return Whether the Terminal should continue processing the keydown event.
68 */
69 CompositionHelper.prototype.keydown = function(ev) {
70 if (this.isComposing || this.isSendingComposition) {
71 if (ev.keyCode === 229) {
72 // Continue composing if the keyCode is the "composition character"
73 return false;
74 } else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
75 // Continue composing if the keyCode is a modifier key
76 return false;
77 } else {
78 // Finish composition immediately. This is mainly here for the case where enter is
79 // pressed and the handler needs to be triggered before the command is exe cuted.
80 this.finalizeComposition(false);
81 }
82 }
83
84 if (ev.keyCode === 229) {
85 // If the "composition character" is used but gets to this point it means a non-composition
86 // character (eg. numbers and punctuation) was pressed when the IME was acti ve.
87 this.handleAnyTextareaChanges();
88 return false;
89 }
90
91 return true;
92 };
93
94 /**
95 * Finalizes the composition, resuming regular input actions. This is called whe n a composition
96 * is ending.
97 * @param {boolean} waitForPropogation Whether to wait for events to propogate b efore sending
98 * the input. This should be false if a non-composition keystroke is entered b efore the
99 * compositionend event is triggered, such as enter, so that the composition i s send before
100 * the command is executed.
101 */
102 CompositionHelper.prototype.finalizeComposition = function(waitForPropogation) {
103 this.compositionView.classList.remove('active');
104 this.isComposing = false;
105 this.clearTextareaPosition();
106
107 if (!waitForPropogation) {
108 // Cancel any delayed composition send requests and send the input immediate ly.
109 this.isSendingComposition = false;
110 var input = this.textarea.value.substring(this.compositionPosition.start, th is.compositionPosition.end);
111 this.terminal.handler(input);
112 } else {
113 // Make a deep copy of the composition position here as a new compositionsta rt event may
114 // fire before the setTimeout executes.
115 var currentCompositionPosition = {
116 start: this.compositionPosition.start,
117 end: this.compositionPosition.end,
118 }
119
120 // Since composition* events happen before the changes take place in the tex tarea on most
121 // browsers, use a setTimeout with 0ms time to allow the native compositione nd event to
122 // complete. This ensures the correct character is retrieved, this solution was used
123 // because:
124 // - The compositionend event's data property is unreliable, at least on Chr omium
125 // - The last compositionupdate event's data property does not always accura tely describe
126 // the character, a counter example being Korean where an ending consonsan t can move to
127 // the following character if the following input is a vowel.
128 var self = this;
129 this.isSendingComposition = true;
130 setTimeout(function () {
131 // Ensure that the input has not already been sent
132 if (self.isSendingComposition) {
133 self.isSendingComposition = false;
134 var input;
135 if (self.isComposing) {
136 // Use the end position to get the string if a new composition has sta rted.
137 input = self.textarea.value.substring(currentCompositionPosition.start , currentCompositionPosition.end);
138 } else {
139 // Don't use the end position here in order to pick up any characters after the
140 // composition has finished, for example when typing a non-composition character
141 // (eg. 2) after a composition character.
142 input = self.textarea.value.substring(currentCompositionPosition.start );
143 }
144 self.terminal.handler(input);
145 }
146 }, 0);
147 }
148 };
149
150 /**
151 * Apply any changes made to the textarea after the current event chain is allow ed to complete.
152 * This should be called when not currently composing but a keydown event with t he "composition
153 * character" (229) is triggered, in order to allow non-composition text to be e ntered when an
154 * IME is active.
155 */
156 CompositionHelper.prototype.handleAnyTextareaChanges = function() {
157 var oldValue = this.textarea.value;
158 var self = this;
159 setTimeout(function() {
160 // Ignore if a composition has started since the timeout
161 if (!self.isComposing) {
162 var newValue = self.textarea.value;
163 var diff = newValue.replace(oldValue, '');
164 if (diff.length > 0) {
165 self.terminal.handler(diff);
166 }
167 }
168 }, 0);
169 };
170
171 /**
172 * Positions the composition view on top of the cursor and the textarea just bel ow it (so the
173 * IME helper dialog is positioned correctly).
174 */
175 CompositionHelper.prototype.updateCompositionElements = function(dontRecurse) {
176 if (!this.isComposing) {
177 return;
178 }
179 var cursor = this.terminal.element.querySelector('.terminal-cursor');
180 if (cursor) {
181 this.compositionView.style.left = cursor.offsetLeft + 'px';
182 this.compositionView.style.top = cursor.offsetTop + 'px';
183 var compositionViewBounds = this.compositionView.getBoundingClientRect();
184 this.textarea.style.left = cursor.offsetLeft + compositionViewBounds.width + 'px';
185 this.textarea.style.top = (cursor.offsetTop + cursor.offsetHeight) + 'px';
186 }
187 if (!dontRecurse) {
188 setTimeout(this.updateCompositionElements.bind(this, true), 0);
189 }
190 };
191
192 /**
193 * Clears the textarea's position so that the cursor does not blink on IE.
194 * @private
195 */
196 CompositionHelper.prototype.clearTextareaPosition = function() {
197 this.textarea.style.left = '';
198 this.textarea.style.top = '';
199 };
200
201 export { CompositionHelper };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698