diff --git a/AutoSetSyntax.sublime-settings b/AutoSetSyntax.sublime-settings index ad8cef39..bfaf3c85 100644 --- a/AutoSetSyntax.sublime-settings +++ b/AutoSetSyntax.sublime-settings @@ -287,9 +287,8 @@ "selector": "text.plain", "match": "all", "rules": [ - // the guesslang server does a better job at guessing JavaScript vs TypeScript { - "constraint": "is_guesslang_enabled", + "constraint": "is_magika_enabled", "inverted": true }, { @@ -703,105 +702,5 @@ "winregistry": ["scope:source.reg"], "xml": ["scope:text.xml"], "yaml": ["scope:source.yaml"] - }, - - /////////////////////////////////////// - // Guesslang settings (experimental) // - ///////////////////////////////////////////////////////////////////////// - // You have to restart ST after modifying any of guesslang's settings. // - ///////////////////////////////////////////////////////////////////////// - - // To use this feature, you have to install the server. - // @see https://jfcherng-sublime.github.io/ST-AutoSetSyntax/experimental/ml-based-syntax-detection/#prerequisites - "guesslang.enabled": false, - // The path of the node-like executable. Leave it blank for auto determination. - "guesslang.node_bin": "", - // Extra command line arguments for invoking the node-like executable. - "guesslang.node_bin_args": [], - // The port number which should be used by the guesslang server. - // You can use a negative number for auto determination. - "guesslang.port": -1, - "guesslang.syntax_map": { - "asm": [ - // no good way to do this? - "scope:source.asm.x86_64", - "scope:source.asm.arm", - "scope:source.rvasm", - "scope:source.assembly" - ], - "bat": ["scope:source.dosbatch"], - "c": ["scope:source.c"], - "cbl": ["scope:source.cobol"], - "clj": ["scope:source.clojure"], - "cmake": ["scope:source.cmake"], - "coffee": ["scope:source.coffee"], - "cpp": ["scope:source.c++"], - "cs": ["scope:source.cs"], - "css": [ - // SCSS is a superset and this server can't distinguish SCSS and CSS - "scope:source.scss", - "scope:source.css" - ], - "csv": ["scope:text.advanced_csv", "scope:text.csv"], - "dart": ["scope:source.dart"], - "dm": ["scope:source.dm"], - "dockerfile": ["scope:source.dockerfile"], - "erl": ["scope:source.erlang"], - "ex": ["scope:source.elixir"], - "f90": ["scope:source.modern-fortran"], - "go": ["scope:source.go"], - "groovy": ["scope:source.groovy"], - "hs": ["scope:source.haskell"], - "html": ["scope:text.html.basic"], - "ini": ["scope:source.ini"], - "java": ["scope:source.java"], - "jl": ["scope:source.julia"], - "js": ["scope:source.js"], - "json": ["scope:source.json"], - "kt": ["scope:source.Kotlin"], - "lisp": ["scope:source.lisp"], - "lua": ["scope:source.lua"], - "makefile": ["scope:source.makefile"], - "matlab": ["scope:source.matlab"], - "md": ["scope:text.html.markdown"], - "ml": [], // what is this? - "mm": ["objective-c"], - "pas": ["scope:source.pascal"], - "php": ["scope:embedding.php", "scope:text.html.php"], - "pl": ["scope:source.perl"], - "pm": [], // what is this? - "prolog": ["scope:source.prolog"], - "ps1": ["scope:source.powershell"], - "py": ["scope:source.python"], - "r": ["scope:source.r"], - "rb": ["scope:source.ruby"], - "rs": ["scope:source.rust"], - "scala": ["scope:source.scala"], - "sh": ["scope:source.shell.bash"], - "sql": ["scope:source.sql"], - "swift": ["scope:source.swift"], - "tex": ["scope:text.tex.latex"], - "toml": ["scope:source.toml"], - "ts": ["scope:source.ts"], - "v": ["scope:source.verilog"], - "vba": ["scope:source.vbs"], - "xml": ["scope:text.xml"], - "yml": ["scope:source.yaml"], - // extra from vscode-regexp-languagedetection (modelLangToVSCodeLang) - "coffeescript": ["=coffee"], - "csharp": ["=cs"], - "erlang": ["=erl"], - "haskell": ["=hs"], - "javascript": ["=js"], - "markdown": ["=md"], - "objective-c": ["=mm"], - "perl": ["=pl"], - "powershell": ["=ps1"], - "python": ["=py"], - "ruby": ["=rb"], - "rust": ["=rs"], - "shellscript": ["=sh"], - "typescript": ["=ts"], - "yaml": ["=yml"] } } diff --git a/docs/src/configurations.md b/docs/src/configurations.md index 30992087..83e5857a 100644 --- a/docs/src/configurations.md +++ b/docs/src/configurations.md @@ -497,18 +497,6 @@ To edit project settings, go to `Project` ยป `Edit Project`. If `case_insensitive` is not provided, it will be `true` on Windows but `false` on other OSes. -#### `is_guesslang_enabled` - -!!! example - - ```js - { - "constraint": "is_guesslang_enabled", - } - ``` - - Test whether the `guesslang` server is enabled. - #### `is_hidden_syntax` !!! example diff --git a/docs/src/experimental/ml-based-syntax-detection.md b/docs/src/experimental/ml-based-syntax-detection.md deleted file mode 100644 index 98aa7c71..00000000 --- a/docs/src/experimental/ml-based-syntax-detection.md +++ /dev/null @@ -1,45 +0,0 @@ -# Machine Learning Based Syntax Detection - ---8<-- "refs.md" - -!!! warning "This feature has been deprecated and will be removed in AutoSetSyntax v3." - -## Overview - -It uses machine learning models from VSCode to predict the syntax of codes. - -## Prerequisites - -1. [Node.js][node.js] โ‰ฅ 16 - - Node.js is searched under the following order: - - - User specified path (i.e., `guesslang.node_bin`) - - Sublime Text's `lsp_utils`-managed Node.js/Electron - - `electron` (i.e., the executable of Electron) - - `node` (i.e., the executable of Node.js) - - `code` (i.e., the executable of VS Code) - - `VSCodium` (i.e., the executable of VSCodium) - - !!! Tip "Windows 7 Users" - - The official Node.js v16 installer won't work on Windows 7 but you can simply download a - [portable](https://nodejs.org/dist/latest-v16.x/) version such as `node-v16.20.2-win-x64.zip`, - decompress it and set `guesslang.node_bin` (or add its directory into the `PATH` environment variable). - -1. Install the server. - - Run `AutoSetSyntax: Download Guesslang Server` from the command palette. It will popup a dialogue when it's done. - -1. Enable the feature. - - Set `"guesslang.enabled"` to `true` in AutoSetSyntax's settings. - -After you've done all steps above and then restart ST, it should work after a few seconds. - -## Demo - - diff --git a/menus/Default.sublime-commands b/menus/Default.sublime-commands index b6bc4f4e..7f79aeaf 100644 --- a/menus/Default.sublime-commands +++ b/menus/Default.sublime-commands @@ -34,14 +34,6 @@ "caption": "AutoSetSyntax: Download Dependencies", "command": "auto_set_syntax_download_dependencies", }, - { - "caption": "AutoSetSyntax: Download Guesslang Server", - "command": "auto_set_syntax_download_guesslang_server", - }, - { - "caption": "AutoSetSyntax: Restart Guesslang Server", - "command": "auto_set_syntax_restart_guesslang", - }, { "caption": "AutoSetSyntax: Settings", "command": "edit_settings", diff --git a/plugin/__init__.py b/plugin/__init__.py index 9c0cc07d..101be3f3 100644 --- a/plugin/__init__.py +++ b/plugin/__init__.py @@ -13,8 +13,6 @@ AutoSetSyntaxCreateNewMatchCommand, AutoSetSyntaxDebugInformationCommand, AutoSetSyntaxDownloadDependenciesCommand, - AutoSetSyntaxDownloadGuesslangServerCommand, - AutoSetSyntaxRestartGuesslangCommand, run_auto_set_syntax_on_view, ) from .constants import PLUGIN_CUSTOM_MODULE_PATHS, PLUGIN_NAME, PLUGIN_PY_LIBS_DIR @@ -51,8 +49,6 @@ "AutoSetSyntaxCreateNewMatchCommand", "AutoSetSyntaxDebugInformationCommand", "AutoSetSyntaxDownloadDependenciesCommand", - "AutoSetSyntaxDownloadGuesslangServerCommand", - "AutoSetSyntaxRestartGuesslangCommand", # ST: listeners "AioSettings", "AutoSetSyntaxEventListener", @@ -83,7 +79,6 @@ def _plugin_loaded() -> None: if get_merged_plugin_setting("run_on_startup_views"): sublime.set_timeout_async(_run_on_startup_views) - sublime.run_command("auto_set_syntax_restart_guesslang") def plugin_unloaded() -> None: @@ -93,9 +88,6 @@ def plugin_unloaded() -> None: for window in sublime.windows(): tear_down_window(window) - if G.guesslang_server: - G.guesslang_server.stop() - def _settings_changed_callback(window: sublime.Window) -> None: clear_all_cached_functions() diff --git a/plugin/commands/__init__.py b/plugin/commands/__init__.py index 57840f6e..60b32b1a 100644 --- a/plugin/commands/__init__.py +++ b/plugin/commands/__init__.py @@ -5,8 +5,6 @@ ) from .auto_set_syntax_debug_information import AutoSetSyntaxDebugInformationCommand from .auto_set_syntax_download_dependencies import AutoSetSyntaxDownloadDependenciesCommand -from .auto_set_syntax_download_guesslang_server import AutoSetSyntaxDownloadGuesslangServerCommand -from .auto_set_syntax_restart_guesslang import AutoSetSyntaxRestartGuesslangCommand __all__ = ( # ST: commands @@ -15,8 +13,6 @@ "AutoSetSyntaxCreateNewMatchCommand", "AutoSetSyntaxDebugInformationCommand", "AutoSetSyntaxDownloadDependenciesCommand", - "AutoSetSyntaxDownloadGuesslangServerCommand", - "AutoSetSyntaxRestartGuesslangCommand", # ... "run_auto_set_syntax_on_view", ) diff --git a/plugin/commands/auto_set_syntax.py b/plugin/commands/auto_set_syntax.py index 0c637f98..2296ac36 100644 --- a/plugin/commands/auto_set_syntax.py +++ b/plugin/commands/auto_set_syntax.py @@ -12,7 +12,6 @@ import sublime_plugin from ..constants import PLUGIN_NAME, RE_ST_SYNTAX_TEST_LINE, RE_VIM_SYNTAX_LINE -from ..guesslang.types import GuesslangServerPredictionItem, GuesslangServerResponse from ..helpers import is_syntaxable_view, resolve_magika_label_with_syntax_map from ..libs import websocket from ..logger import Logger @@ -44,101 +43,6 @@ def run(self, edit: sublime.Edit) -> None: run_auto_set_syntax_on_view(self.view, ListenerEvent.COMMAND, must_plaintext=False) -class GuesslangClientCallbacks: - """This class contains event callbacks for the guesslang server.""" - - def on_open(self, ws: websocket.WebSocketApp) -> None: - self._status_msg_and_log(f"๐Ÿค Connected to guesslang server: {ws.url}") - - def on_message(self, ws: websocket.WebSocketApp, message: str) -> None: - try: - response: GuesslangServerResponse = sublime.decode_value(message) - # shorthands - predictions = response["data"] - event = ListenerEvent.from_value(response["event_name"]) - view_id = response["id"] - Logger.log(f"๐Ÿ› Guesslang top predictions: {predictions[:5]}") - except (TypeError, ValueError): - Logger.log(f"๐Ÿ’ฌ Guesslang server says: {message}") - return - except Exception as e: - Logger.log(f"๐Ÿ’ฃ Guesslang exception: {e}") - return - - if not predictions or not (view := get_view_by_id(view_id)) or not (window := view.window()): - return - - predictions.sort(key=itemgetter("confidence"), reverse=True) - - if not (resolved_prediction := self.resolve_guess_predictions(window, predictions)): - return - - # on_message() callback is async and maybe now the syntax has been set by other things somehow - if (current_syntax := view.syntax()) and not is_plaintext_syntax(current_syntax): - return - - best_syntax, confidence = resolved_prediction - details = {"event": event, "reason": "predict", "confidence": confidence} - status_message = f'Predicted as "{get_syntax_name(best_syntax)}"' - if confidence >= 0: - status_message += f" ({int(confidence * 100)}% confidence)" - - assign_syntax_to_view(view, best_syntax, details=details) - sublime.status_message(status_message) - - def on_error(self, ws: websocket.WebSocketApp, error: str) -> None: - self._status_msg_and_log(f"โŒ Guesslang server went wrong: {error}") - - def on_close(self, ws: websocket.WebSocketApp, close_status_code: int, close_msg: str) -> None: - self._status_msg_and_log("๐Ÿ’” Guesslang server disconnected...") - - @classmethod - def resolve_guess_predictions( - cls, - window: sublime.Window, - predictions: Iterable[GuesslangServerPredictionItem], - ) -> tuple[sublime.Syntax, float] | None: - if not (best_prediction := first_true(predictions)): - return None - - settings = get_merged_plugin_settings(window=window) - syntax_map: dict[str, list[str]] = settings.get("guesslang.syntax_map", {}) - - if not (syntax_likes := cls.resolve_language_id(syntax_map, best_prediction["languageId"])): - Logger.log(f'๐Ÿค” Unknown "languageId" from guesslang: {best_prediction["languageId"]}', window=window) - return None - - if not (syntax := find_syntax_by_syntax_likes(syntax_likes, include_plaintext=False)): - Logger.log(f"๐Ÿ˜ข Failed finding syntax from guesslang: {syntax_likes}", window=window) - return None - - return (syntax, best_prediction["confidence"]) - - @classmethod - def resolve_language_id( - cls, - syntax_map: Mapping[str, Iterable[str]], - language_id: str, - *, - referred: set[str] | None = None, - ) -> list[str]: - res: list[str] = [] - referred = referred or set() - for syntax_like in syntax_map.get(language_id, []): - if syntax_like.startswith("="): - if (language_id := syntax_like[1:]) not in referred: - referred.add(language_id) - res.extend(cls.resolve_language_id(syntax_map, language_id, referred=referred)) - else: - res.append(syntax_like) - return res - - @staticmethod - def _status_msg_and_log(msg: str, window: sublime.Window | None = None) -> None: - Logger.log(msg, window=window) - sublime.status_message(msg) - - def _snapshot_view(failed_ret: Any = None) -> Callable[[_T_Callable], _T_Callable]: def decorator(func: _T_Callable) -> _T_Callable: @wraps(func) @@ -208,18 +112,6 @@ def run_auto_set_syntax_on_view( if _assign_syntax_with_heuristics(view, event): return True - if event in { - ListenerEvent.COMMAND, - ListenerEvent.INIT, - ListenerEvent.LOAD, - ListenerEvent.MODIFY, - ListenerEvent.PASTE, - ListenerEvent.SAVE, - ListenerEvent.UNTRANSIENTIZE, - }: - # this is the ultimate fallback and done async - _assign_syntax_with_guesslang_async(view, event) - return _sorry_cannot_help(view, event) @@ -429,24 +321,6 @@ def _assign_syntax_with_magika(view: sublime.View, event: ListenerEvent | None = return assign_syntax_to_view(view, syntax, details={"event": event, "reason": "Magika (Deep Learning)"}) -def _assign_syntax_with_guesslang_async(view: sublime.View, event: ListenerEvent | None = None) -> None: - if not ( - G.guesslang_client - and (view_snapshot := G.view_snapshot_collection.get_by_view(view)) - # don't apply on those have an extension - and (event == ListenerEvent.COMMAND or "." not in view_snapshot.file_name_unhidden) - # only apply on plain text syntax - and ((syntax := view_snapshot.syntax) and is_plaintext_syntax(syntax)) - # we don't want to use AI model during typing when there is only one line - # that may result in unwanted behavior such as a new buffer may be assigned to Python - # right after "import" is typed but it could be JavaScript or TypeScript as well - and (event != ListenerEvent.MODIFY or "\n" in view_snapshot.content) - ): - return - - G.guesslang_client.request_guess_snapshot(view_snapshot, event=event) - - def _sorry_cannot_help(view: sublime.View, event: ListenerEvent | None = None) -> bool: details = {"event": event, "reason": "no matching rule"} Logger.log(f"โŒ Cannot help {stringify(view)} because {stringify(details)}", window=view.window()) diff --git a/plugin/commands/auto_set_syntax_download_guesslang_server.py b/plugin/commands/auto_set_syntax_download_guesslang_server.py deleted file mode 100644 index fca7dbd6..00000000 --- a/plugin/commands/auto_set_syntax_download_guesslang_server.py +++ /dev/null @@ -1,157 +0,0 @@ -from __future__ import annotations - -import gzip -import tarfile -import threading -import time -import urllib.request -import zipfile -from collections.abc import Iterable -from pathlib import Path -from typing import Union - -import sublime -import sublime_plugin - -from ..constants import GUESSLANG_SERVER_URL, PLUGIN_NAME -from ..guesslang.server import GuesslangServer -from ..settings import get_merged_plugin_setting -from ..shared import G -from ..utils import first_true, rmtree_ex - -PathLike = Union[Path, str] - - -class AutoSetSyntaxDownloadGuesslangServerCommand(sublime_plugin.ApplicationCommand): - # Server codes are published on https://github.com/jfcherng-sublime/ST-AutoSetSyntax/tree/guesslang-server - - def description(self) -> str: - return f"{PLUGIN_NAME}: Download Guesslang Server" - - def run(self) -> None: - self.t = threading.Thread(target=self._worker) - self.t.start() - - @classmethod - def _worker(cls) -> None: - sublime.status_message("Begin downloading guesslang server...") - - if (server := G.guesslang_server) and server.is_running(): - server.stop() - time.sleep(1) # wait for stopping the server - - rmtree_ex(GuesslangServer.SERVER_DIR, ignore_errors=True) - - try: - cls._prepare_bin() - - if not GuesslangServer.SERVER_FILE.is_file(): - sublime.error_message(f"[{PLUGIN_NAME}] Cannot find the server: {str(GuesslangServer.SERVER_FILE)}") - - if get_merged_plugin_setting("guesslang.enabled"): - sublime.run_command("auto_set_syntax_restart_guesslang") - - sublime.message_dialog(f"[{PLUGIN_NAME}] Finish downloading guesslang server!") - except Exception as e: - sublime.error_message(f"[{PLUGIN_NAME}] {e}") - - @staticmethod - def _prepare_bin() -> None: - zip_path = GuesslangServer.SERVER_DIR / "source.zip" - download_file(GUESSLANG_SERVER_URL, zip_path) - decompress_file(zip_path) - - # get the folder, which is just decompressed - folder = first_true( - sorted( - filter(Path.is_dir, zip_path.parent.iterdir()), - key=lambda p: p.stat().st_mtime, - reverse=True, - ), - ) - - if not folder: - return - - # move the decompressed folder one level up - guesslang_server_dir = folder.parent - tmp_dir = guesslang_server_dir.parent / ".tmp" - rmtree_ex(tmp_dir, ignore_errors=True) - folder.replace(tmp_dir) - rmtree_ex(guesslang_server_dir, ignore_errors=True) - tmp_dir.replace(guesslang_server_dir) - # cleanup - zip_path.unlink(missing_ok=True) - - -def decompress_file(tarball: PathLike, dst_dir: PathLike | None = None) -> bool: - """ - Decompress the tarball. - - :param tarball: The tarball - :param dst_dir: The destination directory - - :returns: Successfully decompressed the tarball or not - """ - - def tar_safe_extract( - tar: tarfile.TarFile, - path: PathLike = ".", - members: Iterable[tarfile.TarInfo] | None = None, - *, - numeric_owner: bool = False, - ) -> None: - path = Path(path).resolve() - for member in tar.getmembers(): - member_path = (path / member.name).resolve() - if path not in member_path.parents: - raise Exception("Attempted Path Traversal in Tar File") - - tar.extractall(path, members, numeric_owner=numeric_owner) - - tarball = Path(tarball) - dst_dir = Path(dst_dir) if dst_dir else tarball.parent - filename = tarball.name - - try: - if filename.endswith(".tar.gz"): - with tarfile.open(tarball, "r:gz") as f_1: - tar_safe_extract(f_1, dst_dir) - return True - - if filename.endswith(".tar"): - with tarfile.open(tarball, "r:") as f_2: - tar_safe_extract(f_2, dst_dir) - return True - - if filename.endswith(".zip"): - with zipfile.ZipFile(tarball) as f_3: - f_3.extractall(dst_dir) - return True - except Exception: - pass - return False - - -def download_file(url: str, save_path: PathLike) -> None: - """ - Downloads a file. - - :param url: The url - :param save_path: The path of the saved file - """ - - save_path = Path(save_path) - save_path.unlink(missing_ok=True) - save_path.parent.mkdir(parents=True, exist_ok=True) - save_path.write_bytes(simple_urlopen(url)) - - -def simple_urlopen(url: str, chunk_size: int = 512 * 1024) -> bytes: - response = urllib.request.urlopen(url) - data = b"" - while chunk := response.read(chunk_size): - data += chunk - if response.info().get("Content-Encoding") == "gzip": - data = gzip.decompress(data) - return data diff --git a/plugin/commands/auto_set_syntax_restart_guesslang.py b/plugin/commands/auto_set_syntax_restart_guesslang.py deleted file mode 100644 index 11294da5..00000000 --- a/plugin/commands/auto_set_syntax_restart_guesslang.py +++ /dev/null @@ -1,61 +0,0 @@ -from __future__ import annotations - -import socket -import threading -import time -from collections.abc import Iterable - -import sublime -import sublime_plugin - -from ..constants import PLUGIN_NAME -from ..guesslang.client import GuesslangClient -from ..guesslang.server import GuesslangServer -from ..logger import Logger -from ..settings import get_merged_plugin_setting -from ..shared import G -from ..utils import first_true -from .auto_set_syntax import GuesslangClientCallbacks - - -class AutoSetSyntaxRestartGuesslangCommand(sublime_plugin.ApplicationCommand): - def description(self) -> str: - return f"{PLUGIN_NAME}: Restart Guesslang Client And Server" - - def is_enabled(self) -> bool: - return bool(get_merged_plugin_setting("guesslang.enabled")) - - def run(self) -> None: - t = threading.Thread(target=self._worker) - t.start() - - def _worker(self) -> None: - window = sublime.active_window() - port_raw = get_merged_plugin_setting("guesslang.port") - if (port := _resolve_port(port_raw)) < 0: - Logger.log(f"โŒ Guesslang server port is unusable: {port_raw}", window=window) - return - - host = "localhost" - G.guesslang_client = G.guesslang_client or GuesslangClient(host, port, callback=GuesslangClientCallbacks()) - G.guesslang_server = G.guesslang_server or GuesslangServer(host, port) - if G.guesslang_server.restart(): - time.sleep(1) # wait for server initialization - G.guesslang_client.connect() - - -def _resolve_port(port: int | str) -> int: - try: - port = int(port) - except ValueError: - port = -1 - if 0 <= port <= 65535: - ports: Iterable[int] = (port,) - else: - ports = range(30000, 65536) - return first_true(ports, -1, pred=_is_port_available) - - -def _is_port_available(port: int | str) -> bool: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - return s.connect_ex(("localhost", int(port))) != 0 diff --git a/plugin/guesslang/__init__.py b/plugin/guesslang/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/plugin/guesslang/client.py b/plugin/guesslang/client.py deleted file mode 100644 index 518f1651..00000000 --- a/plugin/guesslang/client.py +++ /dev/null @@ -1,88 +0,0 @@ -from __future__ import annotations - -import threading -from typing import Any, Protocol - -import sublime - -from ..libs import websocket -from ..snapshot import ViewSnapshot -from ..types import ListenerEvent - - -class TransportCallbacks(Protocol): - def on_open(self, ws: websocket.WebSocketApp) -> None: - """Called when connected to the websocket.""" - - def on_message(self, ws: websocket.WebSocketApp, message: str) -> None: - """Called when received a message from the websocket.""" - - def on_error(self, ws: websocket.WebSocketApp, error: str) -> None: - """Called when there is an exception occurred in the websocket.""" - - def on_close(self, ws: websocket.WebSocketApp, close_status_code: int, close_msg: str) -> None: - """Called when disconnected from the websocket.""" - - -class NullTransportCallbacks: - def _(*args: Any, **kwargs: Any) -> None: - pass - - on_open = _ - on_message = _ - on_error = _ - on_close = _ - - -class GuesslangClient: - def __init__( - self, - host: str, - port: int, - *, - callback: TransportCallbacks | None = None, - ) -> None: - self.host = host - self.port = port - self.callback = callback or NullTransportCallbacks() - # internals - self._ws: websocket.WebSocketApp | None = None - - def __del__(self) -> None: - if self._ws: - self._ws.close() - - def connect(self) -> None: - def _worker(client: GuesslangClient) -> None: - client._ws = websocket.WebSocketApp( - f"ws://{client.host}:{client.port}", - on_open=client.callback.on_open, - on_message=client.callback.on_message, - on_error=client.callback.on_error, - on_close=client.callback.on_close, - ) - client._ws.run_forever() - - if not self.is_connected(): - # websocket.enableTrace(True) - self.thread = threading.Thread(target=_worker, args=(self,)) - self.thread.start() - - def is_connected(self) -> bool: - return bool(self._ws and self._ws.sock) - - def request_guess_snapshot( - self, - view_snapshot: ViewSnapshot, - *, - event: ListenerEvent | None = None, - ) -> None: - if self.is_connected(): - assert self._ws - self._ws.send( - sublime.encode_value({ - "id": view_snapshot.id, - "content": view_snapshot.content, - "event_name": event.value if event else None, - }) - ) diff --git a/plugin/guesslang/server.py b/plugin/guesslang/server.py deleted file mode 100644 index 2b5761d1..00000000 --- a/plugin/guesslang/server.py +++ /dev/null @@ -1,126 +0,0 @@ -from __future__ import annotations - -import os -import shutil -import subprocess -from pathlib import Path -from typing import Final, Sequence - -from ..constants import PLUGIN_STORAGE_DIR -from ..logger import Logger -from ..settings import get_merged_plugin_setting -from ..utils import expand_variables - - -class GuesslangServer: - SERVER_DIR: Final[Path] = PLUGIN_STORAGE_DIR / "guesslang-server" - SERVER_FILE: Final[Path] = SERVER_DIR / "websocket.js" - - def __init__(self, host: str, port: int) -> None: - self.host = host - self.port = port - self._proc: subprocess.Popen | None = None - """The server process.""" - - def start(self) -> bool: - """Starts the guesslang server and return whether it starts.""" - if self._proc: - Logger.log("โš ๏ธ Server is already running.") - return True - if not (node_info := parse_node_path_args()): - Logger.log("โŒ Node.js binary is not found or not executable.") - return False - node_path, node_args = node_info - Logger.log(f"โœ” Use Node.js binary ({node_path}) and args ({node_args})") - - try: - process = self._start_process( - (node_path, *node_args, self.SERVER_FILE), - cwd=self.SERVER_DIR, - extra_env={ - "ELECTRON_RUN_AS_NODE": "1", - "NODE_SKIP_PLATFORM_CHECK": "1", - "HOST": self.host, - "PORT": str(self.port), - }, - ) - except Exception as e: - Logger.log(f"โŒ Failed starting guesslang server: {e}") - return False - - if process.stdout and process.stdout.read(2) == "OK": - self._proc = process - return True - - Logger.log("โŒ Failed starting guesslang server.") - return False - - def stop(self) -> None: - if not self._proc: - return - try: - self._proc.kill() - except Exception: - pass - try: - self._proc.wait() - except Exception: - pass - self._proc = None - - def restart(self) -> bool: - self.stop() - return self.start() - - def is_running(self) -> bool: - return self._proc is not None - - @staticmethod - def _start_process( - cmd: str | Path | Sequence[str | Path], - extra_env: dict[str, str] | None = None, - **kwargs, - ) -> subprocess.Popen: - if os.name == "nt": - # do not create a window for the process - startupinfo = subprocess.STARTUPINFO() # type: ignore - startupinfo.dwFlags |= subprocess.SW_HIDE | subprocess.STARTF_USESHOWWINDOW # type: ignore - else: - startupinfo = None # type: ignore - - if isinstance(cmd, (str, Path)): - kwargs["shell"] = True - - return subprocess.Popen( - cmd, - startupinfo=startupinfo, - stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - encoding="utf-8", - env=dict(os.environ, **(extra_env or {})), - **kwargs, - ) - - -def parse_node_path_args() -> tuple[str, list[str]] | None: - for node, args in ( - ( - get_merged_plugin_setting("guesslang.node_bin"), - get_merged_plugin_setting("guesslang.node_bin_args"), - ), - ("${lsp_utils_node_bin}", []), - ("electron", []), - ("node", []), - ("code", ["--ms-enable-electron-run-as-node"]), # VSCode - ("codium", []), # VSCodium (non-Windows) - ("VSCodium", []), # VSCodium (Windows) - ): - if (node := shutil.which(expand_variables(node)) or "") and is_executable(node): - return (node, args) - return None - - -def is_executable(path: str | Path) -> bool: - return os.path.isfile(path) and os.access(path, os.X_OK) diff --git a/plugin/guesslang/types.py b/plugin/guesslang/types.py deleted file mode 100644 index bdabdd0a..00000000 --- a/plugin/guesslang/types.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import annotations - -from typing import TypedDict - - -class GuesslangServerResponse(TypedDict): - id: int - """The message ID, which is an ID of a view actually.""" - data: list[GuesslangServerPredictionItem] - event_name: str | None - - -class GuesslangServerPredictionItem(TypedDict): - languageId: str - confidence: float diff --git a/plugin/listener.py b/plugin/listener.py index 241659c0..f7e54522 100644 --- a/plugin/listener.py +++ b/plugin/listener.py @@ -103,10 +103,6 @@ def on_text_changed_async(self, view: sublime.View, changes: list[sublime.TextCh class AutoSetSyntaxEventListener(sublime_plugin.EventListener): - def on_exit(self) -> None: - if G.guesslang_server: - G.guesslang_server.stop() - def on_activated(self, view: sublime.View) -> None: _try_assign_syntax_when_view_untransientize(view) diff --git a/plugin/rules/constraints/__init__.py b/plugin/rules/constraints/__init__.py index 049c59e1..f132852b 100644 --- a/plugin/rules/constraints/__init__.py +++ b/plugin/rules/constraints/__init__.py @@ -4,7 +4,6 @@ from .first_line_contains_regex import FirstLineContainsRegexConstraint from .is_arch import IsArchConstraint from .is_extension import IsExtensionConstraint -from .is_guesslang_enabled import IsGuesslangEnabledConstraint from .is_hidden_syntax import IsHiddenSyntaxConstraint from .is_in_git_repo import IsInGitRepoConstraint from .is_in_hg_repo import IsInHgRepoConstraint @@ -33,7 +32,6 @@ "FirstLineContainsRegexConstraint", "IsArchConstraint", "IsExtensionConstraint", - "IsGuesslangEnabledConstraint", "IsHiddenSyntaxConstraint", "IsInGitRepoConstraint", "IsInHgRepoConstraint", diff --git a/plugin/rules/constraints/is_guesslang_enabled.py b/plugin/rules/constraints/is_guesslang_enabled.py deleted file mode 100644 index 66e78f51..00000000 --- a/plugin/rules/constraints/is_guesslang_enabled.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import annotations - -from typing import final - -import sublime - -from ...settings import get_merged_plugin_setting -from ..constraint import AbstractConstraint - - -@final -class IsGuesslangEnabledConstraint(AbstractConstraint): - def test(self, view: sublime.View) -> bool: - return bool(get_merged_plugin_setting("guesslang.enabled", False, window=view.window())) diff --git a/plugin/shared.py b/plugin/shared.py index 43e532af..610ea739 100644 --- a/plugin/shared.py +++ b/plugin/shared.py @@ -4,13 +4,11 @@ import sublime -from .guesslang.server import GuesslangServer from .settings import get_merged_plugin_settings from .snapshot import ViewSnapshotCollection from .types import Optimizable, WindowKeyedDict if TYPE_CHECKING: - from .guesslang.client import GuesslangClient from .rules import SyntaxRuleCollection DroppedRules = List[Optimizable] @@ -36,12 +34,6 @@ class SyntaxRuleCollections(_WindowKeyedDict_SyntaxRuleCollection): class G: """This class holds "G"lobal variables as its class variables.""" - guesslang_client: GuesslangClient | None = None - """The guesslang client object, which interacts with the Node.js guesslang server.""" - - guesslang_server: GuesslangServer | None = None - """The guesslang server object.""" - startup_views: set[sublime.View] = set() """Views exist before this plugin is loaded when Sublime Text just starts.""" diff --git a/sublime-package.json b/sublime-package.json index b9876916..1326e1a8 100644 --- a/sublime-package.json +++ b/sublime-package.json @@ -15,9 +15,6 @@ }, { "$ref": "sublime://settings/AutoSetSyntax#/definitions/magika_settings" - }, - { - "$ref": "sublime://settings/AutoSetSyntax#/definitions/guesslang_settings" } ], "definitions": { @@ -220,7 +217,6 @@ "first_line_contains_regex", "is_arch", "is_extension", - "is_guesslang_enabled", "is_hidden_syntax", "is_in_git_repo", "is_in_hg_repo", @@ -249,7 +245,6 @@ "Test whether the first line of the file contains any given regexes. The `args` is a list of regexes.", "Test whether the system architecture is one of the given values. The `args` is a list of `x32`, `x64` and `arm64`.", "Test whether the file name has any given extensions. The `args` is a list of extensions.", - "Test whether the `guesslang` server is enabled.", "Test whether the current syntax is hidden.", "Test whether the file is in a git repository.", "Test whether the file is in a Mercurial repository.", @@ -483,38 +478,6 @@ "default": {} } } - }, - "guesslang_settings": { - "properties": { - "guesslang.enabled": { - "description": "Enables the guesslang feature.", - "type": "boolean", - "default": false - }, - "guesslang.node_bin": { - "markdownDescription": "The path of the node-like executable. Leave it blank for auto determination.", - "type": "string", - "default": "" - }, - "guesslang.node_bin_args": { - "markdownDescription": "Extra command line arguments for invoking the node-like executable.", - "type": "array", - "items": { - "type": "string" - }, - "default": [] - }, - "guesslang.port": { - "description": "The port number which should be used by the guesslang server. You can use a negative number for auto determination.", - "type": "number", - "default": -1 - }, - "guesslang.syntax_map": { - "markdownDescription": "The relationship between `languageId`s in VSCode and syntaxes in ST.", - "type": "object", - "default": {} - } - } } } }