1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 // Session class of Mozc server.
31
32 #include "session/session.h"
33
34 #include <memory>
35 #include <string>
36 #include <vector>
37
38 #include "base/clock.h"
39 #include "base/logging.h"
40 #include "base/port.h"
41 #include "base/singleton.h"
42 #include "base/url.h"
43 #include "base/util.h"
44 #include "base/version.h"
45 #include "composer/composer.h"
46 #include "composer/key_event_util.h"
47 #include "composer/table.h"
48 #include "config/config_handler.h"
49 #include "engine/engine_interface.h"
50 #include "engine/user_data_manager_interface.h"
51 #include "protocol/commands.pb.h"
52 #include "protocol/config.pb.h"
53 #include "session/internal/ime_context.h"
54 #include "session/internal/key_event_transformer.h"
55 #include "session/internal/keymap.h"
56 #include "session/internal/keymap_factory.h"
57 #include "session/internal/keymap-inl.h"
58 #include "session/internal/session_output.h"
59 #include "session/session_converter.h"
60 #include "session/session_usage_stats_util.h"
61 #include "usage_stats/usage_stats.h"
62
63 namespace mozc {
64 namespace session {
65 namespace {
66
67 using ::mozc::usage_stats::UsageStats;
68
69 // Set input mode if the current input mode is not the given mode.
SwitchInputMode(const transliteration::TransliterationType mode,composer::Composer * composer)70 void SwitchInputMode(const transliteration::TransliterationType mode,
71 composer::Composer *composer) {
72 if (composer->GetInputMode() != mode) {
73 composer->SetInputMode(mode);
74 }
75 composer->SetNewInput();
76 }
77
78 // Set input mode to the |composer| if the the input mode of |composer| is not
79 // the given |mode|.
ApplyInputMode(const commands::CompositionMode mode,composer::Composer * composer)80 void ApplyInputMode(const commands::CompositionMode mode,
81 composer::Composer *composer) {
82 switch (mode) {
83 case commands::HIRAGANA:
84 SwitchInputMode(transliteration::HIRAGANA, composer);
85 break;
86 case commands::FULL_KATAKANA:
87 SwitchInputMode(transliteration::FULL_KATAKANA, composer);
88 break;
89 case commands::HALF_KATAKANA:
90 SwitchInputMode(transliteration::HALF_KATAKANA, composer);
91 break;
92 case commands::FULL_ASCII:
93 SwitchInputMode(transliteration::FULL_ASCII, composer);
94 break;
95 case commands::HALF_ASCII:
96 SwitchInputMode(transliteration::HALF_ASCII, composer);
97 break;
98 default:
99 LOG(DFATAL) << "ime on with invalid mode";
100 }
101 }
102
103 // Return true if the specified key event consists of any modifier key only.
IsPureModifierKeyEvent(const commands::KeyEvent & key)104 bool IsPureModifierKeyEvent(const commands::KeyEvent &key) {
105 if (key.has_key_code()) {
106 return false;
107 }
108 if (key.has_special_key()) {
109 return false;
110 }
111 if (key.modifier_keys_size() == 0) {
112 return false;
113 }
114 return true;
115 }
116
IsPureSpaceKey(const commands::KeyEvent & key)117 bool IsPureSpaceKey(const commands::KeyEvent &key) {
118 if (key.has_key_code()) {
119 return false;
120 }
121 if (key.modifier_keys_size() > 0) {
122 return false;
123 }
124 if (!key.has_special_key()) {
125 return false;
126 }
127 if (key.special_key() != commands::KeyEvent::SPACE) {
128 return false;
129 }
130 return true;
131 }
132
133 // Set session state to the given state and also update related status.
SetSessionState(const ImeContext::State state,ImeContext * context)134 void SetSessionState(const ImeContext::State state, ImeContext *context) {
135 const ImeContext::State prev_state = context->state();
136 context->set_state(state);
137 switch (state) {
138 case ImeContext::DIRECT:
139 case ImeContext::PRECOMPOSITION:
140 context->mutable_composer()->Reset();
141 break;
142 case ImeContext::CONVERSION:
143 context->mutable_composer()->ResetInputMode();
144 break;
145 case ImeContext::COMPOSITION:
146 if (prev_state == ImeContext::PRECOMPOSITION) {
147 // Notify the start of composition to the converter so that internal
148 // state can be refreshed by the client context (especially by
149 // preceding text).
150 context->mutable_converter()->OnStartComposition(
151 context->client_context());
152 }
153 break;
154 default:
155 // Do nothing.
156 break;
157 }
158 }
159
ToCompositionMode(mozc::transliteration::TransliterationType type)160 commands::CompositionMode ToCompositionMode(
161 mozc::transliteration::TransliterationType type) {
162 commands::CompositionMode mode = commands::HIRAGANA;
163 switch (type) {
164 case transliteration::HIRAGANA:
165 mode = commands::HIRAGANA;
166 break;
167 case transliteration::FULL_KATAKANA:
168 mode = commands::FULL_KATAKANA;
169 break;
170 case transliteration::HALF_KATAKANA:
171 mode = commands::HALF_KATAKANA;
172 break;
173 case transliteration::FULL_ASCII:
174 mode = commands::FULL_ASCII;
175 break;
176 case transliteration::HALF_ASCII:
177 mode = commands::HALF_ASCII;
178 break;
179 default:
180 LOG(ERROR) << "Unknown input mode: " << type;
181 // use HIRAGANA as a default.
182 }
183 return mode;
184 }
185
GetEffectiveStateForTestSendKey(const commands::KeyEvent & key,ImeContext::State state)186 ImeContext::State GetEffectiveStateForTestSendKey(
187 const commands::KeyEvent &key,
188 ImeContext::State state) {
189 if (!key.has_activated()) {
190 return state;
191 }
192 if (state == ImeContext::DIRECT && key.activated()) {
193 // Indirect IME On found.
194 return ImeContext::PRECOMPOSITION;
195 }
196 if (state != ImeContext::DIRECT && !key.activated()) {
197 // Indirect IME Off found.
198 return ImeContext::DIRECT;
199 }
200 return state;
201 }
202
203 } // namespace
204
205 // TODO(komatsu): Remove these argument by using/making singletons.
Session(EngineInterface * engine)206 Session::Session(EngineInterface *engine)
207 : engine_(engine), context_(new ImeContext) {
208 InitContext(context_.get());
209 }
210
~Session()211 Session::~Session() {}
212
InitContext(ImeContext * context) const213 void Session::InitContext(ImeContext *context) const {
214 context->set_create_time(Clock::GetTime());
215 context->set_last_command_time(0);
216 context->set_composer(new composer::Composer(NULL,
217 &context->GetRequest(),
218 &context->GetConfig()));
219 context->set_converter(
220 new SessionConverter(engine_->GetConverter(),
221 &context->GetRequest(),
222 &context->GetConfig()));
223 #ifdef OS_WIN
224 // On Windows session is started with direct mode.
225 // FIXME(toshiyuki): Ditto for Mac after verifying on Mac.
226 context->set_state(ImeContext::DIRECT);
227 #else
228 context->set_state(ImeContext::PRECOMPOSITION);
229 #endif
230 context->mutable_client_context()->Clear();
231
232 context->SetConfig(&context->GetConfig());
233
234 #if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_NACL)
235 context->mutable_converter()->set_use_cascading_window(false);
236 #endif // OS_LINUX || OS_ANDROID || OS_NACL
237 }
238
239
PushUndoContext()240 void Session::PushUndoContext() {
241 // TODO(komatsu): Support multiple undo.
242 prev_context_.reset(new ImeContext);
243 InitContext(prev_context_.get());
244 ImeContext::CopyContext(*context_, prev_context_.get());
245 }
246
PopUndoContext()247 void Session::PopUndoContext() {
248 // TODO(komatsu): Support multiple undo.
249 if (!prev_context_.get()) {
250 return;
251 }
252 context_.swap(prev_context_);
253 prev_context_.reset();
254 }
255
ClearUndoContext()256 void Session::ClearUndoContext() {
257 prev_context_.reset();
258 }
259
EnsureIMEIsOn()260 void Session::EnsureIMEIsOn() {
261 if (context_->state() == ImeContext::DIRECT) {
262 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
263 }
264 }
265
SendCommand(commands::Command * command)266 bool Session::SendCommand(commands::Command *command) {
267 UpdateTime();
268 UpdatePreferences(command);
269 if (!command->input().has_command()) {
270 return false;
271 }
272 TransformInput(command->mutable_input());
273
274 SessionUsageStatsUtil::AddSendCommandInputStats(command->input());
275
276 const commands::SessionCommand &session_command = command->input().command();
277 bool result = false;
278 if (session_command.type() == commands::SessionCommand::SWITCH_INPUT_MODE) {
279 if (!session_command.has_composition_mode()) {
280 return false;
281 }
282 switch (session_command.composition_mode()) {
283 case commands::DIRECT:
284 // TODO(komatsu): Implement here.
285 break;
286 case commands::HIRAGANA:
287 result = InputModeHiragana(command);
288 break;
289 case commands::FULL_KATAKANA:
290 result = InputModeFullKatakana(command);
291 break;
292 case commands::HALF_ASCII:
293 result = InputModeHalfASCII(command);
294 break;
295 case commands::FULL_ASCII:
296 result = InputModeFullASCII(command);
297 break;
298 case commands::HALF_KATAKANA:
299 result = InputModeHalfKatakana(command);
300 break;
301 default:
302 LOG(ERROR) << "Unknown mode: " << session_command.composition_mode();
303 break;
304 }
305 return result;
306 }
307
308 DCHECK_EQ(false, result);
309 switch (command->input().command().type()) {
310 case commands::SessionCommand::REVERT:
311 result = Revert(command);
312 break;
313 case commands::SessionCommand::SUBMIT:
314 result = Commit(command);
315 break;
316 case commands::SessionCommand::SELECT_CANDIDATE:
317 result = SelectCandidate(command);
318 break;
319 case commands::SessionCommand::SUBMIT_CANDIDATE:
320 result = CommitCandidate(command);
321 break;
322 case commands::SessionCommand::HIGHLIGHT_CANDIDATE:
323 result = HighlightCandidate(command);
324 break;
325 case commands::SessionCommand::GET_STATUS:
326 result = GetStatus(command);
327 break;
328 case commands::SessionCommand::CONVERT_REVERSE:
329 result = ConvertReverse(command);
330 break;
331 case commands::SessionCommand::UNDO:
332 result = Undo(command);
333 break;
334 case commands::SessionCommand::RESET_CONTEXT:
335 result = ResetContext(command);
336 break;
337 case commands::SessionCommand::MOVE_CURSOR:
338 result = MoveCursorTo(command);
339 break;
340 case commands::SessionCommand::EXPAND_SUGGESTION:
341 result = ExpandSuggestion(command);
342 break;
343 case commands::SessionCommand::SWITCH_INPUT_FIELD_TYPE:
344 result = SwitchInputFieldType(command);
345 break;
346 case commands::SessionCommand::USAGE_STATS_EVENT:
347 // Set consumed to false, because the client don't need to do anything
348 // when it receive the output from the server.
349 command->mutable_output()->set_consumed(false);
350 result = true;
351 break;
352 case commands::SessionCommand::UNDO_OR_REWIND:
353 result = UndoOrRewind(command);
354 break;
355 case commands::SessionCommand::COMMIT_RAW_TEXT:
356 result = CommitRawText(command);
357 break;
358 case commands::SessionCommand::CONVERT_PREV_PAGE:
359 result = ConvertPrevPage(command);
360 break;
361 case commands::SessionCommand::CONVERT_NEXT_PAGE:
362 result = ConvertNextPage(command);
363 break;
364 case commands::SessionCommand::TURN_ON_IME:
365 result = MakeSureIMEOn(command);
366 break;
367 case commands::SessionCommand::TURN_OFF_IME:
368 result = MakeSureIMEOff(command);
369 break;
370 default:
371 LOG(WARNING) << "Unknown command" << command->DebugString();
372 result = DoNothing(command);
373 break;
374 }
375
376 return result;
377 }
378
TestSendKey(commands::Command * command)379 bool Session::TestSendKey(commands::Command *command) {
380 UpdateTime();
381 UpdatePreferences(command);
382 TransformInput(command->mutable_input());
383
384 if (context_->state() == ImeContext::NONE) {
385 // This must be an error.
386 LOG(ERROR) << "Invalid state: NONE";
387 return false;
388 }
389
390 const commands::KeyEvent &key = command->input().key();
391
392 // To support indirect IME on/off by using KeyEvent::activated, use effective
393 // state instead of directly using context_->state().
394 const ImeContext::State state = GetEffectiveStateForTestSendKey(
395 key, context_->state());
396
397 const keymap::KeyMapManager *keymap =
398 keymap::KeyMapFactory::GetKeyMapManager(context_->keymap());
399
400 // Direct input
401 if (state == ImeContext::DIRECT) {
402 keymap::DirectInputState::Commands key_command;
403 if (!keymap->GetCommandDirect(key, &key_command) ||
404 key_command == keymap::DirectInputState::NONE) {
405 return EchoBack(command);
406 }
407 return DoNothing(command);
408 }
409
410 // Precomposition
411 if (state == ImeContext::PRECOMPOSITION) {
412 keymap::PrecompositionState::Commands key_command;
413 const bool is_suggestion =
414 context_->converter().CheckState(SessionConverterInterface::SUGGESTION);
415 const bool result = is_suggestion
416 ? keymap->GetCommandZeroQuerySuggestion(key, &key_command)
417 : keymap->GetCommandPrecomposition(key, &key_command);
418 if (!result || key_command == keymap::PrecompositionState::NONE) {
419 // Clear undo context just in case. b/5529702.
420 // Note that the undo context will not be cleared in
421 // EchoBackAndClearUndoContext if the key event consists of modifier keys
422 // only.
423 return EchoBackAndClearUndoContext(command);
424 }
425 // If the input_style is DIRECT_INPUT, KeyEvent is not consumed
426 // and done echo back. It works only when key_string is equal to
427 // key_code. We should fix this limitation when the as_is flag is
428 // used for rather than numpad characters.
429 if (key_command == keymap::PrecompositionState::INSERT_CHARACTER &&
430 key.input_style() == commands::KeyEvent::DIRECT_INPUT) {
431 return EchoBack(command);
432 }
433
434 // TODO(komatsu): This is a hack to work around the problem with
435 // the inconsistency between TestSendKey and SendKey.
436 switch (key_command) {
437 case keymap::PrecompositionState::INSERT_SPACE:
438 if (!IsFullWidthInsertSpace(command->input()) && IsPureSpaceKey(key)) {
439 return EchoBackAndClearUndoContext(command);
440 }
441 return DoNothing(command);
442 case keymap::PrecompositionState::INSERT_ALTERNATE_SPACE:
443 if (IsFullWidthInsertSpace(command->input()) && IsPureSpaceKey(key)) {
444 return EchoBackAndClearUndoContext(command);
445 }
446 return DoNothing(command);
447 case keymap::PrecompositionState::INSERT_HALF_SPACE:
448 if (IsPureSpaceKey(key)) {
449 return EchoBackAndClearUndoContext(command);
450 }
451 return DoNothing(command);
452 case keymap::PrecompositionState::INSERT_FULL_SPACE:
453 return DoNothing(command);
454 default:
455 // Do nothing.
456 break;
457 }
458
459 if (key_command == keymap::PrecompositionState::REVERT) {
460 return Revert(command);
461 }
462
463 // If undo context is empty, echoes back the key event so that it can be
464 // handled by the application. b/5553298
465 if (key_command == keymap::PrecompositionState::UNDO &&
466 !prev_context_.get()) {
467 return EchoBack(command);
468 }
469
470 return DoNothing(command);
471 }
472
473 // Do nothing.
474 return DoNothing(command);
475 }
476
SendKey(commands::Command * command)477 bool Session::SendKey(commands::Command *command) {
478 UpdateTime();
479 UpdatePreferences(command);
480 TransformInput(command->mutable_input());
481 // To support indirect IME on/off by using KeyEvent::activated, use effective
482 // state instead of directly using context_->state().
483 HandleIndirectImeOnOff(command);
484
485 SessionUsageStatsUtil::AddSendKeyInputStats(command->input());
486
487 bool result = false;
488 switch (context_->state()) {
489 case ImeContext::DIRECT:
490 result = SendKeyDirectInputState(command);
491 break;
492
493 case ImeContext::PRECOMPOSITION:
494 result = SendKeyPrecompositionState(command);
495 break;
496
497 case ImeContext::COMPOSITION:
498 result = SendKeyCompositionState(command);
499 break;
500
501 case ImeContext::CONVERSION:
502 result = SendKeyConversionState(command);
503 break;
504
505 case ImeContext::NONE:
506 result = false;
507 break;
508 }
509
510 SessionUsageStatsUtil::AddSendKeyOutputStats(command->output());
511
512 return result;
513 }
514
SendKeyDirectInputState(commands::Command * command)515 bool Session::SendKeyDirectInputState(commands::Command *command) {
516 keymap::DirectInputState::Commands key_command;
517 const keymap::KeyMapManager *keymap =
518 keymap::KeyMapFactory::GetKeyMapManager(context_->keymap());
519 if (!keymap->GetCommandDirect(command->input().key(), &key_command)) {
520 return EchoBackAndClearUndoContext(command);
521 }
522 string command_name;
523 if (keymap->GetNameFromCommandDirect(key_command, &command_name)) {
524 UsageStats::IncrementCount("Performed_Direct_" + command_name);
525 }
526 switch (key_command) {
527 case keymap::DirectInputState::IME_ON:
528 return IMEOn(command);
529 case keymap::DirectInputState::INPUT_MODE_HIRAGANA:
530 return InputModeHiragana(command);
531 case keymap::DirectInputState::INPUT_MODE_FULL_KATAKANA:
532 return InputModeFullKatakana(command);
533 case keymap::DirectInputState::INPUT_MODE_HALF_KATAKANA:
534 return InputModeHalfKatakana(command);
535 case keymap::DirectInputState::INPUT_MODE_FULL_ALPHANUMERIC:
536 return InputModeFullASCII(command);
537 case keymap::DirectInputState::INPUT_MODE_HALF_ALPHANUMERIC:
538 return InputModeHalfASCII(command);
539 case keymap::DirectInputState::NONE:
540 return EchoBackAndClearUndoContext(command);
541 case keymap::DirectInputState::RECONVERT:
542 return RequestConvertReverse(command);
543 }
544 return false;
545 }
546
SendKeyPrecompositionState(commands::Command * command)547 bool Session::SendKeyPrecompositionState(commands::Command *command) {
548 keymap::PrecompositionState::Commands key_command;
549 const keymap::KeyMapManager *keymap =
550 keymap::KeyMapFactory::GetKeyMapManager(context_->keymap());
551 const bool result =
552 context_->converter().CheckState(SessionConverterInterface::SUGGESTION) ?
553 keymap->GetCommandZeroQuerySuggestion(command->input().key(),
554 &key_command) :
555 keymap->GetCommandPrecomposition(command->input().key(), &key_command);
556
557 if (!result) {
558 return EchoBackAndClearUndoContext(command);
559 }
560 string command_name;
561 if (keymap->GetNameFromCommandPrecomposition(key_command, &command_name)) {
562 UsageStats::IncrementCount("Performed_Precomposition_" + command_name);
563 }
564
565 // Update the client context (if any) for later use. Note that the client
566 // context is updated only here. In other words, we will stop updating the
567 // client context once a conversion starts (mainly for performance reasons).
568 if (command->has_input() && command->input().has_context()) {
569 context_->mutable_client_context()->CopyFrom(
570 command->input().context());
571 } else {
572 context_->mutable_client_context()->Clear();
573 }
574
575 switch (key_command) {
576 case keymap::PrecompositionState::INSERT_CHARACTER:
577 return InsertCharacter(command);
578 case keymap::PrecompositionState::INSERT_SPACE:
579 return InsertSpace(command);
580 case keymap::PrecompositionState::INSERT_ALTERNATE_SPACE:
581 return InsertSpaceToggled(command);
582 case keymap::PrecompositionState::INSERT_HALF_SPACE:
583 return InsertSpaceHalfWidth(command);
584 case keymap::PrecompositionState::INSERT_FULL_SPACE:
585 return InsertSpaceFullWidth(command);
586 case keymap::PrecompositionState::TOGGLE_ALPHANUMERIC_MODE:
587 return ToggleAlphanumericMode(command);
588 case keymap::PrecompositionState::REVERT:
589 return Revert(command);
590 case keymap::PrecompositionState::UNDO:
591 return RequestUndo(command);
592 case keymap::PrecompositionState::IME_OFF:
593 return IMEOff(command);
594 case keymap::PrecompositionState::IME_ON:
595 return DoNothing(command);
596
597 case keymap::PrecompositionState::INPUT_MODE_HIRAGANA:
598 return InputModeHiragana(command);
599 case keymap::PrecompositionState::INPUT_MODE_FULL_KATAKANA:
600 return InputModeFullKatakana(command);
601 case keymap::PrecompositionState::INPUT_MODE_HALF_KATAKANA:
602 return InputModeHalfKatakana(command);
603 case keymap::PrecompositionState::INPUT_MODE_FULL_ALPHANUMERIC:
604 return InputModeFullASCII(command);
605 case keymap::PrecompositionState::INPUT_MODE_HALF_ALPHANUMERIC:
606 return InputModeHalfASCII(command);
607 case keymap::PrecompositionState::INPUT_MODE_SWITCH_KANA_TYPE:
608 return InputModeSwitchKanaType(command);
609
610 case keymap::PrecompositionState::LAUNCH_CONFIG_DIALOG:
611 return LaunchConfigDialog(command);
612 case keymap::PrecompositionState::LAUNCH_DICTIONARY_TOOL:
613 return LaunchDictionaryTool(command);
614 case keymap::PrecompositionState::LAUNCH_WORD_REGISTER_DIALOG:
615 return LaunchWordRegisterDialog(command);
616
617 // For zero query suggestion
618 case keymap::PrecompositionState::CANCEL:
619 // It is a little kind of abuse of the EditCancel command. It
620 // would be nice to make a new command when EditCancel is
621 // extended or the requirement of this command is added.
622 return EditCancel(command);
623 case keymap::PrecompositionState::CANCEL_AND_IME_OFF:
624 // The same to keymap::PrecompositionState::CANCEL.
625 return EditCancelAndIMEOff(command);
626 // For zero query suggestion
627 case keymap::PrecompositionState::COMMIT_FIRST_SUGGESTION:
628 return CommitFirstSuggestion(command);
629 // For zero query suggestion
630 case keymap::PrecompositionState::PREDICT_AND_CONVERT:
631 return PredictAndConvert(command);
632
633 case keymap::PrecompositionState::NONE:
634 return EchoBackAndClearUndoContext(command);
635 case keymap::PrecompositionState::RECONVERT:
636 return RequestConvertReverse(command);
637 }
638 return false;
639 }
640
SendKeyCompositionState(commands::Command * command)641 bool Session::SendKeyCompositionState(commands::Command *command) {
642 keymap::CompositionState::Commands key_command;
643 const keymap::KeyMapManager *keymap =
644 keymap::KeyMapFactory::GetKeyMapManager(context_->keymap());
645 const bool result =
646 context_->converter().CheckState(SessionConverterInterface::SUGGESTION) ?
647 keymap->GetCommandSuggestion(command->input().key(), &key_command) :
648 keymap->GetCommandComposition(command->input().key(), &key_command);
649
650 if (!result) {
651 return DoNothing(command);
652 }
653 string command_name;
654 if (keymap->GetNameFromCommandComposition(key_command, &command_name)) {
655 UsageStats::IncrementCount("Performed_Composition_" + command_name);
656 }
657 switch (key_command) {
658 case keymap::CompositionState::INSERT_CHARACTER:
659 return InsertCharacter(command);
660
661 case keymap::CompositionState::COMMIT:
662 return Commit(command);
663
664 case keymap::CompositionState::COMMIT_FIRST_SUGGESTION:
665 return CommitFirstSuggestion(command);
666
667 case keymap::CompositionState::CONVERT:
668 return Convert(command);
669
670 case keymap::CompositionState::CONVERT_WITHOUT_HISTORY:
671 return ConvertWithoutHistory(command);
672
673 case keymap::CompositionState::PREDICT_AND_CONVERT:
674 return PredictAndConvert(command);
675
676 case keymap::CompositionState::DEL:
677 return Delete(command);
678
679 case keymap::CompositionState::BACKSPACE:
680 return Backspace(command);
681
682 case keymap::CompositionState::INSERT_SPACE:
683 return InsertSpace(command);
684
685 case keymap::CompositionState::INSERT_ALTERNATE_SPACE:
686 return InsertSpaceToggled(command);
687
688 case keymap::CompositionState::INSERT_HALF_SPACE:
689 return InsertSpaceHalfWidth(command);
690
691 case keymap::CompositionState::INSERT_FULL_SPACE:
692 return InsertSpaceFullWidth(command);
693
694 case keymap::CompositionState::MOVE_CURSOR_LEFT:
695 return MoveCursorLeft(command);
696
697 case keymap::CompositionState::MOVE_CURSOR_RIGHT:
698 return MoveCursorRight(command);
699
700 case keymap::CompositionState::MOVE_CURSOR_TO_BEGINNING:
701 return MoveCursorToBeginning(command);
702
703 case keymap::CompositionState::MOVE_MOVE_CURSOR_TO_END:
704 return MoveCursorToEnd(command);
705
706 case keymap::CompositionState::CANCEL:
707 return EditCancel(command);
708
709 case keymap::CompositionState::CANCEL_AND_IME_OFF:
710 return EditCancelAndIMEOff(command);
711
712 case keymap::CompositionState::UNDO:
713 return RequestUndo(command);
714
715 case keymap::CompositionState::IME_OFF:
716 return IMEOff(command);
717
718 case keymap::CompositionState::IME_ON:
719 return DoNothing(command);
720
721 case keymap::CompositionState::CONVERT_TO_HIRAGANA:
722 return ConvertToHiragana(command);
723
724 case keymap::CompositionState::CONVERT_TO_FULL_KATAKANA:
725 return ConvertToFullKatakana(command);
726
727 case keymap::CompositionState::CONVERT_TO_HALF_KATAKANA:
728 return ConvertToHalfKatakana(command);
729
730 case keymap::CompositionState::CONVERT_TO_HALF_WIDTH:
731 return ConvertToHalfWidth(command);
732
733 case keymap::CompositionState::CONVERT_TO_FULL_ALPHANUMERIC:
734 return ConvertToFullASCII(command);
735
736 case keymap::CompositionState::CONVERT_TO_HALF_ALPHANUMERIC:
737 return ConvertToHalfASCII(command);
738
739 case keymap::CompositionState::SWITCH_KANA_TYPE:
740 return SwitchKanaType(command);
741
742 case keymap::CompositionState::DISPLAY_AS_HIRAGANA:
743 return DisplayAsHiragana(command);
744
745 case keymap::CompositionState::DISPLAY_AS_FULL_KATAKANA:
746 return DisplayAsFullKatakana(command);
747
748 case keymap::CompositionState::DISPLAY_AS_HALF_KATAKANA:
749 return DisplayAsHalfKatakana(command);
750
751 case keymap::CompositionState::TRANSLATE_HALF_WIDTH:
752 return TranslateHalfWidth(command);
753
754 case keymap::CompositionState::TRANSLATE_FULL_ASCII:
755 return TranslateFullASCII(command);
756
757 case keymap::CompositionState::TRANSLATE_HALF_ASCII:
758 return TranslateHalfASCII(command);
759
760 case keymap::CompositionState::TOGGLE_ALPHANUMERIC_MODE:
761 return ToggleAlphanumericMode(command);
762
763 case keymap::CompositionState::INPUT_MODE_HIRAGANA:
764 return InputModeHiragana(command);
765
766 case keymap::CompositionState::INPUT_MODE_FULL_KATAKANA:
767 return InputModeFullKatakana(command);
768
769 case keymap::CompositionState::INPUT_MODE_HALF_KATAKANA:
770 return InputModeHalfKatakana(command);
771
772 case keymap::CompositionState::INPUT_MODE_FULL_ALPHANUMERIC:
773 return InputModeFullASCII(command);
774
775 case keymap::CompositionState::INPUT_MODE_HALF_ALPHANUMERIC:
776 return InputModeHalfASCII(command);
777
778 case keymap::CompositionState::NONE:
779 return DoNothing(command);
780 }
781 return false;
782 }
783
SendKeyConversionState(commands::Command * command)784 bool Session::SendKeyConversionState(commands::Command *command) {
785 keymap::ConversionState::Commands key_command;
786 const keymap::KeyMapManager *keymap =
787 keymap::KeyMapFactory::GetKeyMapManager(context_->keymap());
788 const bool result =
789 context_->converter().CheckState(SessionConverterInterface::PREDICTION) ?
790 keymap->GetCommandPrediction(command->input().key(), &key_command) :
791 keymap->GetCommandConversion(command->input().key(), &key_command);
792
793 if (!result) {
794 return DoNothing(command);
795 }
796 string command_name;
797 if (keymap->GetNameFromCommandConversion(key_command,
798 &command_name)) {
799 UsageStats::IncrementCount("Performed_Conversion_" + command_name);
800 }
801 switch (key_command) {
802 case keymap::ConversionState::INSERT_CHARACTER:
803 return InsertCharacter(command);
804
805 case keymap::ConversionState::INSERT_SPACE:
806 return InsertSpace(command);
807
808 case keymap::ConversionState::INSERT_ALTERNATE_SPACE:
809 return InsertSpaceToggled(command);
810
811 case keymap::ConversionState::INSERT_HALF_SPACE:
812 return InsertSpaceHalfWidth(command);
813
814 case keymap::ConversionState::INSERT_FULL_SPACE:
815 return InsertSpaceFullWidth(command);
816
817 case keymap::ConversionState::COMMIT:
818 return Commit(command);
819
820 case keymap::ConversionState::COMMIT_SEGMENT:
821 return CommitSegment(command);
822
823 case keymap::ConversionState::CONVERT_NEXT:
824 return ConvertNext(command);
825
826 case keymap::ConversionState::CONVERT_PREV:
827 return ConvertPrev(command);
828
829 case keymap::ConversionState::CONVERT_NEXT_PAGE:
830 return ConvertNextPage(command);
831
832 case keymap::ConversionState::CONVERT_PREV_PAGE:
833 return ConvertPrevPage(command);
834
835 case keymap::ConversionState::PREDICT_AND_CONVERT:
836 return PredictAndConvert(command);
837
838 case keymap::ConversionState::SEGMENT_FOCUS_LEFT:
839 return SegmentFocusLeft(command);
840
841 case keymap::ConversionState::SEGMENT_FOCUS_RIGHT:
842 return SegmentFocusRight(command);
843
844 case keymap::ConversionState::SEGMENT_FOCUS_FIRST:
845 return SegmentFocusLeftEdge(command);
846
847 case keymap::ConversionState::SEGMENT_FOCUS_LAST:
848 return SegmentFocusLast(command);
849
850 case keymap::ConversionState::SEGMENT_WIDTH_EXPAND:
851 return SegmentWidthExpand(command);
852
853 case keymap::ConversionState::SEGMENT_WIDTH_SHRINK:
854 return SegmentWidthShrink(command);
855
856 case keymap::ConversionState::CANCEL:
857 return ConvertCancel(command);
858
859 case keymap::ConversionState::CANCEL_AND_IME_OFF:
860 return EditCancelAndIMEOff(command);
861
862 case keymap::ConversionState::UNDO:
863 return RequestUndo(command);
864
865 case keymap::ConversionState::IME_OFF:
866 return IMEOff(command);
867
868 case keymap::ConversionState::IME_ON:
869 return DoNothing(command);
870
871 case keymap::ConversionState::CONVERT_TO_HIRAGANA:
872 return ConvertToHiragana(command);
873
874 case keymap::ConversionState::CONVERT_TO_FULL_KATAKANA:
875 return ConvertToFullKatakana(command);
876
877 case keymap::ConversionState::CONVERT_TO_HALF_KATAKANA:
878 return ConvertToHalfKatakana(command);
879
880 case keymap::ConversionState::CONVERT_TO_HALF_WIDTH:
881 return ConvertToHalfWidth(command);
882
883 case keymap::ConversionState::CONVERT_TO_FULL_ALPHANUMERIC:
884 return ConvertToFullASCII(command);
885
886 case keymap::ConversionState::CONVERT_TO_HALF_ALPHANUMERIC:
887 return ConvertToHalfASCII(command);
888
889 case keymap::ConversionState::SWITCH_KANA_TYPE:
890 return SwitchKanaType(command);
891
892 case keymap::ConversionState::DISPLAY_AS_HIRAGANA:
893 return DisplayAsHiragana(command);
894
895 case keymap::ConversionState::DISPLAY_AS_FULL_KATAKANA:
896 return DisplayAsFullKatakana(command);
897
898 case keymap::ConversionState::DISPLAY_AS_HALF_KATAKANA:
899 return DisplayAsHalfKatakana(command);
900
901 case keymap::ConversionState::TRANSLATE_HALF_WIDTH:
902 return TranslateHalfWidth(command);
903
904 case keymap::ConversionState::TRANSLATE_FULL_ASCII:
905 return TranslateFullASCII(command);
906
907 case keymap::ConversionState::TRANSLATE_HALF_ASCII:
908 return TranslateHalfASCII(command);
909
910 case keymap::ConversionState::TOGGLE_ALPHANUMERIC_MODE:
911 return ToggleAlphanumericMode(command);
912
913 case keymap::ConversionState::INPUT_MODE_HIRAGANA:
914 return InputModeHiragana(command);
915
916 case keymap::ConversionState::INPUT_MODE_FULL_KATAKANA:
917 return InputModeFullKatakana(command);
918
919 case keymap::ConversionState::INPUT_MODE_HALF_KATAKANA:
920 return InputModeHalfKatakana(command);
921
922 case keymap::ConversionState::INPUT_MODE_FULL_ALPHANUMERIC:
923 return InputModeFullASCII(command);
924
925 case keymap::ConversionState::INPUT_MODE_HALF_ALPHANUMERIC:
926 return InputModeHalfASCII(command);
927
928 case keymap::ConversionState::REPORT_BUG:
929 return ReportBug(command);
930
931 case keymap::ConversionState::DELETE_SELECTED_CANDIDATE:
932 return DeleteSelectedCandidateFromHistory(command);
933
934 case keymap::ConversionState::NONE:
935 return DoNothing(command);
936 }
937 return false;
938 }
939
UpdatePreferences(commands::Command * command)940 void Session::UpdatePreferences(commands::Command *command) {
941 DCHECK(command);
942 const config::Config &config = command->input().config();
943 if (config.has_session_keymap()) {
944 // Set temporary keymap.
945 context_->set_keymap(config.session_keymap());
946 } else {
947 // Reset keymap.
948 context_->set_keymap(context_->GetConfig().session_keymap());
949 }
950
951 if (command->input().has_capability()) {
952 context_->mutable_client_capability()->CopyFrom(
953 command->input().capability());
954 }
955
956 // Update config values modified temporarily.
957 // TODO(team): Stop using config for temporary modification.
958 if (config.has_selection_shortcut()) {
959 context_->mutable_converter()->set_selection_shortcut(
960 config.selection_shortcut());
961 }
962
963 #if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_NACL)
964 context_->mutable_converter()->set_use_cascading_window(false);
965 #else // OS_LINUX || OS_ANDROID || OS_NACL
966 if (config.has_use_cascading_window()) {
967 context_->mutable_converter()->set_use_cascading_window(
968 config.use_cascading_window());
969 }
970 #endif // OS_LINUX || OS_ANDROID || OS_NACL
971 }
972
IMEOn(commands::Command * command)973 bool Session::IMEOn(commands::Command *command) {
974 command->mutable_output()->set_consumed(true);
975 ClearUndoContext();
976
977 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
978 if (command->input().has_key() && command->input().key().has_mode()) {
979 ApplyInputMode(
980 command->input().key().mode(), context_->mutable_composer());
981 }
982 OutputMode(command);
983 return true;
984 }
985
IMEOff(commands::Command * command)986 bool Session::IMEOff(commands::Command *command) {
987 command->mutable_output()->set_consumed(true);
988 ClearUndoContext();
989
990 Commit(command);
991
992 // Reset the context.
993 context_->mutable_converter()->Reset();
994
995 SetSessionState(ImeContext::DIRECT, context_.get());
996 OutputMode(command);
997 return true;
998 }
999
MakeSureIMEOn(mozc::commands::Command * command)1000 bool Session::MakeSureIMEOn(mozc::commands::Command *command) {
1001 if (command->input().has_command() &&
1002 command->input().command().has_composition_mode() &&
1003 (command->input().command().composition_mode() == commands::DIRECT)) {
1004 // This is invalid and unsupported usage.
1005 return false;
1006 }
1007
1008 command->mutable_output()->set_consumed(true);
1009 if (context_->state() == ImeContext::DIRECT) {
1010 ClearUndoContext();
1011 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
1012 }
1013 if (command->input().has_command() &&
1014 command->input().command().has_composition_mode()) {
1015 ApplyInputMode(command->input().command().composition_mode(),
1016 context_->mutable_composer());
1017 }
1018 OutputMode(command);
1019 return true;
1020 }
1021
MakeSureIMEOff(mozc::commands::Command * command)1022 bool Session::MakeSureIMEOff(mozc::commands::Command *command) {
1023 if (command->input().has_command() &&
1024 command->input().command().has_composition_mode() &&
1025 (command->input().command().composition_mode() == commands::DIRECT)) {
1026 // This is invalid and unsupported usage.
1027 return false;
1028 }
1029
1030 command->mutable_output()->set_consumed(true);
1031 if (context_->state() != ImeContext::DIRECT) {
1032 ClearUndoContext();
1033 Commit(command);
1034 // Reset the context.
1035 context_->mutable_converter()->Reset();
1036 SetSessionState(ImeContext::DIRECT, context_.get());
1037 }
1038 if (command->input().has_command() &&
1039 command->input().command().has_composition_mode()) {
1040 ApplyInputMode(command->input().command().composition_mode(),
1041 context_->mutable_composer());
1042 }
1043 OutputMode(command);
1044 return true;
1045 }
1046
EchoBack(commands::Command * command)1047 bool Session::EchoBack(commands::Command *command) {
1048 command->mutable_output()->set_consumed(false);
1049 context_->mutable_converter()->Reset();
1050 OutputKey(command);
1051 return true;
1052 }
1053
EchoBackAndClearUndoContext(commands::Command * command)1054 bool Session::EchoBackAndClearUndoContext(commands::Command *command) {
1055 command->mutable_output()->set_consumed(false);
1056
1057 // Don't clear undo context when KeyEvent has a modifier key only.
1058 // TODO(hsumita): A modifier key may be assigned to another functions.
1059 // ex) InsertSpace
1060 // We need to check it outside of this function.
1061 const commands::KeyEvent &key_event = command->input().key();
1062 if (!IsPureModifierKeyEvent(key_event)) {
1063 ClearUndoContext();
1064 }
1065
1066 return EchoBack(command);
1067 }
1068
DoNothing(commands::Command * command)1069 bool Session::DoNothing(commands::Command *command) {
1070 command->mutable_output()->set_consumed(true);
1071 // Quick hack for zero query suggestion.
1072 // Caveats: Resetting converter causes b/8703702 on Windows.
1073 // Basically we should not *do* something in DoNothing.
1074 // TODO(komatsu): Fix this.
1075 if (context_->GetRequest().zero_query_suggestion() &&
1076 context_->converter().IsActive() &&
1077 (context_->state() == ImeContext::PRECOMPOSITION)) {
1078 context_->mutable_converter()->Reset();
1079 Output(command);
1080 }
1081 if (context_->state() & (ImeContext::COMPOSITION | ImeContext::CONVERSION)) {
1082 Output(command);
1083 }
1084 return true;
1085 }
1086
Revert(commands::Command * command)1087 bool Session::Revert(commands::Command *command) {
1088 if (context_->state() == ImeContext::PRECOMPOSITION) {
1089 context_->mutable_converter()->Revert();
1090 return EchoBackAndClearUndoContext(command);
1091 }
1092
1093 if (!(context_->state() & (ImeContext::COMPOSITION |
1094 ImeContext::CONVERSION))) {
1095 return DoNothing(command);
1096 }
1097
1098 command->mutable_output()->set_consumed(true);
1099 ClearUndoContext();
1100
1101 if (context_->state() == ImeContext::CONVERSION) {
1102 context_->mutable_converter()->Cancel();
1103 }
1104
1105 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
1106 OutputMode(command);
1107 return true;
1108 }
1109
ResetContext(commands::Command * command)1110 bool Session::ResetContext(commands::Command *command) {
1111 if (context_->state() == ImeContext::PRECOMPOSITION) {
1112 context_->mutable_converter()->Reset();
1113 return EchoBackAndClearUndoContext(command);
1114 }
1115
1116 command->mutable_output()->set_consumed(true);
1117 ClearUndoContext();
1118
1119 context_->mutable_converter()->Reset();
1120
1121 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
1122 OutputMode(command);
1123 return true;
1124 }
1125
SetTable(const composer::Table * table)1126 void Session::SetTable(const composer::Table *table) {
1127 ClearUndoContext();
1128 context_->mutable_composer()->SetTable(table);
1129 }
1130
SetConfig(config::Config * config)1131 void Session::SetConfig(config::Config *config) {
1132 context_->SetConfig(config);
1133 }
1134
SetRequest(const commands::Request * request)1135 void Session::SetRequest(const commands::Request *request) {
1136 ClearUndoContext();
1137 context_->SetRequest(request);
1138 }
1139
GetStatus(commands::Command * command)1140 bool Session::GetStatus(commands::Command *command) {
1141 OutputMode(command);
1142 return true;
1143 }
1144
RequestConvertReverse(commands::Command * command)1145 bool Session::RequestConvertReverse(commands::Command *command) {
1146 if (context_->state() != ImeContext::PRECOMPOSITION &&
1147 context_->state() != ImeContext::DIRECT) {
1148 return DoNothing(command);
1149 }
1150 command->mutable_output()->set_consumed(true);
1151 Output(command);
1152
1153 // Fill callback message.
1154 commands::SessionCommand *session_command =
1155 command->mutable_output()->mutable_callback()->mutable_session_command();
1156 session_command->set_type(commands::SessionCommand::CONVERT_REVERSE);
1157 return true;
1158 }
1159
ConvertReverse(commands::Command * command)1160 bool Session::ConvertReverse(commands::Command *command) {
1161 if (context_->state() != ImeContext::PRECOMPOSITION &&
1162 context_->state() != ImeContext::DIRECT) {
1163 return DoNothing(command);
1164 }
1165 const string &composition = command->input().command().text();
1166 string reading;
1167 if (!context_->mutable_converter()->GetReadingText(composition, &reading)) {
1168 LOG(ERROR) << "Failed to get reading text";
1169 return DoNothing(command);
1170 }
1171
1172 composer::Composer *composer = context_->mutable_composer();
1173 composer->Reset();
1174 std::vector<string> reading_characters;
1175 composer->InsertCharacterPreedit(reading);
1176 composer->set_source_text(composition);
1177 // start conversion here.
1178 if (!context_->mutable_converter()->Convert(*composer)) {
1179 LOG(ERROR) << "Failed to start conversion for reverse conversion";
1180 return false;
1181 }
1182
1183 command->mutable_output()->set_consumed(true);
1184
1185 SetSessionState(ImeContext::CONVERSION, context_.get());
1186 context_->mutable_converter()->SetCandidateListVisible(true);
1187 Output(command);
1188 return true;
1189 }
1190
RequestUndo(commands::Command * command)1191 bool Session::RequestUndo(commands::Command *command) {
1192 if (!(context_->state() & (ImeContext::PRECOMPOSITION |
1193 ImeContext::CONVERSION |
1194 ImeContext::COMPOSITION))) {
1195 return DoNothing(command);
1196 }
1197
1198 // If undo context is empty, echoes back the key event so that it can be
1199 // handled by the application. b/5553298
1200 if (context_->state() == ImeContext::PRECOMPOSITION &&
1201 !prev_context_.get()) {
1202 return EchoBack(command);
1203 }
1204
1205 command->mutable_output()->set_consumed(true);
1206 Output(command);
1207
1208 // Fill callback message.
1209 commands::SessionCommand *session_command =
1210 command->mutable_output()->mutable_callback()->mutable_session_command();
1211 session_command->set_type(commands::SessionCommand::UNDO);
1212 return true;
1213 }
1214
Undo(commands::Command * command)1215 bool Session::Undo(commands::Command *command) {
1216 if (!(context_->state() & (ImeContext::PRECOMPOSITION |
1217 ImeContext::CONVERSION |
1218 ImeContext::COMPOSITION))) {
1219 return DoNothing(command);
1220 }
1221 command->mutable_output()->set_consumed(true);
1222
1223 // Check the undo context
1224 if (!prev_context_.get()) {
1225 return DoNothing(command);
1226 }
1227
1228 // Rollback the last user history.
1229 context_->mutable_converter()->Revert();
1230
1231 size_t result_size = 0;
1232 if (context_->output().has_result()) {
1233 // Check the client's capability
1234 if (!(context_->client_capability().text_deletion() &
1235 commands::Capability::DELETE_PRECEDING_TEXT)) {
1236 return DoNothing(command);
1237 }
1238 result_size = Util::CharsLen(context_->output().result().value());
1239 }
1240
1241 PopUndoContext();
1242
1243 if (result_size > 0) {
1244 commands::DeletionRange *range =
1245 command->mutable_output()->mutable_deletion_range();
1246 range->set_offset(-static_cast<int>(result_size));
1247 range->set_length(result_size);
1248 }
1249
1250 Output(command);
1251 return true;
1252 }
1253
SelectCandidateInternal(commands::Command * command)1254 bool Session::SelectCandidateInternal(commands::Command *command) {
1255 // If the current state is not conversion, composition or
1256 // precomposition, the candidate window should not be shown. (On
1257 // composition or precomposition, the window is able to be shown as
1258 // a suggestion window).
1259 if (!(context_->state() & (ImeContext::CONVERSION |
1260 ImeContext::COMPOSITION |
1261 ImeContext::PRECOMPOSITION))) {
1262 return false;
1263 }
1264 if (!command->input().has_command() ||
1265 !command->input().command().has_id()) {
1266 LOG(WARNING) << "input.command or input.command.id did not exist.";
1267 return false;
1268 }
1269 if (!context_->converter().IsActive()) {
1270 LOG(WARNING) << "converter is not active. (no candidates)";
1271 return false;
1272 }
1273
1274 command->mutable_output()->set_consumed(true);
1275
1276 context_->mutable_converter()->CandidateMoveToId(
1277 command->input().command().id(), context_->composer());
1278 SetSessionState(ImeContext::CONVERSION, context_.get());
1279
1280 return true;
1281 }
1282
SelectCandidate(commands::Command * command)1283 bool Session::SelectCandidate(commands::Command *command) {
1284 if (!SelectCandidateInternal(command)) {
1285 return DoNothing(command);
1286 }
1287 Output(command);
1288 return true;
1289 }
1290
CommitCandidate(commands::Command * command)1291 bool Session::CommitCandidate(commands::Command *command) {
1292 if (!(context_->state() & (ImeContext::COMPOSITION |
1293 ImeContext::CONVERSION |
1294 ImeContext::PRECOMPOSITION))) {
1295 return false;
1296 }
1297 const commands::Input &input = command->input();
1298 if (!input.has_command() ||
1299 !input.command().has_id()) {
1300 LOG(WARNING) << "input.command or input.command.id did not exist.";
1301 return false;
1302 }
1303 if (!context_->converter().IsActive()) {
1304 LOG(WARNING) << "converter is not active. (no candidates)";
1305 return false;
1306 }
1307 command->mutable_output()->set_consumed(true);
1308
1309 PushUndoContext();
1310
1311 if (context_->state() & ImeContext::CONVERSION) {
1312 // There is a focused candidate so just select a candidate based on
1313 // input message and commit first segment.
1314 context_->mutable_converter()->CandidateMoveToId(
1315 input.command().id(), context_->composer());
1316 CommitHeadToFocusedSegmentsInternal(command->input().context());
1317 } else {
1318 // No candidate is focused.
1319 size_t consumed_key_size = 0;
1320 if (context_->mutable_converter()->CommitSuggestionById(
1321 input.command().id(), context_->composer(),
1322 command->input().context(), &consumed_key_size)) {
1323 if (consumed_key_size < context_->composer().GetLength()) {
1324 // partial suggestion was committed.
1325 context_->mutable_composer()->DeleteRange(0, consumed_key_size);
1326 MoveCursorToEnd(command);
1327 // Copy the previous output for Undo.
1328 context_->mutable_output()->CopyFrom(command->output());
1329 return true;
1330 }
1331 }
1332 }
1333
1334 if (!context_->converter().IsActive()) {
1335 // If the converter is not active (ie. the segment size was one.),
1336 // the state should be switched to precomposition.
1337 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
1338
1339 // Get suggestion if zero_query_suggestion is set.
1340 // zero_query_suggestion is usually set where the client is a
1341 // mobile.
1342 if (context_->GetRequest().zero_query_suggestion()) {
1343 Suggest(command->input());
1344 }
1345 }
1346 Output(command);
1347 // Copy the previous output for Undo.
1348 context_->mutable_output()->CopyFrom(command->output());
1349 return true;
1350 }
1351
HighlightCandidate(commands::Command * command)1352 bool Session::HighlightCandidate(commands::Command *command) {
1353 if (!SelectCandidateInternal(command)) {
1354 return false;
1355 }
1356 context_->mutable_converter()->SetCandidateListVisible(true);
1357 Output(command);
1358 return true;
1359 }
1360
MaybeSelectCandidate(commands::Command * command)1361 bool Session::MaybeSelectCandidate(commands::Command *command) {
1362 if (context_->state() != ImeContext::CONVERSION) {
1363 return false;
1364 }
1365
1366 // Note that SHORTCUT_ASDFGHJKL should be handled even when the CapsLock is
1367 // enabled. This is why we need to normalize the key event here.
1368 // See b/5655743.
1369 commands::KeyEvent normalized_keyevent;
1370 KeyEventUtil::NormalizeModifiers(command->input().key(),
1371 &normalized_keyevent);
1372
1373 // Check if the input character is in the shortcut.
1374 // TODO(komatsu): Support non ASCII characters such as Unicode and
1375 // special keys.
1376 const char shortcut = static_cast<char>(normalized_keyevent.key_code());
1377 return context_->mutable_converter()->CandidateMoveToShortcut(shortcut);
1378 }
1379
set_client_capability(const commands::Capability & capability)1380 void Session::set_client_capability(const commands::Capability &capability) {
1381 context_->mutable_client_capability()->CopyFrom(capability);
1382 }
1383
set_application_info(const commands::ApplicationInfo & application_info)1384 void Session::set_application_info(const commands::ApplicationInfo
1385 &application_info) {
1386 context_->mutable_application_info()->CopyFrom(application_info);
1387 }
1388
application_info() const1389 const commands::ApplicationInfo &Session::application_info() const {
1390 return context_->application_info();
1391 }
1392
create_session_time() const1393 uint64 Session::create_session_time() const {
1394 return context_->create_time();
1395 }
1396
last_command_time() const1397 uint64 Session::last_command_time() const {
1398 return context_->last_command_time();
1399 }
1400
InsertCharacter(commands::Command * command)1401 bool Session::InsertCharacter(commands::Command *command) {
1402 if (!command->input().has_key()) {
1403 LOG(ERROR) << "No key event: " << command->input().DebugString();
1404 return false;
1405 }
1406
1407 const commands::KeyEvent &key = command->input().key();
1408
1409 if (key.input_style() == commands::KeyEvent::DIRECT_INPUT &&
1410 context_->state() == ImeContext::PRECOMPOSITION) {
1411 // If the key event represents a half width ascii character (ie.
1412 // key_code is equal to key_string), that key event is not
1413 // consumed and done echo back.
1414 // We must not call |EchoBackAndClearUndoContext| for a half-width space
1415 // here because it should be done in Session::TestSendKey or
1416 // Session::InsertSpaceHalfWidth. Note that the |key| comes from
1417 // Session::InsertSpaceHalfWidth and Session::InsertSpaceFullWidth is
1418 // different from the original key event.
1419 // For example, when the client sends a key command like
1420 // {key.special_key(): HENKAN, key.modifier_keys(): [SHIFT]},
1421 // Session::InsertSpaceHalfWidth replaces it with
1422 // {key.key_string(): " ", key.key_code(): ' '}
1423 // when you assign [Shift+HENKAN] to [InsertSpaceHalfWidth].
1424 // So |key.key_code() == ' '| does not always mean that the original key is
1425 // a space key w/o any modifier.
1426 // This is why we cannot call |EchoBackAndClearUndoContext| when
1427 // |key.key_code() == ' '|. This issue was found in b/5872031.
1428 if (key.key_string().size() == 1 &&
1429 key.key_code() == key.key_string()[0] &&
1430 key.key_code() != ' ') {
1431 return EchoBackAndClearUndoContext(command);
1432 }
1433
1434 context_->mutable_composer()->InsertCharacterKeyEvent(key);
1435 CommitCompositionDirectly(command);
1436 ClearUndoContext(); // UndoContext must be invalidated.
1437 return true;
1438 }
1439
1440 command->mutable_output()->set_consumed(true);
1441
1442 // Handle shortcut keys selecting a candidate from a list.
1443 if (MaybeSelectCandidate(command)) {
1444 Output(command);
1445 return true;
1446 }
1447
1448 string composition;
1449 context_->composer().GetQueryForConversion(&composition);
1450 bool should_commit = (context_->state() == ImeContext::CONVERSION);
1451
1452 if (context_->GetRequest().space_on_alphanumeric() ==
1453 commands::Request::SPACE_OR_CONVERT_COMMITING_COMPOSITION &&
1454 context_->state() == ImeContext::COMPOSITION &&
1455 // TODO(komatsu): Support FullWidthSpace
1456 Util::EndsWith(composition, " ")) {
1457 should_commit = true;
1458 }
1459
1460 if (should_commit) {
1461 CommitNotTriggeringZeroQuerySuggest(command);
1462 if (key.input_style() == commands::KeyEvent::DIRECT_INPUT) {
1463 // Do ClearUndoContext() because it is a direct input.
1464 ClearUndoContext();
1465 context_->mutable_composer()->InsertCharacterKeyEvent(key);
1466 CommitCompositionDirectly(command);
1467 return true;
1468 }
1469 }
1470
1471 context_->mutable_composer()->InsertCharacterKeyEvent(key);
1472 if (context_->mutable_composer()->ShouldCommit()) {
1473 CommitCompositionDirectly(command);
1474 return true;
1475 }
1476 size_t length_to_commit = 0;
1477 if (context_->composer().ShouldCommitHead(&length_to_commit)) {
1478 return CommitHead(length_to_commit, command);
1479 }
1480
1481 SetSessionState(ImeContext::COMPOSITION, context_.get());
1482 if (CanStartAutoConversion(key)) {
1483 return Convert(command);
1484 }
1485
1486 if (Suggest(command->input())) {
1487 Output(command);
1488 return true;
1489 }
1490
1491 OutputComposition(command);
1492 return true;
1493 }
1494
IsFullWidthInsertSpace(const commands::Input & input) const1495 bool Session::IsFullWidthInsertSpace(const commands::Input &input) const {
1496 // If IME is off, any space has to be half-width.
1497 if (context_->state() == ImeContext::DIRECT) {
1498 return false;
1499 }
1500
1501 // In this method, we should not update the actual input mode stored in
1502 // the composer even when |input| has a new input mode. Note that this
1503 // method can be called from TestSendKey, where internal input mode is
1504 // is not expected to be changed. This is one of the reasons why this
1505 // method is a const method.
1506 // On the other hand, this method should behave as if the new input mode
1507 // in |input| was applied. For example, this method should behave as if
1508 // the current input mode was HALF_KATAKANA in the following situation.
1509 // composer's input mode: HIRAGANA
1510 // input.key().mode() : HALF_KATAKANA
1511 // To achieve this, we create a temporary composer object to which the
1512 // new input mode will be stored when |input| has a new input mode.
1513 const composer::Composer* target_composer = &context_->composer();
1514 std::unique_ptr<composer::Composer> temporary_composer;
1515 if (input.has_key() && input.key().has_mode()) {
1516 // Allocate an object only when it is necessary.
1517 temporary_composer.reset(new composer::Composer(NULL, NULL, NULL));
1518 // Copy the current composer state just in case.
1519 temporary_composer->CopyFrom(context_->composer());
1520 ApplyInputMode(input.key().mode(), temporary_composer.get());
1521 // Refer to this temporary composer in this method.
1522 target_composer = temporary_composer.get();
1523 }
1524
1525 // Check the current config and the current input status.
1526 bool is_full_width = false;
1527 switch (context_->GetConfig().space_character_form()) {
1528 case config::Config::FUNDAMENTAL_INPUT_MODE: {
1529 const transliteration::TransliterationType input_mode =
1530 target_composer->GetInputMode();
1531 if (transliteration::T13n::IsInHalfAsciiTypes(input_mode) ||
1532 transliteration::T13n::IsInHalfKatakanaTypes(input_mode)) {
1533 is_full_width = false;
1534 } else {
1535 is_full_width = true;
1536 }
1537 break;
1538 }
1539 case config::Config::FUNDAMENTAL_FULL_WIDTH:
1540 is_full_width = true;
1541 break;
1542 case config::Config::FUNDAMENTAL_HALF_WIDTH:
1543 is_full_width = false;
1544 break;
1545 default:
1546 LOG(WARNING) << "Unknown input mode";
1547 is_full_width = false;
1548 break;
1549 }
1550
1551 return is_full_width;
1552 }
1553
InsertSpace(commands::Command * command)1554 bool Session::InsertSpace(commands::Command *command) {
1555 if (IsFullWidthInsertSpace(command->input())) {
1556 return InsertSpaceFullWidth(command);
1557 } else {
1558 return InsertSpaceHalfWidth(command);
1559 }
1560 }
1561
InsertSpaceToggled(commands::Command * command)1562 bool Session::InsertSpaceToggled(commands::Command *command) {
1563 if (IsFullWidthInsertSpace(command->input())) {
1564 return InsertSpaceHalfWidth(command);
1565 } else {
1566 return InsertSpaceFullWidth(command);
1567 }
1568 }
1569
InsertSpaceHalfWidth(commands::Command * command)1570 bool Session::InsertSpaceHalfWidth(commands::Command *command) {
1571 if (!(context_->state() & (ImeContext::PRECOMPOSITION |
1572 ImeContext::COMPOSITION |
1573 ImeContext::CONVERSION))) {
1574 return DoNothing(command);
1575 }
1576
1577 if (context_->state() == ImeContext::PRECOMPOSITION) {
1578 // TODO(komatsu): This is a hack to work around the problem with
1579 // the inconsistency between TestSendKey and SendKey.
1580 if (IsPureSpaceKey(command->input().key())) {
1581 return EchoBackAndClearUndoContext(command);
1582 }
1583 // UndoContext will be cleared in |InsertCharacter| in this case.
1584 }
1585
1586 const bool has_mode = command->input().key().has_mode();
1587 const commands::CompositionMode mode = command->input().key().mode();
1588 command->mutable_input()->clear_key();
1589 commands::KeyEvent *key_event = command->mutable_input()->mutable_key();
1590 key_event->set_key_code(' ');
1591 key_event->set_key_string(" ");
1592 key_event->set_input_style(commands::KeyEvent::DIRECT_INPUT);
1593 if (has_mode) {
1594 key_event->set_mode(mode);
1595 }
1596 return InsertCharacter(command);
1597 }
1598
InsertSpaceFullWidth(commands::Command * command)1599 bool Session::InsertSpaceFullWidth(commands::Command *command) {
1600 if (!(context_->state() & (ImeContext::PRECOMPOSITION |
1601 ImeContext::COMPOSITION |
1602 ImeContext::CONVERSION))) {
1603 return DoNothing(command);
1604 }
1605
1606 if (context_->state() == ImeContext::PRECOMPOSITION) {
1607 // UndoContext will be cleared in |InsertCharacter| in this case.
1608
1609 // TODO(komatsu): make sure if
1610 // |context_->mutable_converter()->Reset()| is necessary here.
1611 context_->mutable_converter()->Reset();
1612 }
1613
1614 const bool has_mode = command->input().key().has_mode();
1615 const commands::CompositionMode mode = command->input().key().mode();
1616 command->mutable_input()->clear_key();
1617 commands::KeyEvent *key_event = command->mutable_input()->mutable_key();
1618 key_event->set_key_code(' ');
1619 key_event->set_key_string(" "); // full-width space
1620 key_event->set_input_style(commands::KeyEvent::DIRECT_INPUT);
1621 if (has_mode) {
1622 key_event->set_mode(mode);
1623 }
1624 return InsertCharacter(command);
1625 }
1626
TryCancelConvertReverse(commands::Command * command)1627 bool Session::TryCancelConvertReverse(commands::Command *command) {
1628 // If source_text is set, it usually means this session started by a
1629 // reverse conversion.
1630 if (context_->composer().source_text().empty()) {
1631 return false;
1632 }
1633 CommitSourceTextDirectly(command);
1634 return true;
1635 }
1636
EditCancelOnPasswordField(commands::Command * command)1637 bool Session::EditCancelOnPasswordField(commands::Command *command) {
1638 if (context_->composer().GetInputFieldType() != commands::Context::PASSWORD) {
1639 return false;
1640 }
1641
1642 // In password mode, we should commit preedit and close keyboard
1643 // on Android.
1644 // TODO(matsuzakit): Remove this trick. b/5955618
1645 if (context_->composer().source_text().empty()) {
1646 CommitCompositionDirectly(command);
1647 } else {
1648 // Commits original text of reverse conversion.
1649 CommitSourceTextDirectly(command);
1650 }
1651 // Passes the key event through to MozcService.java
1652 // to continue the processes which are invoked by cancel operation.
1653 command->mutable_output()->set_consumed(false);
1654
1655 return true;
1656 }
1657
EditCancel(commands::Command * command)1658 bool Session::EditCancel(commands::Command *command) {
1659 if (EditCancelOnPasswordField(command)) {
1660 return true;
1661 }
1662
1663 command->mutable_output()->set_consumed(true);
1664
1665 // To work around b/5034698, we need to use OutputMode() unless the
1666 // original text is restored to cancel reconversion.
1667 const bool text_restored = TryCancelConvertReverse(command);
1668 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
1669 if (text_restored) {
1670 Output(command);
1671 } else {
1672 // It is nice to use Output() instead of OutputMode(). However, if
1673 // Output() is used, unnecessary candidate words are shown because
1674 // the previous candidate state is not cleared here. To fix it, we
1675 // should carefully modify SessionConverter. See b/5034698.
1676 //
1677 // TODO(komatsu): Use Output() instead of OutputMode.
1678 OutputMode(command);
1679 }
1680 return true;
1681 }
1682
EditCancelAndIMEOff(commands::Command * command)1683 bool Session::EditCancelAndIMEOff(commands::Command *command) {
1684 if (EditCancelOnPasswordField(command)) {
1685 return true;
1686 }
1687
1688 if (!(context_->state() & (ImeContext::PRECOMPOSITION |
1689 ImeContext::COMPOSITION |
1690 ImeContext::CONVERSION))) {
1691 return DoNothing(command);
1692 }
1693
1694 command->mutable_output()->set_consumed(true);
1695
1696 TryCancelConvertReverse(command);
1697
1698 ClearUndoContext();
1699
1700 // Reset the context.
1701 context_->mutable_converter()->Reset();
1702
1703 SetSessionState(ImeContext::DIRECT, context_.get());
1704 Output(command);
1705 return true;
1706 }
1707
CommitInternal(commands::Command * command,bool trigger_zero_query_suggest)1708 bool Session::CommitInternal(commands::Command *command,
1709 bool trigger_zero_query_suggest) {
1710 if (!(context_->state() & (ImeContext::COMPOSITION |
1711 ImeContext::CONVERSION))) {
1712 return DoNothing(command);
1713 }
1714 command->mutable_output()->set_consumed(true);
1715
1716 PushUndoContext();
1717
1718 if (context_->state() == ImeContext::COMPOSITION) {
1719 context_->mutable_converter()->CommitPreedit(context_->composer(),
1720 command->input().context());
1721 } else { // ImeContext::CONVERSION
1722 context_->mutable_converter()->Commit(context_->composer(),
1723 command->input().context());
1724 }
1725
1726 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
1727
1728 if (trigger_zero_query_suggest) {
1729 Suggest(command->input());
1730 }
1731
1732 Output(command);
1733 // Copy the previous output for Undo.
1734 context_->mutable_output()->CopyFrom(command->output());
1735 return true;
1736 }
1737
Commit(commands::Command * command)1738 bool Session::Commit(commands::Command *command) {
1739 return CommitInternal(command,
1740 context_->GetRequest().zero_query_suggestion());
1741 }
1742
CommitNotTriggeringZeroQuerySuggest(commands::Command * command)1743 bool Session::CommitNotTriggeringZeroQuerySuggest(commands::Command *command) {
1744 return CommitInternal(command, false);
1745 }
1746
CommitHead(size_t count,commands::Command * command)1747 bool Session::CommitHead(size_t count, commands::Command *command) {
1748 if (!(context_->state() &
1749 (ImeContext::COMPOSITION | ImeContext::PRECOMPOSITION))) {
1750 return DoNothing(command);
1751 }
1752 command->mutable_output()->set_consumed(true);
1753
1754 // TODO(yamaguchi): Support undo feature.
1755 ClearUndoContext();
1756
1757 size_t committed_size;
1758 context_->mutable_converter()->
1759 CommitHead(count, context_->composer(), &committed_size);
1760 context_->mutable_composer()->DeleteRange(0, committed_size);
1761 Output(command);
1762 return true;
1763 }
1764
CommitFirstSuggestion(commands::Command * command)1765 bool Session::CommitFirstSuggestion(commands::Command *command) {
1766 if (!(context_->state() == ImeContext::COMPOSITION ||
1767 context_->state() == ImeContext::PRECOMPOSITION)) {
1768 return DoNothing(command);
1769 }
1770 if (!context_->converter().IsActive()) {
1771 return DoNothing(command);
1772 }
1773 command->mutable_output()->set_consumed(true);
1774
1775 PushUndoContext();
1776
1777 const int kFirstIndex = 0;
1778 size_t committed_key_size = 0;
1779 context_->mutable_converter()->CommitSuggestionByIndex(
1780 kFirstIndex, context_->composer(), command->input().context(),
1781 &committed_key_size);
1782
1783 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
1784
1785 // Get suggestion if zero_query_suggestion is set.
1786 // zero_query_suggestion is usually set where the client is a mobile.
1787 if (context_->GetRequest().zero_query_suggestion()) {
1788 Suggest(command->input());
1789 }
1790
1791 Output(command);
1792 // Copy the previous output for Undo.
1793 context_->mutable_output()->CopyFrom(command->output());
1794 return true;
1795 }
1796
CommitSegment(commands::Command * command)1797 bool Session::CommitSegment(commands::Command *command) {
1798 if (!(context_->state() & (ImeContext::CONVERSION))) {
1799 return DoNothing(command);
1800 }
1801 command->mutable_output()->set_consumed(true);
1802
1803 PushUndoContext();
1804
1805 CommitFirstSegmentInternal(command->input().context());
1806
1807 if (!context_->converter().IsActive()) {
1808 // If the converter is not active (ie. the segment size was one.),
1809 // the state should be switched to precomposition.
1810 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
1811
1812 // Get suggestion if zero_query_suggestion is set.
1813 // zero_query_suggestion is usually set where the client is a mobile.
1814 if (context_->GetRequest().zero_query_suggestion()) {
1815 Suggest(command->input());
1816 }
1817 }
1818 Output(command);
1819 // Copy the previous output for Undo.
1820 context_->mutable_output()->CopyFrom(command->output());
1821 return true;
1822 }
1823
CommitFirstSegmentInternal(const commands::Context & context)1824 void Session::CommitFirstSegmentInternal(const commands::Context &context) {
1825 size_t size;
1826 context_->mutable_converter()->CommitFirstSegment(
1827 context_->composer(), context, &size);
1828 if (size > 0) {
1829 // Delete the key characters of the first segment from the preedit.
1830 context_->mutable_composer()->DeleteRange(0, size);
1831 // The number of segments should be more than one.
1832 DCHECK_GT(context_->composer().GetLength(), 0);
1833 }
1834 }
1835
CommitHeadToFocusedSegmentsInternal(const commands::Context & context)1836 void Session::CommitHeadToFocusedSegmentsInternal(
1837 const commands::Context &context) {
1838 size_t size;
1839 context_->mutable_converter()->CommitHeadToFocusedSegments(
1840 context_->composer(), context, &size);
1841 if (size > 0) {
1842 // Delete the key characters of the first segment from the preedit.
1843 context_->mutable_composer()->DeleteRange(0, size);
1844 // The number of segments should be more than one.
1845 DCHECK_GT(context_->composer().GetLength(), 0);
1846 }
1847 }
1848
CommitCompositionDirectly(commands::Command * command)1849 void Session::CommitCompositionDirectly(commands::Command *command) {
1850 string composition, conversion;
1851 context_->composer().GetQueryForConversion(&composition);
1852 context_->composer().GetStringForSubmission(&conversion);
1853 CommitStringDirectly(composition, conversion, command);
1854 }
1855
CommitSourceTextDirectly(commands::Command * command)1856 void Session::CommitSourceTextDirectly(commands::Command *command) {
1857 // We cannot use a reference since composer will be cleared on
1858 // CommitStringDirectly.
1859 const string copied_source_text = context_->composer().source_text();
1860 CommitStringDirectly(copied_source_text, copied_source_text, command);
1861 }
1862
CommitRawTextDirectly(commands::Command * command)1863 void Session::CommitRawTextDirectly(commands::Command *command) {
1864 string raw_text;
1865 context_->composer().GetRawString(&raw_text);
1866 CommitStringDirectly(raw_text, raw_text, command);
1867 }
1868
CommitStringDirectly(const string & key,const string & preedit,commands::Command * command)1869 void Session::CommitStringDirectly(const string &key, const string &preedit,
1870 commands::Command *command) {
1871 if (key.empty() || preedit.empty()) {
1872 return;
1873 }
1874
1875 command->mutable_output()->set_consumed(true);
1876 context_->mutable_converter()->Reset();
1877
1878 commands::Result *result = command->mutable_output()->mutable_result();
1879 DCHECK(result != NULL);
1880 result->set_type(commands::Result::STRING);
1881 result->mutable_key()->append(key);
1882 result->mutable_value()->append(preedit);
1883 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
1884
1885 // Get suggestion if zero_query_suggestion is set.
1886 // zero_query_suggestion is usually set where the client is a mobile.
1887 if (context_->GetRequest().zero_query_suggestion()) {
1888 Suggest(command->input());
1889 }
1890
1891 Output(command);
1892 }
1893
1894 namespace {
SuppressSuggestion(const commands::Input & input)1895 bool SuppressSuggestion(const commands::Input &input) {
1896 if (!input.has_context()) {
1897 return false;
1898 }
1899 // If the target input field is in Chrome's Omnibox or Google
1900 // search box, the suggest window is hidden.
1901 for (size_t i = 0; i < input.context().experimental_features_size(); ++i) {
1902 const string &feature = input.context().experimental_features(i);
1903 if (feature == "chrome_omnibox" || feature == "google_search_box") {
1904 return true;
1905 }
1906 }
1907 return false;
1908 }
1909 } // namespace
1910
Suggest(const commands::Input & input)1911 bool Session::Suggest(const commands::Input &input) {
1912 if (SuppressSuggestion(input)) {
1913 return false;
1914 }
1915
1916 // |reuqest_suggestion| is not supposed to always ensure suppressing
1917 // suggestion since this field is used for performance improvement
1918 // by skipping interim suggestions. However, the implementation of
1919 // SessionConverter::SuggestWithPreferences does not perform suggest
1920 // whenever this flag is on. So the caller should consider whether
1921 // this flag should be set or not. Because the original logic was
1922 // implemented in Session::InserCharacter, we check the input.type()
1923 // is SEND_KEY assuming SEND_KEY results InsertCharacter (in most
1924 // cases).
1925 //
1926 // TODO(komatsu): Move the logic into SessionConverter.
1927 if (input.has_request_suggestion() &&
1928 input.type() == commands::Input::SEND_KEY) {
1929 ConversionPreferences conversion_preferences =
1930 context_->converter().conversion_preferences();
1931 conversion_preferences.request_suggestion = input.request_suggestion();
1932 return context_->mutable_converter()->SuggestWithPreferences(
1933 context_->composer(), conversion_preferences);
1934 }
1935
1936 return context_->mutable_converter()->Suggest(context_->composer());
1937 }
1938
1939
ConvertToTransliteration(commands::Command * command,const transliteration::TransliterationType type)1940 bool Session::ConvertToTransliteration(
1941 commands::Command *command,
1942 const transliteration::TransliterationType type) {
1943 if (!(context_->state() & (ImeContext::CONVERSION |
1944 ImeContext::COMPOSITION))) {
1945 return DoNothing(command);
1946 }
1947 command->mutable_output()->set_consumed(true);
1948
1949 if (!context_->mutable_converter()->ConvertToTransliteration(
1950 context_->composer(), type)) {
1951 return false;
1952 }
1953 SetSessionState(ImeContext::CONVERSION, context_.get());
1954 Output(command);
1955 return true;
1956 }
1957
ConvertToHiragana(commands::Command * command)1958 bool Session::ConvertToHiragana(commands::Command *command) {
1959 return ConvertToTransliteration(command, transliteration::HIRAGANA);
1960 }
1961
ConvertToFullKatakana(commands::Command * command)1962 bool Session::ConvertToFullKatakana(commands::Command *command) {
1963 return ConvertToTransliteration(command, transliteration::FULL_KATAKANA);
1964 }
1965
ConvertToHalfKatakana(commands::Command * command)1966 bool Session::ConvertToHalfKatakana(commands::Command *command) {
1967 return ConvertToTransliteration(command, transliteration::HALF_KATAKANA);
1968 }
1969
ConvertToFullASCII(commands::Command * command)1970 bool Session::ConvertToFullASCII(commands::Command *command) {
1971 return ConvertToTransliteration(command, transliteration::FULL_ASCII);
1972 }
1973
ConvertToHalfASCII(commands::Command * command)1974 bool Session::ConvertToHalfASCII(commands::Command *command) {
1975 return ConvertToTransliteration(command, transliteration::HALF_ASCII);
1976 }
1977
SwitchKanaType(commands::Command * command)1978 bool Session::SwitchKanaType(commands::Command *command) {
1979 if (!(context_->state() & (ImeContext::CONVERSION |
1980 ImeContext::COMPOSITION))) {
1981 return DoNothing(command);
1982 }
1983 command->mutable_output()->set_consumed(true);
1984
1985 if (!context_->mutable_converter()->SwitchKanaType(context_->composer())) {
1986 return false;
1987 }
1988 SetSessionState(ImeContext::CONVERSION, context_.get());
1989 Output(command);
1990 return true;
1991 }
1992
DisplayAsHiragana(commands::Command * command)1993 bool Session::DisplayAsHiragana(commands::Command *command) {
1994 command->mutable_output()->set_consumed(true);
1995 if (context_->state() == ImeContext::CONVERSION) {
1996 return ConvertToHiragana(command);
1997 } else { // context_->state() == ImeContext::COMPOSITION
1998 context_->mutable_composer()->SetOutputMode(transliteration::HIRAGANA);
1999 OutputComposition(command);
2000 return true;
2001 }
2002 }
2003
DisplayAsFullKatakana(commands::Command * command)2004 bool Session::DisplayAsFullKatakana(commands::Command *command) {
2005 command->mutable_output()->set_consumed(true);
2006 if (context_->state() == ImeContext::CONVERSION) {
2007 return ConvertToFullKatakana(command);
2008 } else { // context_->state() == ImeContext::COMPOSITION
2009 context_->mutable_composer()->SetOutputMode(transliteration::FULL_KATAKANA);
2010 OutputComposition(command);
2011 return true;
2012 }
2013 }
2014
DisplayAsHalfKatakana(commands::Command * command)2015 bool Session::DisplayAsHalfKatakana(commands::Command *command) {
2016 command->mutable_output()->set_consumed(true);
2017 if (context_->state() == ImeContext::CONVERSION) {
2018 return ConvertToHalfKatakana(command);
2019 } else { // context_->state() == ImeContext::COMPOSITION
2020 context_->mutable_composer()->SetOutputMode(transliteration::HALF_KATAKANA);
2021 OutputComposition(command);
2022 return true;
2023 }
2024 }
2025
TranslateFullASCII(commands::Command * command)2026 bool Session::TranslateFullASCII(commands::Command *command) {
2027 command->mutable_output()->set_consumed(true);
2028 if (context_->state() == ImeContext::CONVERSION) {
2029 return ConvertToFullASCII(command);
2030 } else { // context_->state() == ImeContext::COMPOSITION
2031 context_->mutable_composer()->SetOutputMode(
2032 transliteration::T13n::ToggleFullAsciiTypes(
2033 context_->composer().GetOutputMode()));
2034 OutputComposition(command);
2035 return true;
2036 }
2037 }
2038
TranslateHalfASCII(commands::Command * command)2039 bool Session::TranslateHalfASCII(commands::Command *command) {
2040 command->mutable_output()->set_consumed(true);
2041 if (context_->state() == ImeContext::CONVERSION) {
2042 return ConvertToHalfASCII(command);
2043 } else { // context_->state() == ImeContext::COMPOSITION
2044 context_->mutable_composer()->SetOutputMode(
2045 transliteration::T13n::ToggleHalfAsciiTypes(
2046 context_->composer().GetOutputMode()));
2047 OutputComposition(command);
2048 return true;
2049 }
2050 }
2051
InputModeHiragana(commands::Command * command)2052 bool Session::InputModeHiragana(commands::Command *command) {
2053 command->mutable_output()->set_consumed(true);
2054 EnsureIMEIsOn();
2055 // The temporary mode should not be overridden.
2056 SwitchInputMode(transliteration::HIRAGANA, context_->mutable_composer());
2057 OutputFromState(command);
2058 return true;
2059 }
2060
InputModeFullKatakana(commands::Command * command)2061 bool Session::InputModeFullKatakana(commands::Command *command) {
2062 command->mutable_output()->set_consumed(true);
2063 EnsureIMEIsOn();
2064 // The temporary mode should not be overridden.
2065 SwitchInputMode(transliteration::FULL_KATAKANA, context_->mutable_composer());
2066 OutputFromState(command);
2067 return true;
2068 }
2069
InputModeHalfKatakana(commands::Command * command)2070 bool Session::InputModeHalfKatakana(commands::Command *command) {
2071 command->mutable_output()->set_consumed(true);
2072 EnsureIMEIsOn();
2073 // The temporary mode should not be overridden.
2074 SwitchInputMode(transliteration::HALF_KATAKANA, context_->mutable_composer());
2075 OutputFromState(command);
2076 return true;
2077 }
2078
InputModeFullASCII(commands::Command * command)2079 bool Session::InputModeFullASCII(commands::Command *command) {
2080 command->mutable_output()->set_consumed(true);
2081 EnsureIMEIsOn();
2082 // The temporary mode should not be overridden.
2083 SwitchInputMode(transliteration::FULL_ASCII, context_->mutable_composer());
2084 OutputFromState(command);
2085 return true;
2086 }
2087
InputModeHalfASCII(commands::Command * command)2088 bool Session::InputModeHalfASCII(commands::Command *command) {
2089 command->mutable_output()->set_consumed(true);
2090 EnsureIMEIsOn();
2091 // The temporary mode should not be overridden.
2092 SwitchInputMode(transliteration::HALF_ASCII, context_->mutable_composer());
2093 OutputFromState(command);
2094 return true;
2095 }
2096
InputModeSwitchKanaType(commands::Command * command)2097 bool Session::InputModeSwitchKanaType(commands::Command *command) {
2098 if (context_->state() != ImeContext::PRECOMPOSITION) {
2099 return DoNothing(command);
2100 }
2101
2102 command->mutable_output()->set_consumed(true);
2103
2104 transliteration::TransliterationType current_type =
2105 context_->composer().GetInputMode();
2106 transliteration::TransliterationType next_type;
2107
2108 switch (current_type) {
2109 case transliteration::HIRAGANA:
2110 next_type = transliteration::FULL_KATAKANA;
2111 break;
2112
2113 case transliteration::FULL_KATAKANA:
2114 next_type = transliteration::HALF_KATAKANA;
2115 break;
2116
2117 case transliteration::HALF_KATAKANA:
2118 next_type = transliteration::HIRAGANA;
2119 break;
2120
2121 case transliteration::HALF_ASCII:
2122 case transliteration::FULL_ASCII:
2123 next_type = current_type;
2124 break;
2125
2126 default:
2127 LOG(ERROR) << "Unknown input mode: " << current_type;
2128 // don't change input mode
2129 next_type = current_type;
2130 break;
2131 }
2132
2133 // The temporary mode should not be overridden.
2134 SwitchInputMode(next_type, context_->mutable_composer());
2135 OutputFromState(command);
2136 return true;
2137 }
2138
ConvertToHalfWidth(commands::Command * command)2139 bool Session::ConvertToHalfWidth(commands::Command *command) {
2140 if (!(context_->state() & (ImeContext::CONVERSION |
2141 ImeContext::COMPOSITION))) {
2142 return DoNothing(command);
2143 }
2144 command->mutable_output()->set_consumed(true);
2145
2146 if (!context_->mutable_converter()->ConvertToHalfWidth(
2147 context_->composer())) {
2148 return false;
2149 }
2150 SetSessionState(ImeContext::CONVERSION, context_.get());
2151 Output(command);
2152 return true;
2153 }
2154
TranslateHalfWidth(commands::Command * command)2155 bool Session::TranslateHalfWidth(commands::Command *command) {
2156 command->mutable_output()->set_consumed(true);
2157 if (context_->state() == ImeContext::CONVERSION) {
2158 return ConvertToHalfWidth(command);
2159 } else { // context_->state() == ImeContext::COMPOSITION
2160 const transliteration::TransliterationType type =
2161 context_->composer().GetOutputMode();
2162 if (type == transliteration::HIRAGANA ||
2163 type == transliteration::FULL_KATAKANA ||
2164 type == transliteration::HALF_KATAKANA) {
2165 context_->mutable_composer()->SetOutputMode(
2166 transliteration::HALF_KATAKANA);
2167 } else if (type == transliteration::FULL_ASCII) {
2168 context_->mutable_composer()->SetOutputMode(transliteration::HALF_ASCII);
2169 } else if (type == transliteration::FULL_ASCII_UPPER) {
2170 context_->mutable_composer()->SetOutputMode(
2171 transliteration::HALF_ASCII_UPPER);
2172 } else if (type == transliteration::FULL_ASCII_LOWER) {
2173 context_->mutable_composer()->SetOutputMode(
2174 transliteration::HALF_ASCII_LOWER);
2175 } else if (type == transliteration::FULL_ASCII_CAPITALIZED) {
2176 context_->mutable_composer()->SetOutputMode(
2177 transliteration::HALF_ASCII_CAPITALIZED);
2178 } else {
2179 // transliteration::HALF_ASCII_something
2180 return TranslateHalfASCII(command);
2181 }
2182 OutputComposition(command);
2183 return true;
2184 }
2185 }
2186
LaunchConfigDialog(commands::Command * command)2187 bool Session::LaunchConfigDialog(commands::Command *command) {
2188 command->mutable_output()->set_launch_tool_mode(
2189 commands::Output::CONFIG_DIALOG);
2190 return DoNothing(command);
2191 }
2192
LaunchDictionaryTool(commands::Command * command)2193 bool Session::LaunchDictionaryTool(commands::Command *command) {
2194 command->mutable_output()->set_launch_tool_mode(
2195 commands::Output::DICTIONARY_TOOL);
2196 return DoNothing(command);
2197 }
2198
LaunchWordRegisterDialog(commands::Command * command)2199 bool Session::LaunchWordRegisterDialog(commands::Command *command) {
2200 command->mutable_output()->set_launch_tool_mode(
2201 commands::Output::WORD_REGISTER_DIALOG);
2202 return DoNothing(command);
2203 }
2204
UndoOrRewind(commands::Command * command)2205 bool Session::UndoOrRewind(commands::Command *command) {
2206 // Rewind if the state is in composition.
2207 if (context_->state() & ImeContext::COMPOSITION) {
2208 command->mutable_output()->set_consumed(true);
2209 return SendComposerCommand(composer::Composer::REWIND, command);
2210 }
2211
2212 // Undo if we can order UNDO command.
2213 if (prev_context_.get()) {
2214 return Undo(command);
2215 }
2216
2217 return DoNothing(command);
2218 }
2219
SendComposerCommand(const composer::Composer::InternalCommand composer_command,commands::Command * command)2220 bool Session::SendComposerCommand(
2221 const composer::Composer::InternalCommand composer_command,
2222 commands::Command *command) {
2223 if (!(context_->state() & ImeContext::COMPOSITION)) {
2224 DLOG(WARNING) << "State : " << context_->state();
2225 return false;
2226 }
2227
2228 context_->mutable_composer()->InsertCommandCharacter(composer_command);
2229 // InsertCommandCharacter method updates the preedit text
2230 // so we need to update suggest candidates.
2231 if (Suggest(command->input())) {
2232 Output(command);
2233 return true;
2234 }
2235 OutputComposition(command);
2236 return true;
2237 }
2238
ToggleAlphanumericMode(commands::Command * command)2239 bool Session::ToggleAlphanumericMode(commands::Command *command) {
2240 command->mutable_output()->set_consumed(true);
2241 context_->mutable_composer()->ToggleInputMode();
2242
2243 OutputFromState(command);
2244 return true;
2245 }
2246
DeleteSelectedCandidateFromHistory(commands::Command * command)2247 bool Session::DeleteSelectedCandidateFromHistory(commands::Command *command) {
2248 const Segment::Candidate *cand =
2249 context_->converter().GetSelectedCandidateOfFocusedSegment();
2250 if (cand == NULL) {
2251 LOG(WARNING) << "No candidate is selected.";
2252 return DoNothing(command);
2253 }
2254 UserDataManagerInterface *manager = engine_->GetUserDataManager();
2255 if (!manager->ClearUserPredictionEntry(cand->key, cand->value)) {
2256 DLOG(WARNING) << "Cannot delete non-history candidate or deletion failed: "
2257 << cand->DebugString();
2258 return DoNothing(command);
2259 }
2260 return ConvertCancel(command);
2261 }
2262
Convert(commands::Command * command)2263 bool Session::Convert(commands::Command *command) {
2264 command->mutable_output()->set_consumed(true);
2265 string composition;
2266 context_->composer().GetQueryForConversion(&composition);
2267
2268 // TODO(komatsu): Make a function like ConvertOrSpace.
2269 // Handle a space key on the ASCII composition mode.
2270 if (context_->state() == ImeContext::COMPOSITION &&
2271 (context_->composer().GetInputMode() == transliteration::HALF_ASCII ||
2272 context_->composer().GetInputMode() == transliteration::FULL_ASCII) &&
2273 command->input().key().has_special_key() &&
2274 command->input().key().special_key() == commands::KeyEvent::SPACE) {
2275 // TODO(komatsu): Consider FullWidth Space too.
2276 if (!Util::EndsWith(composition, " ")) {
2277 if (context_->GetRequest().space_on_alphanumeric() ==
2278 commands::Request::COMMIT) {
2279 // Space is committed with the composition
2280 context_->mutable_composer()->InsertCharacterPreedit(" ");
2281 return Commit(command);
2282 } else {
2283 // SPACE_OR_CONVERT_KEEPING_COMPOSITION or
2284 // SPACE_OR_CONVERT_COMMITING_COMPOSITION.
2285
2286 // If the last character is not space, space is inserted to the
2287 // composition.
2288 command->mutable_input()->mutable_key()->set_key_code(' ');
2289 return InsertCharacter(command);
2290 }
2291 }
2292
2293 if (!composition.empty()) {
2294 DCHECK_EQ(' ', composition[composition.size() - 1]);
2295 // Delete the last space.
2296 context_->mutable_composer()->Backspace();
2297 }
2298 }
2299
2300 if (!context_->mutable_converter()->Convert(context_->composer())) {
2301 LOG(ERROR) << "Conversion failed for some reasons.";
2302 OutputComposition(command);
2303 return true;
2304 }
2305
2306 SetSessionState(ImeContext::CONVERSION, context_.get());
2307 Output(command);
2308 return true;
2309 }
2310
ConvertWithoutHistory(commands::Command * command)2311 bool Session::ConvertWithoutHistory(commands::Command *command) {
2312 command->mutable_output()->set_consumed(true);
2313
2314 ConversionPreferences preferences =
2315 context_->converter().conversion_preferences();
2316 preferences.use_history = false;
2317 if (!context_->mutable_converter()->ConvertWithPreferences(
2318 context_->composer(), preferences)) {
2319 LOG(ERROR) << "Conversion failed for some reasons.";
2320 OutputComposition(command);
2321 return true;
2322 }
2323
2324 SetSessionState(ImeContext::CONVERSION, context_.get());
2325 Output(command);
2326 return true;
2327 }
2328
CommitIfPassword(commands::Command * command)2329 bool Session::CommitIfPassword(commands::Command *command) {
2330 if (context_->composer().GetInputFieldType() == commands::Context::PASSWORD) {
2331 CommitCompositionDirectly(command);
2332 return true;
2333 }
2334 return false;
2335 }
2336
MoveCursorRight(commands::Command * command)2337 bool Session::MoveCursorRight(commands::Command *command) {
2338 // In future, we may want to change the strategy of committing, to support
2339 // more flexible behavior.
2340 // - If the composing text has some "pending toggling character(s) at the
2341 // end", we'd like to "fix" the toggling state, but not to commit.
2342 // - Otherwise (i.e. if there is no such character(s)), we'd like to commit
2343 // (considering the use cases, probably we'd like to apply it only for
2344 // alphabet mode).
2345 // Before supporting it, we'll need to support auto fixing by waiting
2346 // a period. Also, it is necessary to support displaying the current toggling
2347 // state (otherwise, users would be confused).
2348 // So, to keep users out from such confusion, we only commit if the current
2349 // composing mode doesn't has toggling state. Clients has the responsibility
2350 // to check if the keyboard has toggling state or not. Note that the server
2351 // should know the current table has toggling state or not. However,
2352 // a client may NOT want to auto committing even if the composition mode
2353 // doesn't have the toggling state, so the server just relies on the flag
2354 // passed from the client.
2355 // TODO(hidehiko): Support it, when it is prioritized.
2356 if (context_->GetRequest().crossing_edge_behavior() ==
2357 commands::Request::COMMIT_WITHOUT_CONSUMING &&
2358 context_->composer().GetLength() == context_->composer().GetCursor()) {
2359 Commit(command);
2360
2361 // Do not consume.
2362 command->mutable_output()->set_consumed(false);
2363 return true;
2364 }
2365
2366 command->mutable_output()->set_consumed(true);
2367 if (CommitIfPassword(command)) {
2368 return true;
2369 }
2370 context_->mutable_composer()->MoveCursorRight();
2371 if (Suggest(command->input())) {
2372 Output(command);
2373 return true;
2374 }
2375 OutputComposition(command);
2376 return true;
2377 }
2378
MoveCursorLeft(commands::Command * command)2379 bool Session::MoveCursorLeft(commands::Command *command) {
2380 if (context_->GetRequest().crossing_edge_behavior() ==
2381 commands::Request::COMMIT_WITHOUT_CONSUMING &&
2382 context_->composer().GetCursor() == 0) {
2383 CommitNotTriggeringZeroQuerySuggest(command);
2384
2385 // Move the cursor to the beginning of the values.
2386 command->mutable_output()->mutable_result()->set_cursor_offset(
2387 -static_cast<int32>(
2388 Util::CharsLen(command->output().result().value())));
2389
2390 // Do not consume.
2391 command->mutable_output()->set_consumed(false);
2392 return true;
2393 }
2394
2395 command->mutable_output()->set_consumed(true);
2396 if (CommitIfPassword(command)) {
2397 return true;
2398 }
2399 context_->mutable_composer()->MoveCursorLeft();
2400 if (Suggest(command->input())) {
2401 Output(command);
2402 return true;
2403 }
2404 OutputComposition(command);
2405 return true;
2406 }
2407
MoveCursorToEnd(commands::Command * command)2408 bool Session::MoveCursorToEnd(commands::Command *command) {
2409 command->mutable_output()->set_consumed(true);
2410 if (CommitIfPassword(command)) {
2411 return true;
2412 }
2413 context_->mutable_composer()->MoveCursorToEnd();
2414 if (Suggest(command->input())) {
2415 Output(command);
2416 return true;
2417 }
2418 OutputComposition(command);
2419 return true;
2420 }
2421
MoveCursorTo(commands::Command * command)2422 bool Session::MoveCursorTo(commands::Command *command) {
2423 if (context_->state() != ImeContext::COMPOSITION) {
2424 return DoNothing(command);
2425 }
2426 command->mutable_output()->set_consumed(true);
2427 if (CommitIfPassword(command)) {
2428 return true;
2429 }
2430 context_->mutable_composer()->
2431 MoveCursorTo(command->input().command().cursor_position());
2432 if (Suggest(command->input())) {
2433 Output(command);
2434 return true;
2435 }
2436 OutputComposition(command);
2437 return true;
2438 }
2439
MoveCursorToBeginning(commands::Command * command)2440 bool Session::MoveCursorToBeginning(commands::Command *command) {
2441 command->mutable_output()->set_consumed(true);
2442 if (CommitIfPassword(command)) {
2443 return true;
2444 }
2445 context_->mutable_composer()->MoveCursorToBeginning();
2446 if (Suggest(command->input())) {
2447 Output(command);
2448 return true;
2449 }
2450 OutputComposition(command);
2451 return true;
2452 }
2453
Delete(commands::Command * command)2454 bool Session::Delete(commands::Command *command) {
2455 command->mutable_output()->set_consumed(true);
2456 context_->mutable_composer()->Delete();
2457 if (context_->mutable_composer()->Empty()) {
2458 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
2459 OutputMode(command);
2460 } else if (Suggest(command->input())) {
2461 Output(command);
2462 return true;
2463 } else {
2464 OutputComposition(command);
2465 }
2466 return true;
2467 }
2468
Backspace(commands::Command * command)2469 bool Session::Backspace(commands::Command *command) {
2470 command->mutable_output()->set_consumed(true);
2471 context_->mutable_composer()->Backspace();
2472 if (context_->mutable_composer()->Empty()) {
2473 SetSessionState(ImeContext::PRECOMPOSITION, context_.get());
2474 OutputMode(command);
2475 } else if (Suggest(command->input())) {
2476 Output(command);
2477 return true;
2478 } else {
2479 OutputComposition(command);
2480 }
2481 return true;
2482 }
2483
SegmentFocusRight(commands::Command * command)2484 bool Session::SegmentFocusRight(commands::Command *command) {
2485 if (!(context_->state() & (ImeContext::CONVERSION))) {
2486 return DoNothing(command);
2487 }
2488 command->mutable_output()->set_consumed(true);
2489 context_->mutable_converter()->SegmentFocusRight();
2490 Output(command);
2491 return true;
2492 }
2493
SegmentFocusLast(commands::Command * command)2494 bool Session::SegmentFocusLast(commands::Command *command) {
2495 if (!(context_->state() & (ImeContext::CONVERSION))) {
2496 return DoNothing(command);
2497 }
2498 command->mutable_output()->set_consumed(true);
2499 context_->mutable_converter()->SegmentFocusLast();
2500 Output(command);
2501 return true;
2502 }
2503
SegmentFocusLeft(commands::Command * command)2504 bool Session::SegmentFocusLeft(commands::Command *command) {
2505 if (!(context_->state() & (ImeContext::CONVERSION))) {
2506 return DoNothing(command);
2507 }
2508 command->mutable_output()->set_consumed(true);
2509 context_->mutable_converter()->SegmentFocusLeft();
2510 Output(command);
2511 return true;
2512 }
2513
SegmentFocusLeftEdge(commands::Command * command)2514 bool Session::SegmentFocusLeftEdge(commands::Command *command) {
2515 if (!(context_->state() & (ImeContext::CONVERSION))) {
2516 return DoNothing(command);
2517 }
2518 command->mutable_output()->set_consumed(true);
2519 context_->mutable_converter()->SegmentFocusLeftEdge();
2520 Output(command);
2521 return true;
2522 }
2523
SegmentWidthExpand(commands::Command * command)2524 bool Session::SegmentWidthExpand(commands::Command *command) {
2525 if (!(context_->state() & (ImeContext::CONVERSION))) {
2526 return DoNothing(command);
2527 }
2528 command->mutable_output()->set_consumed(true);
2529 context_->mutable_converter()->SegmentWidthExpand(context_->composer());
2530 Output(command);
2531 return true;
2532 }
2533
SegmentWidthShrink(commands::Command * command)2534 bool Session::SegmentWidthShrink(commands::Command *command) {
2535 if (!(context_->state() & (ImeContext::CONVERSION))) {
2536 return DoNothing(command);
2537 }
2538 command->mutable_output()->set_consumed(true);
2539 context_->mutable_converter()->SegmentWidthShrink(context_->composer());
2540 Output(command);
2541 return true;
2542 }
2543
ReportBug(commands::Command * command)2544 bool Session::ReportBug(commands::Command *command) {
2545 return DoNothing(command);
2546 }
2547
ConvertNext(commands::Command * command)2548 bool Session::ConvertNext(commands::Command *command) {
2549 command->mutable_output()->set_consumed(true);
2550 context_->mutable_converter()->CandidateNext(context_->composer());
2551 Output(command);
2552 return true;
2553 }
2554
ConvertNextPage(commands::Command * command)2555 bool Session::ConvertNextPage(commands::Command *command) {
2556 if (!(context_->state() & (ImeContext::CONVERSION))) {
2557 return DoNothing(command);
2558 }
2559 command->mutable_output()->set_consumed(true);
2560 context_->mutable_converter()->CandidateNextPage();
2561 Output(command);
2562 return true;
2563 }
2564
ConvertPrev(commands::Command * command)2565 bool Session::ConvertPrev(commands::Command *command) {
2566 command->mutable_output()->set_consumed(true);
2567 context_->mutable_converter()->CandidatePrev();
2568 Output(command);
2569 return true;
2570 }
2571
ConvertPrevPage(commands::Command * command)2572 bool Session::ConvertPrevPage(commands::Command *command) {
2573 if (!(context_->state() & (ImeContext::CONVERSION))) {
2574 return DoNothing(command);
2575 }
2576 command->mutable_output()->set_consumed(true);
2577 context_->mutable_converter()->CandidatePrevPage();
2578 Output(command);
2579 return true;
2580 }
2581
ConvertCancel(commands::Command * command)2582 bool Session::ConvertCancel(commands::Command *command) {
2583 command->mutable_output()->set_consumed(true);
2584
2585 SetSessionState(ImeContext::COMPOSITION, context_.get());
2586 context_->mutable_converter()->Cancel();
2587 if (Suggest(command->input())) {
2588 Output(command);
2589 } else {
2590 OutputComposition(command);
2591 }
2592 return true;
2593 }
2594
PredictAndConvert(commands::Command * command)2595 bool Session::PredictAndConvert(commands::Command *command) {
2596 if (context_->state() == ImeContext::CONVERSION) {
2597 return ConvertNext(command);
2598 }
2599
2600 command->mutable_output()->set_consumed(true);
2601 if (context_->mutable_converter()->Predict(context_->composer())) {
2602 SetSessionState(ImeContext::CONVERSION, context_.get());
2603 Output(command);
2604 } else {
2605 OutputComposition(command);
2606 }
2607 return true;
2608 }
2609
ExpandSuggestion(commands::Command * command)2610 bool Session::ExpandSuggestion(commands::Command *command) {
2611 if (context_->state() == ImeContext::CONVERSION ||
2612 context_->state() == ImeContext::DIRECT) {
2613 return DoNothing(command);
2614 }
2615
2616 command->mutable_output()->set_consumed(true);
2617 context_->mutable_converter()->ExpandSuggestion(context_->composer());
2618 Output(command);
2619 return true;
2620 }
2621
OutputFromState(commands::Command * command)2622 void Session::OutputFromState(commands::Command *command) {
2623 if (context_->state() == ImeContext::PRECOMPOSITION) {
2624 OutputMode(command);
2625 return;
2626 }
2627 if (context_->state() == ImeContext::COMPOSITION) {
2628 OutputComposition(command);
2629 return;
2630 }
2631 if (context_->state() == ImeContext::CONVERSION) {
2632 Output(command);
2633 return;
2634 }
2635 OutputMode(command);
2636 }
2637
Output(commands::Command * command)2638 void Session::Output(commands::Command *command) {
2639 OutputMode(command);
2640 context_->mutable_converter()->PopOutput(
2641 context_->composer(), command->mutable_output());
2642 }
2643
OutputMode(commands::Command * command) const2644 void Session::OutputMode(commands::Command *command) const {
2645 const commands::CompositionMode mode =
2646 ToCompositionMode(context_->composer().GetInputMode());
2647 const commands::CompositionMode comeback_mode =
2648 ToCompositionMode(context_->composer().GetComebackInputMode());
2649
2650 commands::Output *output = command->mutable_output();
2651 commands::Status *status = output->mutable_status();
2652 if (context_->state() == ImeContext::DIRECT) {
2653 output->set_mode(commands::DIRECT);
2654 status->set_activated(false);
2655 } else {
2656 output->set_mode(mode);
2657 status->set_activated(true);
2658 }
2659 status->set_mode(mode);
2660 status->set_comeback_mode(comeback_mode);
2661 }
2662
OutputComposition(commands::Command * command) const2663 void Session::OutputComposition(commands::Command *command) const {
2664 OutputMode(command);
2665 commands::Preedit *preedit = command->mutable_output()->mutable_preedit();
2666 SessionOutput::FillPreedit(context_->composer(), preedit);
2667 }
2668
OutputKey(commands::Command * command) const2669 void Session::OutputKey(commands::Command *command) const {
2670 OutputMode(command);
2671 commands::KeyEvent *key = command->mutable_output()->mutable_key();
2672 key->CopyFrom(command->input().key());
2673 }
2674
2675 namespace {
2676 // return
2677 // ((key_code == static_cast<uint32>('.') ||
2678 // key_string == "." || key_string == "." ||
2679 // key_string == "。" || key_string == "。") &&
2680 // (config.auto_conversion_key() &
2681 // config::Config::AUTO_CONVERSION_KUTEN)) ||
2682 // ((key_code == static_cast<uint32>(',') ||
2683 // key_string == "," || key_string == "," ||
2684 // key_string == "、" || key_string == "、") &&
2685 // (config.auto_conversion_key() &
2686 // config::Config::AUTO_CONVERSION_TOUTEN)) ||
2687 // ((key_code == static_cast<uint32>('?') ||
2688 // key_string == "?" || key_string == "?") &&
2689 // (config.auto_conversion_key() &
2690 // config::Config::AUTO_CONVERSION_QUESTION_MARK)) ||
2691 // ((key_code == static_cast<uint32>('!') ||
2692 // key_string == "!" || key_string == "!") &&
2693 // (config.auto_conversion_key() &
2694 // config::Config::AUTO_CONVERSION_EXCLAMATION_MARK));
IsValidKey(const config::Config & config,const uint32 key_code,const string & key_string)2695 bool IsValidKey(const config::Config &config,
2696 const uint32 key_code, const string &key_string) {
2697 return
2698 (((key_code == static_cast<uint32>('.') && key_string.empty()) ||
2699 key_string == "." || key_string == "." ||
2700 key_string == "。" || key_string == "。") &&
2701 (config.auto_conversion_key() &
2702 config::Config::AUTO_CONVERSION_KUTEN)) ||
2703 (((key_code == static_cast<uint32>(',') && key_string.empty()) ||
2704 key_string == "," || key_string == "," ||
2705 key_string == "、" || key_string == "、") &&
2706 (config.auto_conversion_key() &
2707 config::Config::AUTO_CONVERSION_TOUTEN)) ||
2708 (((key_code == static_cast<uint32>('?') && key_string.empty()) ||
2709 key_string == "?" || key_string == "?") &&
2710 (config.auto_conversion_key() &
2711 config::Config::AUTO_CONVERSION_QUESTION_MARK)) ||
2712 (((key_code == static_cast<uint32>('!') && key_string.empty()) ||
2713 key_string == "!" || key_string == "!") &&
2714 (config.auto_conversion_key() &
2715 config::Config::AUTO_CONVERSION_EXCLAMATION_MARK));
2716 }
2717 } // namespace
2718
CanStartAutoConversion(const commands::KeyEvent & key_event) const2719 bool Session::CanStartAutoConversion(
2720 const commands::KeyEvent &key_event) const {
2721 if (!context_->GetConfig().use_auto_conversion()) {
2722 return false;
2723 }
2724
2725 // Disable if the input comes from non-standard user keyboards, like numpad.
2726 // http://b/issue?id=2932067
2727 if (key_event.input_style() != commands::KeyEvent::FOLLOW_MODE) {
2728 return false;
2729 }
2730
2731 // This is a tentative workaround for the bug http://b/issue?id=2932028
2732 // When user types <Shift Down>O<Shift Up>racle<Shift Down>!<Shift Up>,
2733 // The final "!" must be half-width, however, due to the limitation
2734 // of converter interface, we don't have a good way to change it halfwidth, as
2735 // the default preference of "!" is fullwidth. Basically, the converter is
2736 // not composition-mode-aware.
2737 // We simply disable the auto conversion feature if the mode is ASCII.
2738 // We conclude that disabling this feature is better in this situation.
2739 // TODO(taku): fix the behavior. Converter module needs to be fixed.
2740 if (key_event.mode() == commands::HALF_ASCII ||
2741 key_event.mode() == commands::FULL_ASCII) {
2742 return false;
2743 }
2744
2745 // We should NOT check key_string. http://b/issue?id=3217992
2746
2747 // Auto conversion is not triggered if the composition is empty or
2748 // only one character, or the cursor is not in the end of the
2749 // composition.
2750 const size_t length = context_->composer().GetLength();
2751 if (length <= 1 || length != context_->composer().GetCursor()) {
2752 return false;
2753 }
2754
2755 const uint32 key_code = key_event.key_code();
2756
2757 string preedit;
2758 context_->composer().GetStringForPreedit(&preedit);
2759 const string last_char = Util::SubString(preedit, length - 1, 1);
2760 if (last_char.empty()) {
2761 return false;
2762 }
2763
2764 // Check last character as user may change romaji table,
2765 // For instance, if user assigns "." as "foo", we don't
2766 // want to invoke auto_conversion.
2767 if (!IsValidKey(context_->GetConfig(), key_code, last_char)) {
2768 return false;
2769 }
2770
2771 // check the previous character of last_character.
2772 // when |last_prev_char| is number, we don't invoke auto_conversion
2773 // if the same invoke key is repeated, do not conversion.
2774 // http://b/issue?id=2932118
2775 const string last_prev_char = Util::SubString(preedit, length - 2, 1);
2776 if (last_prev_char.empty() || last_prev_char == last_char ||
2777 Util::NUMBER == Util::GetScriptType(last_prev_char)) {
2778 return false;
2779 }
2780
2781 return true;
2782 }
2783
UpdateTime()2784 void Session::UpdateTime() {
2785 context_->set_last_command_time(Clock::GetTime());
2786 }
2787
TransformInput(commands::Input * input)2788 void Session::TransformInput(commands::Input *input) {
2789 if (input->has_key()) {
2790 context_->key_event_transformer().TransformKeyEvent(input->mutable_key());
2791 }
2792 }
2793
SwitchInputFieldType(commands::Command * command)2794 bool Session::SwitchInputFieldType(commands::Command *command) {
2795 command->mutable_output()->set_consumed(true);
2796 context_->mutable_composer()->SetInputFieldType(
2797 command->input().context().input_field_type());
2798 Output(command);
2799 return true;
2800 }
2801
HandleIndirectImeOnOff(commands::Command * command)2802 bool Session::HandleIndirectImeOnOff(commands::Command *command) {
2803 const commands::KeyEvent &key = command->input().key();
2804 if (!key.has_activated()) {
2805 return true;
2806 }
2807 const ImeContext::State state = context_->state();
2808 if (state == ImeContext::DIRECT && key.activated()) {
2809 // Indirect IME On found.
2810 commands::Command on_command;
2811 on_command.CopyFrom(*command);
2812 if (!IMEOn(&on_command)) {
2813 return false;
2814 }
2815 } else if (state != ImeContext::DIRECT && !key.activated()) {
2816 // Indirect IME Off found.
2817 commands::Command off_command;
2818 off_command.CopyFrom(*command);
2819 if (!IMEOff(&off_command)) {
2820 return false;
2821 }
2822 }
2823 return true;
2824 }
2825
CommitRawText(commands::Command * command)2826 bool Session::CommitRawText(commands::Command *command) {
2827 if (context_->composer().GetLength() == 0) {
2828 return false;
2829 }
2830 CommitRawTextDirectly(command);
2831 return true;
2832 }
2833
2834 // TODO(komatsu): delete this function.
get_internal_composer_only_for_unittest()2835 composer::Composer *Session::get_internal_composer_only_for_unittest() {
2836 return context_->mutable_composer();
2837 }
2838
context() const2839 const ImeContext &Session::context() const {
2840 return *context_;
2841 }
2842
2843 } // namespace session
2844 } // namespace mozc
2845