diff --git a/plugin/commands/fanhuaji_convert.py b/plugin/commands/fanhuaji_convert.py index 811ca08..329d518 100644 --- a/plugin/commands/fanhuaji_convert.py +++ b/plugin/commands/fanhuaji_convert.py @@ -1,54 +1,34 @@ -from ..constant import HTTP_HEADERS from ..constant import TEXT_DELIMITER +from ..fanhuaji import Fanhuaji from ..functions import prepare_fanhuaji_convert_args -from ..libs import requests -from ..log import msg, print_msg -from ..settings import get_setting -from typing import Dict +from ..log import msg +from typing import Dict, Optional import sublime import sublime_plugin class FanhuajiConvertCommand(sublime_plugin.TextCommand): def is_enabled(self) -> bool: - return sum(map(len, self.view.sel())) > 0 + return self.view.has_non_empty_selection_region() def is_visible(self) -> bool: return self.is_enabled() - def run(self, edit: sublime.Edit, args: Dict = {}) -> None: - real_args = prepare_fanhuaji_convert_args(self.view) - real_args.update(args) + def run(self, edit: sublime.Edit, args: Optional[Dict] = None) -> None: + args = prepare_fanhuaji_convert_args(self.view, args) try: - result = self._do_api_convert(real_args) - except requests.exceptions.ConnectionError as e: + result = Fanhuaji.convert(args) + except Exception as e: sublime.error_message(msg(f"Failed to reach the server: {e}")) return - except requests.exceptions.RequestException as e: - sublime.error_message(msg(f"Request exception: {e}")) - return - except ValueError as e: - sublime.error_message(msg(f"Failed to decode the returned JSON: {e}")) - return if int(result["code"]) != 0: sublime.error_message(msg(f'Error message from the server: {result["msg"]}')) return texts = result["data"]["text"].split(TEXT_DELIMITER) - blocks = tuple({"region": z[0], "text": z[1]} for z in zip(self.view.sel(), texts)) - - for block in reversed(blocks): - self.view.replace(edit, block["region"], block["text"]) - - def _do_api_convert(self, args: Dict) -> Dict: - if get_setting("debug"): - print_msg(f"Request with: {args}") - - url = get_setting("api_server") + "/convert" - verify_ssl = bool(get_setting("ssl_cert_verification")) - - response = requests.post(url=url, data=args, headers=HTTP_HEADERS, verify=verify_ssl) + region_text_pairs = tuple(zip(self.view.sel(), texts)) - return sublime.decode_value(response.text) + for region, text in reversed(region_text_pairs): + self.view.replace(edit, region, text) diff --git a/plugin/commands/fanhuaji_convert_panel.py b/plugin/commands/fanhuaji_convert_panel.py index f81427e..1189a9d 100644 --- a/plugin/commands/fanhuaji_convert_panel.py +++ b/plugin/commands/fanhuaji_convert_panel.py @@ -1,31 +1,28 @@ -from ..settings import get_converters_info -from ..settings import get_converter_info -from typing import Tuple, Union +from ..fanhuaji import Fanhuaji import sublime import sublime_plugin class FanhuajiConvertPanelCommand(sublime_plugin.WindowCommand): def run(self) -> None: - trigger_format = "{name_eng} - {name_chi}" - - items: Tuple[Union[str, sublime.QuickPanelItem], ...] = tuple( - sublime.QuickPanelItem( - trigger=trigger_format.format_map(converter), - # details=converter["detail"], - annotation=converter["desc"], - kind=converter["st_kind"], - ) - for converter in get_converters_info() + self.window.show_quick_panel( + tuple( + sublime.QuickPanelItem( + trigger="{name_eng} - {name_chi}".format_map(converter), + annotation=converter["annotation"], + details=converter["details"], + kind=converter["st_kind"], + ) + for converter in Fanhuaji.converters + ), + self.on_done, ) - self.window.show_quick_panel(items, self.on_done) - def on_done(self, index: int) -> None: if index == -1: return - converter = get_converter_info(index) + converter = Fanhuaji.converters[index] self.window.run_command( "fanhuaji_convert", diff --git a/plugin/constant.py b/plugin/constant.py index a407cf7..a3c48e4 100644 --- a/plugin/constant.py +++ b/plugin/constant.py @@ -1,5 +1,3 @@ -from .types import TD_ConverterInfo -from typing import Tuple import sublime import sys @@ -17,90 +15,3 @@ # so we could convert multiple text with only a single API call. # This delimiter should be a extremely rarely used string. TEXT_DELIMITER = r"\n\5\9\8\n" - -HTTP_HEADERS = { - "user-agent": f"Sublime Text {ST_VERSION} Fanhuaji", -} - -CONVERTERS_INFO: Tuple[TD_ConverterInfo, ...] = ( - { - "name_api": "Simplified", - "name_eng": "Simplified", - "name_chi": "简体化", - "desc": "将文字转换为简体。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "简", ""), - }, - { - "name_api": "Traditional", - "name_eng": "Traditional", - "name_chi": "繁體化", - "desc": "將文字轉換為繁體。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "繁", ""), - }, - { - "name_api": "China", - "name_eng": "China Localization", - "name_chi": "中国化", - "desc": "将文字转换为简体,并使用中国地区的词语修正。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "中", ""), - }, - { - "name_api": "Hongkong", - "name_eng": "Hongkong Localization", - "name_chi": "香港化", - "desc": "將文字轉換為繁體,並使用香港地區的詞語修正。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "港", ""), - }, - { - "name_api": "Taiwan", - "name_eng": "Taiwan Localization", - "name_chi": "台灣化", - "desc": "將文字轉換為繁體,並使用台灣地區的詞語修正。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "台", ""), - }, - { - "name_api": "Pinyin", - "name_eng": "Pinyin", - "name_chi": "拼音化", - "desc": "將文字轉為拼音。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "拼", ""), - }, - { - "name_api": "Bopomofo", - "name_eng": "Bopomofo", - "name_chi": "注音化", - "desc": "將文字轉為注音。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "注", ""), - }, - { - "name_api": "Mars", - "name_eng": "Mars", - "name_chi": "火星化", - "desc": "將文字轉換為繁體火星文。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "火", ""), - }, - { - "name_api": "WikiSimplified", - "name_eng": "Simplified (Wikipeida)", - "name_chi": "维基简体化", - "desc": "只使用维基百科的词库将文字转换为简体。", - "detail": "一般而言,你应该用不到这个模式。", - "st_kind": (sublime.KIND_ID_AMBIGUOUS, "简", ""), - }, - { - "name_api": "WikiTraditional", - "name_eng": "Traditional (Wikipeida)", - "name_chi": "維基繁體化", - "desc": "只使用維基百科的詞庫將文字轉換為繁體。", - "detail": "一般而言,你應該用不到這個模式。", - "st_kind": (sublime.KIND_ID_AMBIGUOUS, "繁", ""), - }, -) diff --git a/plugin/fanhuaji.py b/plugin/fanhuaji.py new file mode 100644 index 0000000..9dee5b0 --- /dev/null +++ b/plugin/fanhuaji.py @@ -0,0 +1,113 @@ +from .constant import ST_VERSION, ST_PLATFORM_ARCH +from .libs import requests +from .log import print_msg +from .settings import get_setting +from .types import TD_ApiConvertResponse, TD_ConverterInfo +from typing import Any, Dict, Tuple +import sublime + +HTTP_HEADERS = { + "user-agent": f"Sublime Text {ST_VERSION} ({ST_PLATFORM_ARCH}) Fanhuaji", +} + + +class Fanhuaji: + converters: Tuple[TD_ConverterInfo, ...] = ( + { + "name_api": "Simplified", + "name_eng": "Simplified", + "name_chi": "简体化", + "details": "将文字转换为简体。", + "annotation": "", + "st_kind": (sublime.KIND_ID_COLOR_ORANGISH, "简", ""), + }, + { + "name_api": "Traditional", + "name_eng": "Traditional", + "name_chi": "繁體化", + "details": "將文字轉換為繁體。", + "annotation": "", + "st_kind": (sublime.KIND_ID_COLOR_ORANGISH, "繁", ""), + }, + { + "name_api": "China", + "name_eng": "China Localization", + "name_chi": "中国化", + "details": "将文字转换为简体,并使用中国地区的词语修正。", + "annotation": "", + "st_kind": (sublime.KIND_ID_COLOR_CYANISH, "中", ""), + }, + { + "name_api": "Hongkong", + "name_eng": "Hongkong Localization", + "name_chi": "香港化", + "details": "將文字轉換為繁體,並使用香港地區的詞語修正。", + "annotation": "", + "st_kind": (sublime.KIND_ID_COLOR_CYANISH, "港", ""), + }, + { + "name_api": "Taiwan", + "name_eng": "Taiwan Localization", + "name_chi": "台灣化", + "details": "將文字轉換為繁體,並使用台灣地區的詞語修正。", + "annotation": "", + "st_kind": (sublime.KIND_ID_COLOR_CYANISH, "台", ""), + }, + { + "name_api": "Pinyin", + "name_eng": "Pinyin", + "name_chi": "拼音化", + "details": "將文字轉為拼音。", + "annotation": "", + "st_kind": (sublime.KIND_ID_COLOR_GREENISH, "拼", ""), + }, + { + "name_api": "Bopomofo", + "name_eng": "Bopomofo", + "name_chi": "注音化", + "details": "將文字轉為注音。", + "annotation": "", + "st_kind": (sublime.KIND_ID_COLOR_GREENISH, "注", ""), + }, + { + "name_api": "Mars", + "name_eng": "Mars", + "name_chi": "火星化", + "details": "將文字轉換為繁體火星文。", + "annotation": "", + "st_kind": (sublime.KIND_ID_COLOR_GREENISH, "火", ""), + }, + { + "name_api": "WikiSimplified", + "name_eng": "Simplified (Wikipeida)", + "name_chi": "维基简体化", + "details": "只使用维基百科的词库将文字转换为简体。", + "annotation": "(少用)", + "st_kind": (sublime.KIND_ID_COLOR_LIGHT, "简", ""), + }, + { + "name_api": "WikiTraditional", + "name_eng": "Traditional (Wikipeida)", + "name_chi": "維基繁體化", + "details": "只使用維基百科的詞庫將文字轉換為繁體。", + "annotation": "(少用)", + "st_kind": (sublime.KIND_ID_COLOR_LIGHT, "繁", ""), + }, + ) + + @classmethod + def convert(cls, args: Dict[str, Any]) -> TD_ApiConvertResponse: + if get_setting("debug"): + print_msg(f"Request {args = }") + + url = get_setting("api_server") + "/convert" + verify_ssl = bool(get_setting("ssl_cert_verification")) + + try: + response = requests.post(url, data=args, headers=HTTP_HEADERS, verify=verify_ssl) + except requests.exceptions.ConnectionError as e: + raise RuntimeError(f"Failed to reach the server: {e}") + except requests.exceptions.RequestException as e: + raise RuntimeError(f"Request exception: {e}") + + return sublime.decode_value(response.text) diff --git a/plugin/functions.py b/plugin/functions.py index 3d7567f..b27d9d1 100644 --- a/plugin/functions.py +++ b/plugin/functions.py @@ -1,42 +1,36 @@ from .constant import TEXT_DELIMITER -from .constant import CONVERTERS_INFO from .settings import get_setting -from .types import TD_ConverterInfo -from functools import lru_cache -from typing import Any, Dict +from typing import Any, Dict, Optional import sublime -@lru_cache() -def get_converter_info(index: int) -> TD_ConverterInfo: - return CONVERTERS_INFO[index] - - -def prepare_fanhuaji_convert_args(view: sublime.View) -> Dict[str, Any]: - args: Dict[str, Any] = get_setting("convert_params") +def prepare_fanhuaji_convert_args(view: sublime.View, args: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + _args: Dict[str, Any] = get_setting("convert_params") # 轉換模組 - if "modules" in args and isinstance(args["modules"], dict): - args["modules"] = sublime.encode_value(args["modules"]) + if "modules" in _args and isinstance(_args["modules"], dict): + _args["modules"] = sublime.encode_value(_args["modules"]) # 轉換前取代 - if "userPreReplace" in args and isinstance(args["userPreReplace"], dict): - args["userPreReplace"] = "\n".join(f"{from_}={to_}" for from_, to_ in args["userPreReplace"].items()) + if "userPreReplace" in _args and isinstance(_args["userPreReplace"], dict): + _args["userPreReplace"] = "\n".join(f"{old}={new}" for old, new in _args["userPreReplace"].items()) # 轉換後取代 - if "userPostReplace" in args and isinstance(args["userPostReplace"], dict): - args["userPostReplace"] = "\n".join(f"{from_}={to_}" for from_, to_ in args["userPostReplace"].items()) + if "userPostReplace" in _args and isinstance(_args["userPostReplace"], dict): + _args["userPostReplace"] = "\n".join(f"{old}={new}" for old, new in _args["userPostReplace"].items()) # 保護字詞 - if "userProtectReplace" in args and isinstance(args["userProtectReplace"], list): - args["userProtectReplace"] = "\n".join(args["userProtectReplace"]) + if "userProtectReplace" in _args and isinstance(_args["userProtectReplace"], list): + _args["userProtectReplace"] = "\n".join(_args["userProtectReplace"]) # 參數: API 全域 - args["apiKey"] = get_setting("api_key") - args["prettify"] = False + _args["apiKey"] = get_setting("api_key") + _args["prettify"] = False # 參數: API convert 端點 - args["text"] = TEXT_DELIMITER.join(view.substr(region) for region in view.sel()) - args["diffEnable"] = False + _args["text"] = TEXT_DELIMITER.join(view.substr(region) for region in view.sel()) + _args["diffEnable"] = False + + _args.update(args or {}) - return args + return _args diff --git a/plugin/settings.py b/plugin/settings.py index 8af96ac..d77d57b 100644 --- a/plugin/settings.py +++ b/plugin/settings.py @@ -1,105 +1,11 @@ from .constant import PLUGIN_NAME -from .types import TD_ConverterInfo -from functools import lru_cache -from typing import Any, Optional, Tuple +from typing import Any, Optional import sublime -@lru_cache -def get_settings_object() -> sublime.Settings: +def get_settings() -> sublime.Settings: return sublime.load_settings(f"{PLUGIN_NAME}.sublime-settings") def get_setting(key: str, default: Optional[Any] = None) -> Any: - return get_settings_object().get(key, default) - - -@lru_cache -def get_converter_info(index: int) -> TD_ConverterInfo: - return get_converters_info()[index] - - -@lru_cache -def get_converters_info() -> Tuple[TD_ConverterInfo, ...]: - return ( - { - "name_api": "Simplified", - "name_eng": "Simplified", - "name_chi": "简体化", - "desc": "将文字转换为简体。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "简", ""), - }, - { - "name_api": "Traditional", - "name_eng": "Traditional", - "name_chi": "繁體化", - "desc": "將文字轉換為繁體。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "繁", ""), - }, - { - "name_api": "China", - "name_eng": "China Localization", - "name_chi": "中国化", - "desc": "将文字转换为简体,并使用中国地区的词语修正。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "中", ""), - }, - { - "name_api": "Hongkong", - "name_eng": "Hongkong Localization", - "name_chi": "香港化", - "desc": "將文字轉換為繁體,並使用香港地區的詞語修正。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "港", ""), - }, - { - "name_api": "Taiwan", - "name_eng": "Taiwan Localization", - "name_chi": "台灣化", - "desc": "將文字轉換為繁體,並使用台灣地區的詞語修正。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "台", ""), - }, - { - "name_api": "Pinyin", - "name_eng": "Pinyin", - "name_chi": "拼音化", - "desc": "將文字轉為拼音。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "拼", ""), - }, - { - "name_api": "Bopomofo", - "name_eng": "Bopomofo", - "name_chi": "注音化", - "desc": "將文字轉為注音。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "注", ""), - }, - { - "name_api": "Mars", - "name_eng": "Mars", - "name_chi": "火星化", - "desc": "將文字轉換為繁體火星文。", - "detail": "", - "st_kind": (sublime.KIND_ID_MARKUP, "火", ""), - }, - { - "name_api": "WikiSimplified", - "name_eng": "Simplified (Wikipeida)", - "name_chi": "维基简体化", - "desc": "只使用维基百科的词库将文字转换为简体。", - "detail": "一般而言,你应该用不到这个模式。", - "st_kind": (sublime.KIND_ID_AMBIGUOUS, "简", ""), - }, - { - "name_api": "WikiTraditional", - "name_eng": "Traditional (Wikipeida)", - "name_chi": "維基繁體化", - "desc": "只使用維基百科的詞庫將文字轉換為繁體。", - "detail": "一般而言,你應該用不到這個模式。", - "st_kind": (sublime.KIND_ID_AMBIGUOUS, "繁", ""), - }, - ) + return get_settings().get(key, default) diff --git a/plugin/types.py b/plugin/types.py index 335a92f..53fac57 100644 --- a/plugin/types.py +++ b/plugin/types.py @@ -1,10 +1,64 @@ -from typing import Tuple, TypedDict +from typing import Dict, List, Optional, Tuple, TypedDict class TD_ConverterInfo(TypedDict): name_api: str # like "WikiTraditional" name_eng: str # like "Traditional (Wikipeida)" name_chi: str # like "維基繁體化" - desc: str # like "只使用維基百科的詞庫將文字轉換為繁體。" - detail: str # like "一般而言,你應該用不到這個模式。" + details: str # like "只使用維基百科的詞庫將文字轉換為繁體。" + annotation: str # like "(少用)" st_kind: Tuple[int, str, str] # like (sublime.KIND_ID_AMBIGUOUS, "繁", "") + + +class TD_ApiRevisionInfo(TypedDict): + build: str + msg: str + time: int + + +class TD_ApiBaseResponse(TypedDict): + code: int + msg: str + revisions: TD_ApiRevisionInfo + execTime: float + + +class TD_ApiConvertResponseData(TypedDict): + converter: str + text: str + diff: Optional[str] + jpTextStyles: List[str] + usedModules: List[str] + textFormat: str + + +class TD_ApiConvertResponse(TD_ApiBaseResponse): + data: TD_ApiConvertResponseData + + +class TD_ApiConvertResponseDataConverterInfo(TypedDict): + name: str + desc: str + cat: str + + +class TD_ApiConvertResponseDataModuleInfo(TypedDict): + name: str + desc: str + cat: str + isManual: bool + + +class TD_ApiServiceInfoResponseData(TypedDict): + converters: Dict[str, TD_ApiConvertResponseDataConverterInfo] + modules: Dict[str, TD_ApiConvertResponseDataModuleInfo] + converterCategories: Dict[str, str] + moduleCategories: Dict[str, str] + textFormats: Dict[str, str] + diffTemplates: Dict[str, str] + allowEmptyApiKey: bool + maxPostBodyBytes: int + + +class TD_ApiServiceInfoResponse(TD_ApiBaseResponse): + data: TD_ApiServiceInfoResponseData