Skip to content

Instantly share code, notes, and snippets.

@faywong
Last active May 20, 2024 03:21
Show Gist options
  • Save faywong/06fafe09fc76a7d32bd5034e1a8d04bd to your computer and use it in GitHub Desktop.
Save faywong/06fafe09fc76a7d32bd5034e1a8d04bd to your computer and use it in GitHub Desktop.
rime IME 流程简析

抱歉,本文在 Gentoo Linux Rime 输入法下写成,由于缺乏敏捷的截图工具,本文图片较少(UML 图后期再补)。请将就读完。

基础

术语

  • schema -> 输入法方案,比如 朙月拼音、倉頡五代,其 schema id 分别为:luna_pinyin、cangjie5

  • compose -> 选词组句

  • 部署 -> 将用户配置和数据应用到 rime 运行时的过程,用户视角就是新的设置生效。

配置文件组织

用 YAML 格式组织的一个用户配置 + 运行时状态的 k-v 以实现一个轻量弱关系 “数据库”。

  • 〔全局設定〕 default.yaml,主配置文件,类似于 sql 中的主表,起提纲挈领的配置作用。还有 default.custom.yaml 为用户级别配置文件。
  • 〔安装信息〕 installation.yaml,构建信息,安装时间,版本号等
  • 〔用戶运行时状态〕 user.yaml,上一次访问时间,部署时间,上一次选择的输入法方案
  • 输入法方案定义文件:<schema id>.schema.yaml, 它定义了一个输入法方案的各个原子组件应该以怎样的方式初始化并串联成一个管线正常运转(组件之间如何交互),以及对用户的 UI 呈现(方案选单名称、各类 Switches 开关等)。类似于多媒体中的 OpenMax IL 的角色,图形学中渲染管线构建器的角色。 以 luna_pinyin 为例:
# 以下代碼片段節選自 luna_pinyin.schema.yaml

schema:
  schema_id: luna_pinyin
  name: 朙月拼音
  version: "0.9"
  author:
    - 佛振 <[email protected]>
  description: |
    Rime 預設的拼音輸入方案。

schema/schema_id 和 schema/version 作为运行时输入法方案的标识。

数据文件组织

  • <schema_id>.prism.bin,Rime 棱镜,拼写运算规则
  • <schema_id>.table.bin:Rime 固态词典,按音节编码检索词条的索引,这是二进制紧凑格式;它还有个 .txt 后缀的文本格式版本。
  • <schema_id>.reverse.bin:Rime 反查词典,按词条检索编码的索引

平台集成

与系统交互

Linux

以 Linux OS 为例(相对较难的,也比较混乱)

image

这套 XIM 由于 X18n 还有 fontset 等一套玩下来极其繁琐,而 QT、GTK 等 GUI Toolkit 针对输入法嵌入做了较好的封装。各个输入法只需要面向 QT、GTK 框架实现 IM Module 即可(Qt5 开始不再支持 XIM)。 它的基本原理就是 Qt 应用在启动时,根据QT_IM_MODULE 环境变量得到一个输入法名字,然后 Qt 就会去 LD_LIBRARY_PATH 中寻到提供这个名字的 plugin 的so,然后加载它。这个 so 库里提供了一个工厂方法,调用工厂方法可以创建一个 QInputMethod 类的实例,这个类就包含了输入法的各种操作。应用程序需要输入中文时,就会调用 QInputMethod 的方法来获取文字。

QInputMethod的定义如下:(QInputMethod Class) image

输入法的开发者在实现这个类的时候,可以直接查表返回对应中文。

以我此刻码字的 Gentoo Linux 上为例(输入法为 fcitx-rime + KDE/QT 环境):

faywong@gentoo ~ $ ll /usr/lib64/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so
-rwxr-xr-x 1 root root 181K Feb  7 05:07 /usr/lib64/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so
faywong@gentoo ~ $ ll /usr/lib64/qt6/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so
-rwxr-xr-x 1 root root 728K Jan  3 22:51 /usr/lib64/qt6/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so
faywong@gentoo /proc/866 $ cat maps  |grep fcitx
7ffbcc33d000-7ffbcc346000 r--p 00000000 00:1d 9698256                    /usr/lib64/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so
7ffbcc346000-7ffbcc35e000 r-xp 00009000 00:1d 9698256                    /usr/lib64/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so
7ffbcc35e000-7ffbcc368000 r--p 00021000 00:1d 9698256                    /usr/lib64/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so
7ffbcc368000-7ffbcc369000 r--p 0002b000 00:1d 9698256                    /usr/lib64/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so
7ffbcc369000-7ffbcc36a000 rw-p 0002c000 00:1d 9698256                    /usr/lib64/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so
# /etc/skel/.bash_profile

# This file is sourced by bash for login shells.  The following line
# runs your .bashrc and is recommended by the bash info pages.
if [[ -f ~/.bashrc ]] ; then
        . ~/.bashrc
fi
export XMODIFIERS=@im=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export EDITOR=vim

QT/GTK 框架会在运行时通过 xxx_IM_MODULE 这个环境变量的值 "fcitx" 找到对应的插件 so,并加载之,通过如下方法来创建具体的 inputContext 实现:

// ref from https://github.com/fcitx/fcitx-qt5/blob/master/qt5/platforminputcontext/main.cpp
#include "main.h"

QStringList QFcitxPlatformInputContextPlugin::keys() const {
    return QStringList(QStringLiteral("fcitx"));
}

QFcitxPlatformInputContext *
QFcitxPlatformInputContextPlugin::create(const QString &system,
                                         const QStringList &paramList) {
    Q_UNUSED(paramList);
    if (system.compare(system, QStringLiteral("fcitx"), Qt::CaseInsensitive) ==
        0)
        return new QFcitxPlatformInputContext;
    return 0;
}

Android

与 Linux 平台存在 Xlib/xcb/GTK IM Module/QT IM Module 等多条新老机制不同, Android 平台只有一个官方应用框架,也是典型的服务端 - 客户端结构(客户端这里有个应用全局的 IMM,与 fcitx-qt(QT IM Module)里向进程全局的 QCoreApplication 发送 event 类似),相对简单灵活得多,对 createInputView、startInputView 和 createCandidateView 等做了较好的抽象。

image

某个具体的三方输入法服务的生命周期:

image

对于具体的输入法 App 开发者来说,主要关注上图高亮着的几个关键回调函数即可。

启动流程

得益于模块化设计和精致的 api 封装(所以被移植到了非常多的平台,Win/Linux/MacOS 平台的社区开发者比较活跃,不错的插件生态),一个简单的集成并启动 librime 的流程非常短小:

主入口函数:

int main(int argc, char* argv[]) {
  unsigned int codepage = SetConsoleOutputCodePage();
  RimeApi* rime = rime_get_api();

  RIME_STRUCT(RimeTraits, traits);
  traits.app_name = "rime.console";
  rime->setup(&traits);

  rime->set_notification_handler(&on_message, NULL);

  fprintf(stderr, "initializing...\n");
reload:
  rime->initialize(NULL);
  Bool full_check = True;
  if (rime->start_maintenance(full_check))
    rime->join_maintenance_thread();
  fprintf(stderr, "ready.\n");

  RimeSessionId session_id = rime->create_session();
  if (!session_id) {
    fprintf(stderr, "Error creating rime session.\n");
    SetConsoleOutputCodePage(codepage);
    return 1;
  }

  const int kMaxLength = 99;
  char line[kMaxLength + 1] = {0};
  while (fgets(line, kMaxLength, stdin) != NULL) {
    for (char* p = line; *p; ++p) {
      if (*p == '\r' || *p == '\n') {
        *p = '\0';
        break;
      }
    }
    if (!strcmp(line, "exit"))
      break;
    else if (!strcmp(line, "reload")) {
      rime->destroy_session(session_id);
      rime->finalize();
      goto reload;
    }
    if (execute_special_command(line, session_id))
      continue;
    if (rime->simulate_key_sequence(session_id, line)) {
      print(session_id);
    } else {
      fprintf(stderr, "Error processing key sequence: %s\n", line);
    }
  }

  rime->destroy_session(session_id);

  rime->finalize();

  SetConsoleOutputCodePage(codepage);
  return 0;
}

向 rime_api 获取其内部状态:

void print(RimeSessionId session_id) {
  RimeApi* rime = rime_get_api();

  RIME_STRUCT(RimeCommit, commit);
  RIME_STRUCT(RimeStatus, status);
  RIME_STRUCT(RimeContext, context);

  if (rime->get_commit(session_id, &commit)) {
    printf("commit: %s\n", commit.text);
    rime->free_commit(&commit);
  }

  // 这是具体同步查询 rime 内部状态的 API
  if (rime->get_status(session_id, &status)) {
    print_status(&status);
    rime->free_status(&status);
  }

  if (rime->get_context(session_id, &context)) {
    print_context(&context);
    rime->free_context(&context);
  }
}

接收 rime_api 抛出的异步消息(比如 deploy 进展情况):

void on_message(void* context_object,
                RimeSessionId session_id,
                const char* message_type,
                const char* message_value) {
  printf("message: [%lu] [%s] %s\n", session_id, message_type, message_value);
  RimeApi* rime = rime_get_api();
  if (RIME_API_AVAILABLE(rime, get_state_label) &&
      !strcmp(message_type, "option")) {
    Bool state = message_value[0] != '!';
    const char* option_name = message_value + !state;
    const char* state_label =
        rime->get_state_label(session_id, option_name, state);
    if (state_label) {
      printf("updated option: %s = %d // %s\n", option_name, state,
             state_label);
    }
  }
}

继续走读下 rime api 内部主要链路

  1. 注册模块

default 模块组:

RIME_REGISTER_MODULE_GROUP(default, "core", "dict", "gears")

注册了: core_module、dict_module、gears_module、plugins_module

deployer 模块组:

RIME_REGISTER_MODULE_GROUP(deployer, "core", "dict", "levers")

其中比较重要的是:levers_module

  static void rime_levers_initialize() {
    LOG(INFO) << "registering components from module 'levers'.";
    Registry& r = Registry::instance();

    // deployment tools
    r.Register("detect_modifications", new Component<DetectModifications>);
    r.Register("installation_update", new Component<InstallationUpdate>);
    r.Register("workspace_update", new Component<WorkspaceUpdate>);
    r.Register("schema_update", new Component<SchemaUpdate>);
    r.Register("config_file_update", new Component<ConfigFileUpdate>);
    r.Register("prebuild_all_schemas", new Component<PrebuildAllSchemas>);
    r.Register("user_dict_upgrade", new Component<UserDictUpgrade>);
    r.Register("cleanup_trash", new Component<CleanupTrash>);
    r.Register("user_dict_sync", new Component<UserDictSync>);
    r.Register("backup_config_files", new Component<BackupConfigFiles>);
    r.Register("clean_old_log_files", new Component<CleanOldLogFiles>);
  }

其中 Component<SchemaUpdate> 包含输入法方案更新-应用的处理:

   bool SchemaUpdate::Run(Deployer* deployer) {
     if (!fs::exists(source_path_)) {
       LOG(ERROR) << "Error updating schema: nonexistent file '" << source_path_
                  << "'.";
       return false;
     }
     string schema_id;
     the<Config> config(new Config);
     if (!config->LoadFromFile(source_path_) ||
         !config->GetString("schema/schema_id", &schema_id) || schema_id.empty()) {
       LOG(ERROR) << "invalid schema definition in '" << source_path_ << "'.";
       return false;
     }

     the<DeploymentTask> config_file_update(
         new ConfigFileUpdate(schema_id + ".schema.yaml", "schema/version"));
     if (!config_file_update->Run(deployer)) {
       return false;
     }
     // reload compiled config
     config.reset(Config::Require("schema")->Create(schema_id));
     string dict_name;
     if (!config->GetString("translator/dictionary", &dict_name)) {
       // not requiring a dictionary
       return true;
     }
     Schema schema(schema_id, config.release());
     the<Dictionary> dict(
         Dictionary::Require("dictionary")->Create({&schema, "translator"}));
     if (!dict) {
       LOG(ERROR) << "Error creating dictionary '" << dict_name << "'.";
       return false;
     }

     LOG(INFO) << "preparing dictionary '" << dict_name << "'.";
     const path& user_data_path(deployer->user_data_dir);
     if (!MaybeCreateDirectory(deployer->staging_dir)) {
       return false;
     }
     DictCompiler dict_compiler(dict.get());
     if (verbose_) {
       dict_compiler.set_options(DictCompiler::kRebuild | DictCompiler::kDump);
     }
     the<ResourceResolver> resolver(
         Service::instance().CreateDeployedResourceResolver(
             {"compiled_schema", "", ".schema.yaml"}));
     auto compiled_schema = resolver->ResolvePath(schema_id);
     if (!dict_compiler.Compile(compiled_schema)) {
       LOG(ERROR) << "dictionary '" << dict_name << "' failed to compile.";
       return false;
     }
     LOG(INFO) << "dictionary '" << dict_name << "' is ready.";
     return true;
   }
  1. 启动维护线程(maintenance thread),首次读取配置并部署 WorkspaceUpdate(解析 default.yaml ),SchemaUpate(解析 <schema_id>.schema.yaml), 在 SchemaUpdate Run 的过程中,会去加载词典 <schema_id>.table.bin:

          // reload compiled config
          config.reset(Config::Require("schema")->Create(schema_id));
          string dict_name;
          if (!config->GetString("translator/dictionary", &dict_name)) {
            // not requiring a dictionary
            return true;
          }
          Schema schema(schema_id, config.release());
          the<Dictionary> dict(
              Dictionary::Require("dictionary")->Create({&schema, "translator"}));
          if (!dict) {
            LOG(ERROR) << "Error creating dictionary '" << dict_name << "'.";
            return false;
          }

    对应在 luna_pinyin.schema.yaml 中的配置为:

        translator:
          dictionary: luna_pinyin
          preedit_format:
            - "xform/([nljqxy])v/$1ü/"
    Dictionary* DictionaryComponent::Create(string dict_name,
                                      string prism_name,
                                      vector<string> packs) {
      // obtain prism and primary table objects
      auto primary_table = table_map_[dict_name].lock();
      if (!primary_table) {
        auto file_path = table_resource_resolver_->ResolvePath(dict_name);
        table_map_[dict_name] = primary_table = New<Table>(file_path);
      }
      auto prism = prism_map_[prism_name].lock();
      if (!prism) {
        auto file_path = prism_resource_resolver_->ResolvePath(prism_name);
        prism_map_[prism_name] = prism = New<Prism>(file_path);
      }
      vector<of<Table>> tables = {std::move(primary_table)};
      for (const auto& pack : packs) {
        auto table = table_map_[pack].lock();
        if (!table) {
          auto file_path = table_resource_resolver_->ResolvePath(pack);
          table_map_[pack] = table = New<Table>(file_path);
        }
        tables.push_back(std::move(table));
      }
      return new Dictionary(std::move(dict_name), std::move(packs),
                            std::move(tables), std::move(prism));
    }
  2. 创建会话(为一个需要输入法的应用) 其中会触发对输入法引擎的创建

    Session::Session() {
      engine_.reset(Engine::Create());
      engine_->sink().connect(std::bind(&Session::OnCommit, this, _1));
      SessionId session_id = reinterpret_cast<SessionId>(this);
      engine_->message_sink().connect(
          std::bind(&Service::Notify, &Service::instance(), session_id, _1, _2));
    }

    回顾下 luna_pinyin.schema.yaml 里的内容:

    engine:
        filters:
          - "reverse_lookup_filter@cangjie_lookup"
          - "simplifier@zh_simp"
          - "simplifier@zh_tw"
          - uniquifier
        processors:
          - ascii_composer
          - recognizer
          - key_binder
          - speller
          - punctuator
          - selector
          - navigator
          - express_editor
        segmentors:
          - ascii_segmentor
          - matcher
          - abc_segmentor
          - "affix_segmentor@alphabet"
          - "affix_segmentor@cangjie"
          - "affix_segmentor@pinyin"
          - punct_segmentor
          - fallback_segmentor
        translators:
          - punct_translator
          - reverse_lookup_translator
          - script_translator
          - "table_translator@cangjie"
          - "script_translator@pinyin"

    在 Engine 的实例化中,触发对核心四类组件的实例化:

    void ConcreteEngine::ApplySchema(Schema* schema) {
      if (!schema)
        return;
      if (auto switcher = switcher_.lock()) {
        if (Config* user_config = switcher->user_config()) {
          user_config->SetString("var/previously_selected_schema",
                                 schema->schema_id());
          user_config->SetInt("var/schema_access_time/" + schema->schema_id(),
                              time(NULL));
        }
      }
      schema_.reset(schema);
      context_->Clear();
      context_->ClearTransientOptions();
      InitializeComponents();
      InitializeOptions();
      message_sink_("schema", schema->schema_id() + "/" + schema->schema_name());
    }
    
    void ConcreteEngine::InitializeComponents() {
      processors_.clear();
      segmentors_.clear();
      translators_.clear();
      filters_.clear();
    
      if (auto switcher = New<Switcher>(this)) {
        switcher_ = switcher;
        processors_.push_back(switcher);
        if (schema_->schema_id() == ".default") {
          if (Schema* schema = switcher->CreateSchema()) {
            schema_.reset(schema);
          }
        }
      }
    
      Config* config = schema_->config();
      if (!config)
        return;
      // create processors
      if (auto processor_list = config->GetList("engine/processors")) {
        size_t n = processor_list->size();
        for (size_t i = 0; i < n; ++i) {
          auto prescription = As<ConfigValue>(processor_list->GetAt(i));
          if (!prescription)
            continue;
          Ticket ticket{this, "processor", prescription->str()};
          if (auto c = Processor::Require(ticket.klass)) {
            an<Processor> p(c->Create(ticket));
            processors_.push_back(p);
          } else {
            LOG(ERROR) << "error creating processor: '" << ticket.klass << "'";
          }
        }
      }
      // create segmentors
      if (auto segmentor_list = config->GetList("engine/segmentors")) {
        size_t n = segmentor_list->size();
        for (size_t i = 0; i < n; ++i) {
          auto prescription = As<ConfigValue>(segmentor_list->GetAt(i));
          if (!prescription)
            continue;
          Ticket ticket{this, "segmentor", prescription->str()};
          if (auto c = Segmentor::Require(ticket.klass)) {
            an<Segmentor> s(c->Create(ticket));
            segmentors_.push_back(s);
          } else {
            LOG(ERROR) << "error creating segmentor: '" << ticket.klass << "'";
          }
        }
      }
      // create translators
      if (auto translator_list = config->GetList("engine/translators")) {
        size_t n = translator_list->size();
        for (size_t i = 0; i < n; ++i) {
          auto prescription = As<ConfigValue>(translator_list->GetAt(i));
          if (!prescription)
            continue;
          Ticket ticket{this, "translator", prescription->str()};
          if (auto c = Translator::Require(ticket.klass)) {
            an<Translator> t(c->Create(ticket));
            translators_.push_back(t);
          } else {
            LOG(ERROR) << "error creating translator: '" << ticket.klass << "'";
          }
        }
      }
      // create filters
      if (auto filter_list = config->GetList("engine/filters")) {
        size_t n = filter_list->size();
        for (size_t i = 0; i < n; ++i) {
          auto prescription = As<ConfigValue>(filter_list->GetAt(i));
          if (!prescription)
            continue;
          Ticket ticket{this, "filter", prescription->str()};
          if (auto c = Filter::Require(ticket.klass)) {
            an<Filter> f(c->Create(ticket));
            filters_.push_back(f);
          } else {
            LOG(ERROR) << "error creating filter: '" << ticket.klass << "'";
          }
        }
      }
      // create formatters
      if (auto c = Formatter::Require("shape_formatter")) {
        an<Formatter> f(c->Create(Ticket(this)));
        formatters_.push_back(f);
      } else {
        LOG(WARNING) << "shape_formatter not available.";
      }
      // create post-processors
      if (auto c = Processor::Require("shape_processor")) {
        an<Processor> p(c->Create(Ticket(this)));
        post_processors_.push_back(p);
      } else {
        LOG(WARNING) << "shape_processor not available.";
      }
    }

    可以看出实例间的对应关系,启动一个应用实例 -> 一个 Rime 会话实例 -> 一个引擎实例 -> 多个输入法 schema 配置(由 service host 他们的配置)-> 用户通过 switcher 切换输入法的时候 -> 引擎应用新的输入法 schema 配置 -> 引擎重新创建各个 components。是一个配置动态从文件系统同步到运行时,配置 + 用户选项驱动引擎实例化对应的 component 来完成输入法功能的过程。

  3. 模拟(批量行缓冲)输入(以 rime_api_console 为例)

    RIME_API Bool RimeSimulateKeySequence(RimeSessionId session_id,
                                          const char* key_sequence) {
      LOG(INFO) << "simulate key sequence: " << key_sequence;
      an<Session> session(Service::instance().GetSession(session_id));
      if (!session)
        return False;
      KeySequence keys;
      if (!keys.Parse(key_sequence)) {
        LOG(ERROR) << "error parsing input: '" << key_sequence << "'";
        return False;
      }
      for (const KeyEvent& key : keys) {
        session->ProcessKey(key);
      }
      return True;
    }
    
    bool Session::ProcessKey(const KeyEvent& key_event) {
      return engine_->ProcessKey(key_event);
    }

    其中核心的是 engine_->ProcessKey()

输入法方案运行

Rime 的核心原理是通过 enagine 下的 4 大组件对用户输入进行处理,4 大组件分别是:

  • Processors(Switcher、AsciiComposer、Recognizer、KeyBinder、Speller 等)
  • Segmentors
  • Translators
  • Filters

整个流程为:

  1. Processors 下的各个 processor 对用户的输入(即按下键盘的哪一个键)依次进行处理,将按键按照预设的规则对按键进行响应
  2. 不处理:Rime 不对该按键做任何响应,使用系统默认操作,特殊操作:比如 Enter 上屏,�切换输入方案、组合键等
  3. 输入候选:该按键是需要转换为文字的按键,比如 123abc,将该按键字符存入【输入码】上下文
  4. 当【输入码】上下文改变时,Segmentors 下的 segmentor 会将当前输入码根据格式分段,各自打上标签。比如【朙月拼音】中,输入码 2012nian\,划分为三个编码段:2012(贴 number 标签)、nian(贴 abc 标签)、\(贴 punct 标签)。
  5. 顾名思义,Translators 完成由编码到文字的翻译。但有几个要点:
    • 翻译的对象是划分好的一个代码段。
    • 某个 translator 组件往往只翻译具有特定标签的代码段。
    • 翻译的结果可能有多条,每条结果成为一个展现给用户的候选项。
    • 代码段可由几种 translator 分别翻译、翻译结果按一定规则合并成一列候选。
    • 候选项所对应的编码未必是整个代码段。用拼音敲一个词组时,词组后面继续列出单字候选,即是此例。
  6. 翻译完成后,由 Filters 对所有翻译结果进行处理,比如去重

常用 Processors:

ascii_composer:处理英文模式及中英文切换
recognizer:与 matcher 搭配,处理符合特定规则的输入码,如网址,反查等
key_binder:在特定条件下将按键绑定到其他按键,如重定义逗号,句号为预设翻页,开关快捷键等
处理某些自定义的组合键
speller:拼写处理器,接受字符按键,编辑输入
处理自定义的键,通常为 26 个英文字母
punctuator:句读处理器,将个别字符按键直接映射为标点符号或文字
处理句读键、数字键
selector:选字处理器
处理数字键、上下方向键、PageUp、PageDown
navigator:处理输入栏内的光标移动
处理左右方向键、Home、End
express_editor:编辑器
处理空格、回车、回退键

常见 Segmentors

ascii_segmentor:标识英文段落〔例如在英文模式下〕字母直接上屛
matcher:配合 recognizer 标识符合特定规则的段落,如网址,反查等,加上特定 tag
abc_segmentor:标识常规的文字段落,加上 abc 这个默认tag
punct_segmentor:标识句读段落〔键入标点符号用〕加上 punct 这个tag
fallback_segmentor:标识其他未标识段落
affix_segmentor:用户自定义 tag
可加载多个实例,后接 @tag名

常见 Translators:

table_translator:编码表翻译器,用于仓颉,五笔等基于编码表的输入方案
可加载多个实例,后接 @翻译器名〔如:cangjie,wubi等〕
script_translator:脚本翻译器,用于拼音,粤拼等基于音节表的输入方案
可加载多个实例,后接 @翻译器名〔如:pinyin,jyutping等〕
punct_translator:配合 punct_segmentor 转换标点符号
echo_translator:没有其他候选字时,显示输入码〔输入码可以 Shift+Enter 上屛〕

常见 Filters:

simplifier:简繁转换,表情转换等
可加载多个实例,后接 @d转化器名〔如:zh_simp、emoji_suggestion 等〕
uniquifier:过滤重复的候选字,有可能来自 simplifier
reverse_lookup_filter:反查滤镜,以更灵活的方式反查,Rime1.0 后替代 reverse_lookup_translator
可加载多个实例,后接 @滤镜名〔如:pinyin_lookup,jyutping_lookup 等〕
charset_filter 字符集过滤
后接 @字符集名〔如:utf-8(无过滤),big5,big5hkscs,gbk,gb2312〕
lua_filter:使用 lua 自定义过滤,例如过滤字符集,调整排序,后接 @lua函数名
lua函数名即用户文件夹内rime.lua中函数名,参数为(input, env)
可以env.engine.context:get_option(“option_name”)方式绑定到switch开关/key_binder快捷键
仅 script_translator:

cjk_minifier:字符集过滤,使之支持 extended_charset 开关
仅 table_translator:

single_char_filter:单字过滤器,如加载此组件,则屛敝词典中的词组

朙月拼音输入法下关键组件处理逻辑:

Switcher 主管各种全局功能的开关切换,所以优先级最高:

ProcessResult Switcher::ProcessKeyEvent(const KeyEvent& key_event) {
  for (const KeyEvent& hotkey : hotkeys_) {
    if (key_event == hotkey) {
      if (!active_ && engine_) {
        Activate();
      } else if (active_) {
        HighlightNextSchema();
      }
      return kAccepted;
    }
  }
  if (active_) {
    for (auto& p : processors_) {
      ProcessResult result = p->ProcessKeyEvent(key_event);
      if (result != kNoop) {
        return result;
      }
    }
    if (key_event.release() || key_event.ctrl() || key_event.alt()) {
      return kAccepted;
    }
    int ch = key_event.keycode();
    if (ch == XK_space || ch == XK_Return) {
      context_->ConfirmCurrentSelection();
    } else if (ch == XK_Escape) {
      Deactivate();
    }
    return kAccepted;
  }
  return kNoop;
}

AsciiComposer、Recognizer、KeyBinder、Speller、

Speller 之后,便到了最底下的拼音 -> 汉字查表:

rime::Dictionary::Lookup(rime::SyllableGraph const&, unsigned long, double) (/home/faywong/repo/librime/src/rime/dict/dictionary.cc:231)
rime::ScriptTranslation::Evaluate(rime::Dictionary*, rime::UserDictionary*) (/home/faywong/repo/librime/src/rime/gear/script_translator.cc:347)
rime::ScriptTranslator::Query(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, rime::Segment const&) (/home/faywong/repo/librime/src/rime/gear/script_translator.cc:195)
rime::ConcreteEngine::TranslateSegments(rime::Segmentation*) (/home/faywong/repo/librime/src/rime/engine.cc:212)
rime::ConcreteEngine::Compose(rime::Context*) (/home/faywong/repo/librime/src/rime/engine.cc:164)
rime::ConcreteEngine::OnContextUpdate(rime::Context*) (/home/faywong/repo/librime/src/rime/engine.cc:124)
rime::ConcreteEngine::ConcreteEngine()::$_2::operator()(rime::Context*) const (/home/faywong/repo/librime/src/rime/engine.cc:79)
boost::detail::function::void_function_obj_invoker1<rime::ConcreteEngine::ConcreteEngine()::$_2, void, rime::Context*>::invoke(boost::detail::function::function_buffer&, rime::Context*) (/usr/include/boost/function/function_template.hpp:158)
boost::function1<void, rime::Context*>::operator()(rime::Context*) const (/usr/include/boost/function/function_template.hpp:771)
boost::signals2::detail::void_type boost::signals2::detail::call_with_tuple_args<boost::signals2::detail::void_type>::m_invoke<boost::function<void (rime::Context*)>, 0u, rime::Context*&>(boost::function<void (rime::Context*)>&, boost::signals2::detail::unsigned_meta_array<0u>, std::tuple<rime::Context*&> const&, boost::enable_if<boost::is_void<boost::function<void (rime::Context*)>::result_type>, void>::type*) const (/usr/include/boost/signals2/detail/variadic_slot_invoker.hpp:105)
boost::signals2::detail::void_type boost::signals2::detail::call_with_tuple_args<boost::signals2::detail::void_type>::operator()<boost::function<void (rime::Context*)>, rime::Context*&, 1ul>(boost::function<void (rime::Context*)>&, std::tuple<rime::Context*&> const&, mpl_::size_t<1ul>) const (/usr/include/boost/signals2/detail/variadic_slot_invoker.hpp:90)
boost::signals2::detail::void_type boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>::operator()<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>(boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>> const&) const (/usr/include/boost/signals2/detail/variadic_slot_invoker.hpp:133)
boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>::dereference() const (/usr/include/boost/signals2/detail/slot_call_iterator.hpp:110)
boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>::reference boost::iterators::iterator_core_access::dereference<boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>(boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>> const&) (/usr/include/boost/iterator/iterator_facade.hpp:631)
boost::iterators::detail::iterator_facade_base<boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>, boost::signals2::detail::void_type, boost::iterators::single_pass_traversal_tag, boost::signals2::detail::void_type const&, long, false, false>::operator*() const (/usr/include/boost/iterator/iterator_facade.hpp:737)
void boost::signals2::optional_last_value<void>::operator()<boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>(boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>) const (/usr/include/boost/signals2/optional_last_value.hpp:57)
void boost::signals2::detail::combiner_invoker<void>::operator()<boost::signals2::optional_last_value<void>, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>(boost::signals2::optional_last_value<void>&, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>) const (/usr/include/boost/signals2/detail/result_type_wrapper.hpp:64)
boost::signals2::detail::signal_impl<void (rime::Context*), boost::signals2::optional_last_value<void>, int, std::less<int>, boost::function<void (rime::Context*)>, boost::function<void (boost::signals2::connection const&, rime::Context*)>, boost::signals2::mutex>::operator()(rime::Context*) (/usr/include/boost/signals2/detail/signal_template.hpp:242)
boost::signals2::signal<void (rime::Context*), boost::signals2::optional_last_value<void>, int, std::less<int>, boost::function<void (rime::Context*)>, boost::function<void (boost::signals2::connection const&, rime::Context*)>, boost::signals2::mutex>::operator()(rime::Context*) (/usr/include/boost/signals2/detail/signal_template.hpp:722)
rime::Context::PushInput(char) (/home/faywong/repo/librime/src/rime/context.cc:72)
rime::Speller::ProcessKeyEvent(rime::KeyEvent const&) (/home/faywong/repo/librime/src/rime/gear/speller.cc:123)
rime::ConcreteEngine::ProcessKey(rime::KeyEvent const&) (/home/faywong/repo/librime/src/rime/engine.cc:100)
rime::Session::ProcessKey(rime::KeyEvent const&) (/home/faywong/repo/librime/src/rime/service.cc:26)
::RimeSimulateKeySequence(RimeSessionId, const char *) (/home/faywong/repo/librime/src/rime_api.cc:788)
main (/home/faywong/repo/librime/tools/rime_api_console.cc:230)
___lldb_unnamed_symbol3204 (@___lldb_unnamed_symbol3204:26)
__libc_start_main (@__libc_start_main:43)
_start (@_start:14)

speller 处理完,pushInput -> OnContextUpdate -> 分段:

rime::Matcher::Proceed(rime::Segmentation*) (/home/faywong/repo/librime/src/rime/gear/matcher.cc:24)
rime::ConcreteEngine::CalculateSegmentation(rime::Segmentation*) (/home/faywong/repo/librime/src/rime/engine.cc:178)
rime::ConcreteEngine::Compose(rime::Context*) (/home/faywong/repo/librime/src/rime/engine.cc:163)
rime::ConcreteEngine::OnContextUpdate(rime::Context*) (/home/faywong/repo/librime/src/rime/engine.cc:124)
rime::ConcreteEngine::ConcreteEngine()::$_2::operator()(rime::Context*) const (/home/faywong/repo/librime/src/rime/engine.cc:79)
boost::detail::function::void_function_obj_invoker1<rime::ConcreteEngine::ConcreteEngine()::$_2, void, rime::Context*>::invoke(boost::detail::function::function_buffer&, rime::Context*) (/usr/include/boost/function/function_template.hpp:158)
boost::function1<void, rime::Context*>::operator()(rime::Context*) const (/usr/include/boost/function/function_template.hpp:771)
boost::signals2::detail::void_type boost::signals2::detail::call_with_tuple_args<boost::signals2::detail::void_type>::m_invoke<boost::function<void (rime::Context*)>, 0u, rime::Context*&>(boost::function<void (rime::Context*)>&, boost::signals2::detail::unsigned_meta_array<0u>, std::tuple<rime::Context*&> const&, boost::enable_if<boost::is_void<boost::function<void (rime::Context*)>::result_type>, void>::type*) const (/usr/include/boost/signals2/detail/variadic_slot_invoker.hpp:105)
boost::signals2::detail::void_type boost::signals2::detail::call_with_tuple_args<boost::signals2::detail::void_type>::operator()<boost::function<void (rime::Context*)>, rime::Context*&, 1ul>(boost::function<void (rime::Context*)>&, std::tuple<rime::Context*&> const&, mpl_::size_t<1ul>) const (/usr/include/boost/signals2/detail/variadic_slot_invoker.hpp:90)
boost::signals2::detail::void_type boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>::operator()<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>(boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>> const&) const (/usr/include/boost/signals2/detail/variadic_slot_invoker.hpp:133)
boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>::dereference() const (/usr/include/boost/signals2/detail/slot_call_iterator.hpp:110)
boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>::reference boost::iterators::iterator_core_access::dereference<boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>(boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>> const&) (/usr/include/boost/iterator/iterator_facade.hpp:631)
boost::iterators::detail::iterator_facade_base<boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>, boost::signals2::detail::void_type, boost::iterators::single_pass_traversal_tag, boost::signals2::detail::void_type const&, long, false, false>::operator*() const (/usr/include/boost/iterator/iterator_facade.hpp:737)
void boost::signals2::optional_last_value<void>::operator()<boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>(boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>) const (/usr/include/boost/signals2/optional_last_value.hpp:57)
void boost::signals2::detail::combiner_invoker<void>::operator()<boost::signals2::optional_last_value<void>, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>(boost::signals2::optional_last_value<void>&, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::variadic_slot_invoker<boost::signals2::detail::void_type, rime::Context*>, std::_List_iterator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>>, boost::signals2::detail::connection_body<std::pair<boost::signals2::detail::slot_meta_group, boost::optional<int>>, boost::signals2::slot<void (rime::Context*), boost::function<void (rime::Context*)>>, boost::signals2::mutex>>) const (/usr/include/boost/signals2/detail/result_type_wrapper.hpp:64)
boost::signals2::detail::signal_impl<void (rime::Context*), boost::signals2::optional_last_value<void>, int, std::less<int>, boost::function<void (rime::Context*)>, boost::function<void (boost::signals2::connection const&, rime::Context*)>, boost::signals2::mutex>::operator()(rime::Context*) (/usr/include/boost/signals2/detail/signal_template.hpp:242)
boost::signals2::signal<void (rime::Context*), boost::signals2::optional_last_value<void>, int, std::less<int>, boost::function<void (rime::Context*)>, boost::function<void (boost::signals2::connection const&, rime::Context*)>, boost::signals2::mutex>::operator()(rime::Context*) (/usr/include/boost/signals2/detail/signal_template.hpp:722)
rime::Context::PushInput(char) (/home/faywong/repo/librime/src/rime/context.cc:72)
rime::Speller::ProcessKeyEvent(rime::KeyEvent const&) (/home/faywong/repo/librime/src/rime/gear/speller.cc:123)
rime::ConcreteEngine::ProcessKey(rime::KeyEvent const&) (/home/faywong/repo/librime/src/rime/engine.cc:100)
initializing...
message: [0] [deploy] start
message: [0] [deploy] success
ready.
pinyin
schema: luna_pinyin / 朙月拼音
status: composing 
[pin yin]|
page: 1  (of size 5)
1. [拼音]
2.  品 
3.  拼 
4.  浜 
5.  頻 
1
commit: 拼音
schema: luna_pinyin / 朙月拼音
status: 
(not composing)

Rime 输入法全生命周期:

image

如何扩展 Rime(以 Google 中->英翻译为例)

先附上改动:

--- a/rime_ice.schema.yaml
+++ b/rime_ice.schema.yaml
@@ -50,6 +50,9 @@ engine:
     - selector
     - navigator
     - express_editor
+    - lua_processor@c2e_processor
+    - lua_processor@e2c_processor
+    - lua_processor@google_processor
   segmentors:
     - ascii_segmentor
     - matcher
@@ -65,6 +68,9 @@ engine:
     - table_translator@custom_phrase    # 自定义短语 custom_phrase.txt
     - table_translator@melt_eng         # 英文输入
     - table_translator@cn_en            # 中英混合词汇
+    - lua_translator@c2e_translator
+    - lua_translator@e2c_translator
+    - lua_translator@google_translator
     - table_translator@radical_lookup   # 部件拆字反查
     - lua_translator@unicode            # Unicode
     - lua_translator@number_translator  # 数字、金额大写
diff --git a/rime.lua b/rime.lua
index aadf369..8559856 100644
--- a/rime.lua
+++ b/rime.lua
@@ -95,3 +95,12 @@ function debug_checker(input, env)
         ))
     end
 end
+
+-- Google 翻译
+local c2e = require("c2etrigger")("Control+t", require("c2e"))
+c2e_translator = c2e.translator
+c2e_processor = c2e.processor
+
+local e2c = require("e2ctrigger")("Control+e", require("e2c"))
+e2c_translator = e2c.translator
+e2c_processor = e2c.processor
diff --git a/lua/c2etrigger.lua b/lua/c2etrigger.lua
new file mode 100644
index 0000000..d0ce812
--- /dev/null
+++ b/lua/c2etrigger.lua
@@ -0,0 +1,38 @@
+local function make(trig_key, trig_translator)
+   local flag = false
+   local focus_text = nil
+
+
+   local function processor(key, env)
+       local kAccepted = 1
+       local kNoop = 2
+       local engine = env.engine
+       local context = engine.context
+       
+       if key:repr() == trig_key then
+           if context:is_composing() then
+               focus_text = context:get_commit_text()
+               flag = true
+               context:refresh_non_confirmed_composition()
+               return kAccepted
+           end
+       end
+
+       return kNoop
+   end
+
+   local function translator(input, seg, env)
+       if flag then
+           flag = false
+           env.focus_text = focus_text
+           trig_translator(input, seg, env)
+       end
+   end
+
+   return {
+       processor = processor,
+       translator = translator
+   }
+end
+
+return make
diff --git a/lua/c2e.lua b/lua/c2e.lua
new file mode 100644
index 0000000..073b3c6
--- /dev/null
+++ b/lua/c2e.lua
@@ -0,0 +1,32 @@
+local json = require("json")
+local http = require("simplehttp")
+
+local function escape(s)
+   return (string.gsub(s, "([^A-Za-z0-9_])", function(c)
+       return string.format("%%%02x", string.byte(c))
+   end))
+end
+
+local function make_url(input)
+   local sl = "zh_TW"
+   local tl = "en"
+   return 'http://translate.googleapis.com/translate_a/single?client=gtx&sl='.. sl ..'&tl='.. tl ..'&dt=t&q='.. escape(input)
+end
+
+
+
+local function translator(input, seg, env)
+   local string = env.focus_text
+   local reply = http.request(make_url(string))
+   local data = json.decode(reply)
+
+   for i, v in ipairs(data) do
+      -- get the output string
+      local output = v[1][1]
+      local c = Candidate("translate", seg.start, seg._end, output, "翻譯")
+      -- add to Candidate
+      yield(c)
+   end
+end
+
+return translator

核心是 processor 函数和 translator 函数:

  1. processor 函数中,如果按下的是 trigger key(ctrl + T),则把 commit text(即用户选中的词句)存下来。
+   local function processor(key, env)
+       local kAccepted = 1
+       local kNoop = 2
+       local engine = env.engine
+       local context = engine.context
+
+       if key:repr() == trig_key then
+           if context:is_composing() then
+               focus_text = context:get_commit_text()
+               flag = true
+               context:refresh_non_confirmed_composition()
+               return kAccepted
+           end
+       end
+
+       return kNoop
+   end
  1. translator 函数中,获取刚刚的用户选中的词句,去查询 google 翻译 API,然后将翻译结果插入到候选词中。
+local function translator(input, seg, env)
+   local string = env.focus_text
+   local reply = http.request(make_url(string))
+   local data = json.decode(reply)
+
+   for i, v in ipairs(data) do
+      -- get the output string
+      local output = v[1][1]
+      local c = Candidate("translate", seg.start, seg._end, output, "翻譯")
+      -- add to Candidate
+      yield(c)
+   end
+end
+

image

多会话支持

service -> createSession(具体的 ConcreteEngine 实例及其管理的四大组件们) -> rime api(session level) -> rime app

由以上对象件的关系可以看出,service 是进程全局的,rime app 里对于 session 的选择和组织是自由的。一般默认创建一个 session,再透过 api 去 select & apply schema(即切输入法)。

但从其 api 都带一个 session id 的设计上来看,也可以一个 app 多个 session。这样对于 app 的开发来说无疑会更加灵活。比如解决切输入法的性能消耗问题。

附录:

  1. 有关 rime-lua 扩展机制的设计,可以参看 librime-lua 项目源码

  2. 鸿蒙系统上 IME 服务的接入可参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/08_u8f93_u5165_u6cd5_u5f00_u53d1_u670d_u52a1_uff09-0000001774280218-V5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment