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